Skip to main content
rephrasing
Source Link
Andy
  • 10.4k
  • 4
  • 27
  • 51

Doing it that way not only violates the SRP (1. reason to change is, if you decided to set different validation rules of integral data, 2. reason to change is, if you decided, the validate method should perform its operation in a different way, because you decided to switch from MySQL to PostgreSQL and it somehow requires a totally different code to perform the validating operation.

  • 1st reason to change is, if you decided to set different validation rules of integral data.
  • 2nd reason to change is, if you decided, the validate method should perform its operation in a different way, because you decided to switch from MySQL to PostgreSQL and it somehow requires a totally different code to perform the validating operation.

Doing it that way not only violates the SRP (1. reason to change is, if you decided to set different validation rules of integral data, 2. reason to change is, if you decided, the validate method should perform its operation in a different way, because you decided to switch from MySQL to PostgreSQL and it somehow requires a totally different code to perform the validating operation.

Doing it that way violates the SRP.

  • 1st reason to change is, if you decided to set different validation rules of integral data.
  • 2nd reason to change is, if you decided, the validate method should perform its operation in a different way, because you decided to switch from MySQL to PostgreSQL and it somehow requires a totally different code to perform the validating operation.
typos
Source Link
Andy
  • 10.4k
  • 4
  • 27
  • 51

Even though very simple, canVote method is still a method which encapsulates the internal implementation nobody should know about. NotFurthermore, it gives you access to a variable, which does not even exist in the class as a member, because the variable is made during the runtime as a return type. So you cannot even access the CanVote variable directly.

  • Domain Driven Design: Build your app around your business-logic, that is your domain, not around your data (building around your DL usually makes your dataclasses include both business and persistence logic).

    Pass domain models around your applications, if you do, you will never be able to pass an object in an invalid state.

  • Make rich domain models: Your classes have to include some logic. They are responsible for your business, let them take care of handling their data.

  • SRP is a recommendation: Do not treat it as an idol and do not follow it blindly.

    For example, even though it seems as a different responsibility, implementing bool Equals() method on an object is a completely valid operation, the method should however be used to compare only two objects of the same type and return false every time a different type is passed to it.

    Because if you compare an Apple and a Banana, no matter how much effort will the Banana put into looking like an Apple, it will never be an Apple.

  • Domain-driven Design by Eric Evans: An excelent book on what it says in the nametitle.
  • Patterns of Enterprise Application Architecture by Martin Fowler: An excelent book to read before diving into agile development. Includes many amazing and modern design patterns, which in my opinion should be enforced, but sadly are ignored in favor of patterns which are easier to implement but don't provide as much space for code reuse.
  • Design Patterns: Elements of Reusable Object-Oriented Software by The "Gang of Four" (Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides): A no. 2 book about design patterns to read. Great for providing general outlook.

Even though very simple, canVote method is still a method which encapsulates the internal implementation nobody should know about. Not it gives you access to a variable, which does not even exist in the class as a member, because the variable is made during the runtime as a return type. So you cannot even access the CanVote variable directly.

  • Domain Driven Design: Build your app around your business-logic, that is your domain, not around your data (building around your DL usually makes your data include both business and persistence logic).

    Pass domain models around your applications, if you do, you will never be able to pass an object in an invalid state.

  • Make rich domain models: Your classes have to include some logic. They are responsible for your business, let them take care of handling their data.

  • SRP is a recommendation: Do not treat it as an idol and do not follow it blindly.

    For example, even though it seems as a different responsibility, implementing bool Equals() method on an object is a completely valid operation, the method should however be used to compare only two objects of the same type and return false every time a different type is passed to it.

    Because if you compare an Apple and a Banana, no matter how much effort will the Banana put into looking like an Apple, it will never be an Apple.

  • Domain-driven Design by Eric Evans: An excelent book on what it says in the name.
  • Patterns of Enterprise Application Architecture by Martin Fowler: An excelent book to read before diving into agile development. Includes many amazing and modern design patterns, which in my opinion should be enforced, but sadly are ignored in favor of patterns which are easier to implement but don't provide as much space for code reuse.
  • Design Patterns: Elements of Reusable Object-Oriented Software by The "Gang of Four" (Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides): A no. 2 book about design patterns to read. Great for providing general outlook.

Even though very simple, canVote method is still a method which encapsulates the internal implementation nobody should know about. Furthermore, it gives you access to a variable, which does not even exist in the class as a member, because the variable is made during the runtime as a return type. So you cannot even access the CanVote variable directly.

  • Domain Driven Design: Build your app around your business-logic, that is your domain, not around your data (building around your DL usually makes your classes include both business and persistence logic).

    Pass domain models around your applications, if you do, you will never be able to pass an object in an invalid state.

  • Make rich domain models: Your classes have to include some logic. They are responsible for your business, let them take care of handling their data.

  • SRP is a recommendation: Do not treat it as an idol and do not follow it blindly.

    For example, even though it seems as a different responsibility, implementing bool Equals() method on an object is a completely valid operation, the method should however be used to compare only two objects of the same type and return false every time a different type is passed to it.

    Because if you compare an Apple and a Banana, no matter how much effort will the Banana put into looking like an Apple, it will never be an Apple.

  • Domain-driven Design by Eric Evans: An excelent book on what it says in the title.
  • Patterns of Enterprise Application Architecture by Martin Fowler: An excelent book to read before diving into agile development. Includes many amazing and modern design patterns, which in my opinion should be enforced, but sadly are ignored in favor of patterns which are easier to implement but don't provide as much space for code reuse.
  • Design Patterns: Elements of Reusable Object-Oriented Software by The "Gang of Four" (Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides): A no. 2 book about design patterns to read. Great for providing general outlook.
Source Link
Andy
  • 10.4k
  • 4
  • 27
  • 51

Programmers are mostly divided into two groups:

  • those who think accessors and mutators are okay
  • those who think accessors and mutators are evil

Neither of the two groups is correct. Just as anything else in programming, accessors and mutators may be used correctly or incorrectly. But does it mean, they are necessarily evil? Not really. To truly understand when is the right time to use an accessor, you must know what Single responsibility principle is really about.

The problem begins, when people start creating Anemic Domain Models as a part of their procedure to SOLIDify their code.

Many programmers I have worked with misunderstand the concept of Single Responsibility Principle and are afraid to put any logic inside their models, because they are afraid it would break the SRP. This means they end up with models which are really stupid, contain little to no business logic and have to be passed around to thirty different classes to be able to accomplish the simplest data operations.

I believe SRP is the most difficult SOLID principle to get right, what's worse, it's the first of them. Programmers focus so hard on it they usually forget about other important things.


The Single Responsibility principle

Where does it begin and end?

A question, which is so hard to answer, that it is the reason why so many people don't understand the SRP definition.

Let's take a look at a very simple class Human.

public class Human
{
    public readonly DateTime    DateTimeOfBirth;
    public          DateTime?   DateTimeOfDeath;
    public          string      Name { get; private set; }
    public          float       Height { get; private set; }
    public          float       Weight { get; private set; }

    public Human
        (
        DateTime    dateTimeOfBirth,
        string      name,
        float       height,
        float       weight,
        DateTime?   dateTimeOfDeath = null
        )
    {
        DateTimeOfBirth = dateTimeOfBirth;
        DateTimeOfDeath = dateTimeOfDeath;
        Name            = name;
        Height          = height;
        Weight          = weight;
    }
}

As every domain model, the responsibility of this class is to take care of its data and do it well. Does it take care of its data? It does. Does it do it well? Not so much. There are literally no rules for data manipulation of this class, you could set a DateTimeOfDeath before the DateTimeOfBirth, that is an invalid operation, which should never occur.

After tweaking the class you may end up with something like this:

public class InvalidInputException : Exception
{
    public InvalidInputException(string message) : base(message) { }
}

public class Human
{
    public readonly DateTime    DateTimeOfBirth;
    public          DateTime?   DateTimeOfDeath { get; private set; }
    public          string      Name { get; private set; }
    public          float       Height { get; private set; }
    public          float       Weight { get; private set; }

    public Human
        (
        DateTime    dateTimeOfBirth,
        string      name,
        float       height,
        float       weight,
        DateTime?   dateTimeOfDeath = null
        )
    {
        if (dateTimeOfBirth > DateTime.Now) {
            throw new InvalidInputException("The date and time of birth must not be higher than current date and time.");
        }

        if (dateTimeOfDeath != null && dateTimeOfDeath < dateTimeOfBirth) {
            throw new InvalidInputException("The date and time of death must not be before the date and time of birth.");
        }

        if (name == "") {
            throw new InvalidInputException("The name must not be an empty string.");
        }

        if (height <= 0) {
            throw new InvalidInputException("The height must be a positive number");
        }

        if (weight <= 0) {
            throw new InvalidInputException("The weight must be a positive number");
        }

        DateTimeOfBirth = dateTimeOfBirth;
        DateTimeOfDeath = dateTimeOfDeath;
        Name            = name;
        Height          = height;
        Weight          = weight;
    }

    public void gainWeight(float gainedWeight)
    {
        if (gainedWeight < 0) {
            throw new InvalidInputException("The gained weight must be a positive number.");
        }

        Weight += gainedWeight;
    }

    public void loseWeight(float lostWeight)
    {
        if (lostWeight < 0) {
            throw new InvalidInputException("The lost weight must be a positive number.");
        }

        if (Weight - lostWeight < 0) {
            throw new InvalidInputException("The amount of lost weight must not be greater than the current weight.");
        }

        Weight -= lostWeight;
    }

    public void changeName(string newName)
    {
        if (newName == "") {
            throw new InvalidInputException("The name must not be an empty string.");
        }

        Name = newName;
    }

    public void markAsDead()
    {
        DateTimeOfDeath = DateTime.Now;
    }

    public bool isAlive()
    {
        return DateTimeOfDeath == null ? true : false;
    }
}

While remaining simplicity, you now have a class which does something. It encapsulates the logic for the data manipulation. You no longer need to worry about validation of passed data, because the class will tell you what exactly is wrong, should something be.

But the class now has multiple responsibilites! It contains data as well as performs its validation and even creates a DateTime object.

Based on Single Responsibility responsibility, a class or module should have one, and only one, reason to change, but if I, in the future, decide, I want to allow a Human to have an empty name or that DateTimeOfDeath can actually occur before the DateTimeOfBirth, I will have to perform two changes to my class, but I am only allowed to perform one.

No, it does not have multiple responsibilities. This class is responsible for its data, you can pass any data to the class and the class itself will make sure the data is correct. That's all part of its logic.

If you decide to change the rules for integral data validation, even if you changed all of them, you are not violating the one reason to change rule.

The class actually has only one reason to change, that is, if you decided the integral validation should be performed in a different way, and it applies to all the data inside your class.


The connection of SRP and Getters

Following the previous example, even though a class should always contain valid data, you still decide you would like to validate the data so the object can be safely passed to a MySQL database.

You create a Validation interface containing a bool validate method and make your Human class implement it, which will ensure the class' data is MySQL valid.

Doing it that way not only violates the SRP (1. reason to change is, if you decided to set different validation rules of integral data, 2. reason to change is, if you decided, the validate method should perform its operation in a different way, because you decided to switch from MySQL to PostgreSQL and it somehow requires a totally different code to perform the validating operation.

On top of that, all the previous code for MySQL will be either left commented out or completely removed, so if you at any time in the future again decide to validate for MySQL, you will probably have to write all the code again. That's a huge red flag, that something is wrong.

You are forced to create Validators for your class, which somehow have to get access to its data. That's where the accessors come into play.

Accessors (getters) provide a simple public interface to class' internal implementation. If the Validator may not access some attribute, because the accessor for it does not exist, then the Validator should not. It should trust the object its validating, that the internal components will always be valid.

The accessors are there, so you can retrieve the data from the structure and when you retrieve it, unless you change the data, you may treat is as a part of the object.

The class should also perform operations for you, such as finding out whether a Human can vote or not, using a bool canVote() method.

public bool canVote()
{
    const int allowedVotingAge = 18;

    return (DateTime.Now.Subtract(DateTimeOfBirth).TotalDays/365 >= allowedVotingAge);
}

That looks much better than doing the following operation:

var human = new Human(/* */);

const int allowedVotingAge = 18;
var canVote = DateTime.Now.Subtract(human.DateTimeOfBirth).TotalHours/365 <= allowedVotingAge;

Once again, the class itself knows the rules to finding out, whether a Human can vote or not. A user of the Human class should not be forced to come up with the logic.

Even though very simple, canVote method is still a method which encapsulates the internal implementation nobody should know about. Not it gives you access to a variable, which does not even exist in the class as a member, because the variable is made during the runtime as a return type. So you cannot even access the CanVote variable directly.


A few guidlines on how to become a better developer

  • Domain Driven Design: Build your app around your business-logic, that is your domain, not around your data (building around your DL usually makes your data include both business and persistence logic).

    Pass domain models around your applications, if you do, you will never be able to pass an object in an invalid state.

  • Make rich domain models: Your classes have to include some logic. They are responsible for your business, let them take care of handling their data.

  • SRP is a recommendation: Do not treat it as an idol and do not follow it blindly.

    For example, even though it seems as a different responsibility, implementing bool Equals() method on an object is a completely valid operation, the method should however be used to compare only two objects of the same type and return false every time a different type is passed to it.

    Because if you compare an Apple and a Banana, no matter how much effort will the Banana put into looking like an Apple, it will never be an Apple.


Recommended reading

  • Domain-driven Design by Eric Evans: An excelent book on what it says in the name.
  • Patterns of Enterprise Application Architecture by Martin Fowler: An excelent book to read before diving into agile development. Includes many amazing and modern design patterns, which in my opinion should be enforced, but sadly are ignored in favor of patterns which are easier to implement but don't provide as much space for code reuse.
  • Design Patterns: Elements of Reusable Object-Oriented Software by The "Gang of Four" (Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides): A no. 2 book about design patterns to read. Great for providing general outlook.