3

When does an object truly belong to a class? I'm familiar with the "has-a" relationship, but in the case of an Employee object, it "has-a" 401k, but it's not really composed of a 401k object. As an employee, my 401k does not make me who I am.

What I'm getting at is, I don't feel that I should need to reach into the Employee object to get to his 401k. Adherence to this would be considered adherence to the Law of Demeter (e.g. A().B().C().D()), which I would like to follow.

Additionally, an Employee object has a lot of attributes like an EmployeeID, age, dateOfBirth, etc. You could end up with infinite attributes just for an Employee, which all fall under the same responsibility.

In the case of a 401K, he doesn't manage the 401k itself so the Employee still adheres to the Single Responsibility Principle, but he does "have" one. You just need to reach through the Employee object to get to his 401k. Even a Supervisor doesn't really compose an Employee. Similar to a 401k, maybe the EmployeeRepository should have a FindSupervisor(Employee employee) method.

Is this where a repository & service comes in?

class Employee
{
  public Employee(401k 401k) {...}

  public 401K Get401K() 
  { 
    return 401k; // EWW! No, does not follow LoD
  } 

  public Money Calculate401k() 
  { 
    return 401k.calculate(); // Ugly and breaks SRP?  No Employee should calculate 401k just to follow LoD...
  } 

  private 401K 401k;
}

class 401K
{
  public void MakeContribution(IContribution contribution) 
  {
    //add contribution
  }

  public Money Calculate(ICalculator calculator) 
  {
    //calculate & return money object
  }
}

VS

class Employee
{
  public int Age() {//calculate age with dob...}
  // Other employee-specific methods

  private string name;
  private DateTime dob;
  // other employee-specific attributes
}

class 401kRespository
{
  public 401K FindByEmployee(Employee emp) {// return 401k of Employee);
  // More 401KRepo functions...
}

class 401K
{
  public void MakeContribution(IContribution contribution) {//add contribution}
  public Money Calculate(ICalculator calculator) {//calculate}
}
  • Are you sure the Employee containing all the metadata and the Employee used for the renumeration are actually the same objects? They may actually both be independent and part of different bounded contexts, or even belonging in different systems. You may eventually find out that in the end, the Employee used for calculating the 401K isn't an employee, but actually is a Renumerable which has the calculate401k() method, and it suddenly makes more sense. I may be missing context, but from your question, I see a CRUD subsystem and a Remuneration subsystem, which could evolve separately. – Vincent Savard Dec 15 '17 at 15:37
1

The ambiguity of composition

A has-a relation means that composition is used instead of an is-a inheritance.

In object oriented programming, composition means just a combination of elements. For instance the class Employee would combine several members such as name, address, currentJob and so on. Composition for 401k means to add a member 401k. And then, there would be no doubt that the class has-a 401k member. In this context, the has-a is about the class members.

In object oriented modeling, composition has a more specific meaning: it is a strong whole-part relationship, where the parts are not shared (according to UML). Here, the has-a is about the objects of the class. And your are right, the 401k pension plan is not a part of an employee.

Modeling your domain

In your domain, there is definitively an association between an Employee and a 401K.

The next questions are about the interdependency between the two classes:

  • Navigability of this association: do you navigate only from Employee to 401K (case 1) or do you also need to navigate in both direction, e.g. from a 401K to the corresponding Employee and vis-versa (case 2) ?

  • Aggregate or not: are Employee and 401K to be understood as a whole for the purpose of the data change (case 1) or can they be changed independently of each other (case 2) ?

Conclusion

According to your explanation, I understand that case 2 would seem more appropriate. So you would have 2 repositories: one for the employees, and one for the plans.

1

As an employee, my 401k does not make me who I am.

401k doesn't have to be a part of an identity of an Employee. If you equate the concepts of state and identity (thus treating all of your objects as immutable), obtaining (calculating?) his 401k could be a part of its behavior. This approach aligns with Uniform access principle.

What I'm getting at is, I don't feel that I should need to reach into the Employee object to get to his 401k. Adherence to this would be considered adherence to the Law of Demeter

Law of Demeter, as I see it, is about protecting encapsulation. It doesn't allow to reveal object's internal structure. While I don't see any problems in sequentially calling objects' methods, representing behavior that is extracted behind an interface.

Additionally, an Employee object has a lot of attributes like an EmployeeID, age, dateOfBirth, etc.

It doesn't have to -- in theory, at least :) Expose behavior instead of plain data, if possible. Though I realize you must be using an ORM-framework. Anyway, the fact that you keep an explicit reference to your 401k object is just an implementation detail induced by the ORM, if I got it right. It

That said, making 401k a part of your employee or not is more a question of your semantics. Personally, I can't imagine 401k without an employee. And employee must have a 401k (correct me if I'm wrong, I'm not from US). So the chances are you'd want to treat an employee as an aggregate root, with 401k hidden behind its encapsulation boundary.

  • A 401k needs an employee, but an employee does not always have/need a 401k. – keelerjr12 Dec 15 '17 at 16:04
  • With the Law of Demeter, if you view it as a form of code duplication, then you realize it should be adhered to strictly (unless it's a Fluent interface, etc.). What I mean is let's say I use the relationship A().B().C().D().E() in 100+ different places, but then one day an architect says we're restructuring and won't have a C object anymore. Now I need to go to those 100+ places and fix the A().B().C().D().E() to say A().B().D().E(). If I had encapsulated this properly, I would have had to change it in one place only. Obviously if I had used an interface I wouldn't have this – keelerjr12 Dec 15 '17 at 16:08
  • issue, because they should implement the interface. If there interface changes well then I'd be SOL anyway. But there's a key difference here... This would force me to implement an interface for every class interaction and this is just plain silly. – keelerjr12 Dec 15 '17 at 16:11
  • 1
    To your first comment: why do you reference a 401k from an Employee then? Keep them both as different aggregate roots, so that 401k could reference Employee only by id. It'd require to extract all the necessary for 401k data from an Employee class. So this makes your system less coupled and more object-oriented in terms of combining data and behavior. – Zapadlo Dec 15 '17 at 16:42
  • 1
    It depends on you :) The best practice is to keep your aggregates small. This intrinsically implies less things to encapsulate. But first of all (and I believe the most important part), make sure you identified your service boundaries (or Bounded context) correctly, so you don't mix up inherently different behavior in a single class. – Zapadlo Dec 15 '17 at 16:48

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.