Pergunta

Even after reading a bunch I'm still falling into the same trap. I have a class, usually an enity. I need to implement more than one, similar operations on this type. It feels wrong to (seemingly arbitrarily) choose one of these operations to belong inside the entity and push the others out to a separate class; I end up pushing all operations to service classes and am left with an anaemic domain.

As a crude example, imagine the typical Employee class with numeric properties to hold how many paid days the employee is entitled to for both sickness and holiday and a collection of days taken for each.

public class Employee
{
    public int PaidHolidayAllowance { get; set; }

    public int PaidSicknessAllowance { get; set; }

    public IEnumerable<Holiday> Holidays { get; set; }

    public IEnumerable<SickDays> SickDays { get; set; }
}

I want two operations, one to calculate remaining holiday, another for remaining paid sick days. It seems strange to include say, CalculateRemaingHoliday() in the Employee class and bump CalculateRemainingPaidSick() to some PaidSicknessCalculator class. I would end up with a PaidSicknessCalculator and a RemainingHolidayCalculator and the anaemic Employee entity as seen above. The other alternative would be to put both operations in the Employee class and kick Single Responsibility to the curb. That doesn't make for particularly maintainable code.

I suppose the Employee class should have some initialisation/validation logic (not accepting negative alowances etc.) So maybe I just stick to basic initialisation and validation in the entities themselves and be happy with my separate calculator classes. Or maybe I should be asking myself if Anaemic Domain is actually causing me some tangible problems with my code.

Foi útil?

Solução

Since we're talking about SRP, I think it's a good idea to dissect exactly what a responsibility can mean in the context of an entity. To me their responsibilities come in 2 kinds :

Primary responsibilities of an Entity

  • Identifying itself
  • Composing itself with objects given to it (adding sub-entities/value objects etc.)
  • If you're into DDD : for an Aggregate Root, enforcing aggregate invariants

Secondary responsibilities of an Entity

  • Any other behavior defined in the domain language that naturally seems to belong in the entity

In your example, CalculateRemainingHoliday() and CalculateRemainingPaidSick() clearly fall in the second category.

Even if primary responsibilities taken together can be considered as one responsibility (that of "being an Entity"), I think we can all agree that most rich domain entities still tend to not adhere to SRP strictly speaking, because of secondary responsibilities - these are a growing crowd by nature, and each time you add or remove one of them, the entity effectively changes for a different reason. However, you can design your entities so that they comply with a lesser kind of SRP : changing something inside one secondary responsibility shouldn't change the entity class itself.

In other words, you should design your entities so that secondary responsibilities are delegated to other objects. These can be sub-entities, value objects, or objects that were injected into the entity. For instance, Employee could have a HolidayCalculationStrategy and a SickDaysCalculationStrategy that take care of these concerns. Although they are closely related to an Employee and in fact manipulate Employee data, these objects will be the only ones modified when said strategies need to change, the entity itself remaining untouched.

In summary, rich domain entities can't respect SRP to the letter, but it's necessary evil to ensure cohesion and avoid anemicness. They can still be SRP-ish enough that you can tweak non-quintessential domain behaviors exposed by an entity without touching the code of the entity itself.

Interesting links about SRP vs Rich Domain Model :

https://stackoverflow.com/questions/10454593/can-a-rich-domain-model-violate-the-single-responsibility-principle

http://blog.pluralsight.com/2012/03/02/whats-the-single-responsibility-of-an-entity-in-domain-driven-design/

Outras dicas

If you have several very similar operations, one approach is to make them a single parameterized operation, and another is to split them into a dedicated class and aggregate several instances.

parameterized pseudo-code example:

public class Employee
{
    public enum PaidAbsenceType { Vacation, Sickness, MPternity, Training, Travel };

    public void setAllowance(PaidAbsenceType type, int amount);
    public int getAllowance(PaidAbsenceType type);

    public void useAllowance(PaidAbsenceType type, int amount);
    public int getUsed(PaidAbsenceType type);
}

aggregated pseudo-code example:

public class Employee
{
    public class PaidAbsence {
        public int allowance { get; set; }
        public int used { get; set; }
    }

    public PaidAbsence Vacation;
    public PaidAbsence Sickness;
    public PaidAbsence MPternity;
    public PaidAbsence Training;
    public PaidAbsence Travel;
}

In both cases, the responsibility of the containing class is to aggregate these members, and if necessary to co-ordinate them.

Hard-coding two versions of the same logic into your class, unless there are actual behavioural differences, smells a bit odd.

Note that in either of the above cases, a single calculator would suffice (either passing the PaidAbsenceType of interest, or directly passing the appropriate PaidAbsence object) - again assuming the actual logic is the same.

The other alternative would be to put both operations in the Employee class and kick Single Responsibility to the curb. That doesn't make for particularly maintainable code.

Why? It seems you have an excessively narrow concept what a "responsibility" means in the single responsibility principle. It certainly does not mean each class is only allowed to have a single method containing logic.

If the Employee class contains all the data those methods need, put both of them into the class. They're really a single responsibility: tracking paid free days. If the Employee class gets more complex, you might want to move that aspect into a class of its own - but one that represents a sub-entity of Employee, not a service class.

As a completely non-technical sidenote: I find the concept of a "PaidSicknessAllowance" to be really insane and broken.

You must have read about Strategy pattern. By implementing this pattern, you are moving responsibility of calculating remaining days of vacation and sickness into separate object. But this object still remains related to the Employee. This could also allow you implement "Vacation" as single class, but have "SickDays" and "Holidays" properties of this class inside and employee. Thus reducing code and behavior duplication.

If splitting off a responsibility seems nonsensical to you, chances are you are already following the SRP fairly well, even if you haven't consciously applied it.

To me, it's very difficult to determine if I'm following the SRP by simply asking what the responsibility is, for the reason you've already discovered: you can define a responsibility to be either very large or very small, when the ideal is somewhere in between.

The easiest way to spot an SRP violation is by noticing that you are tending to group methods within a class. Some people even put section comments, like this:

/******** Time off calculations ********/

If you catch yourself doing that, or being tempted to do that, that's a sign you're most likely violating the single responsibility principle.

Licenciado em: CC-BY-SA com atribuição
scroll top