Depending upon abstraction is not about interfaces, is about roles

I recently stumble upon this code where someone took an object and extracted an interface from its methods, something like:

class Parent: IParent{
    public void Teach(){}
    public void Work(){}
}

interface IParent{
    public void Teach();
    public void Work();
}

I’ve seen many people (including myself, tons of times) do this and think: “There. Now we are depending upon abstractions“. The truth is, we are depending on an interface, but depending on abstraction is way more than that.

An object design guideline

All objects have a raison d’etre: to serve. They serve other objects, systems, or users. Although that may seem obvious, I’ve found that’s something often overlooked.

Warning: Rant ahead.

I have mentioned this before but, I believe the main reason object-oriented programming is often criticized is that is not well understood.

The idea of an object as an abstract concept that can represent either code or data has not reached enough people to change the overall perception.

A lot of the people I have seen complaining about OOP is doing structured programming. They still tend to separate the data from the operations that are done upon it. Basically structs and modules. It’s sad because this yield software that is hard. Hard to change, hard to understand, hard to correct. Is not soft (as in soft-ware). I blame schools for this. At least in my particular experience, OOP is often delivered as an extension of structured programming, much like C++ is often seen as an extension of C.

We need to reeducate ourselves on the way we think: OOP is not about using object-oriented technology but about thinking in an object-oriented fashion.

This is the reason I started this blog.

End of Rant 😛

So thinking of objects as either data bags or function bags is the result of ignoring a fundamental design question: whom does this object serve?

To answer this question you have to start with the client (object, system, user) needs. This leads itself to a top-down analysis/design approach. But a lot of us are trained to start a system design by thinking on the structure of a relational database, which it’s a bottom-up approach. Let’s see how they differ from each other.

The Database first approach

When designing a relational database, the thinking tools available are Entities and the Relationships between them, often displayed in an ER diagram. So we start with Entities from the nouns on the domain: Parent, Teacher, Student, Child, Class, Course, and so on. I’m pretty sure you can think of a domain just by looking at these concepts.

Now that you have these Entities, you have to think about the processes that interact with them. How do we create a new student? How do we update some of its data? How do we delete it? If you look closely you will find that most everything is modeled as CRUD operations around the Entities. In this scenario, the entities are your abstractions.

The Objects first approach

In this case, you would start by thinking about the needs of the user. This often is expressed as tasks. We usually discover and document these in the form of user stories or use cases. This initial set of needs will serve as the basis for the features of the system. We can now start creating the objects to fullfill these needs. Often this objects will represent the tasks expressed by the user. This is what is known as the application layer on DDD.

From here on things start to get interesting. Pick one of these task objects. What do you need to accomplish this particular task? These are the needs of the object. Now here comes the trick: define an interface/abstract class that fulfills one specific need and name it as such. By doing this we force ourselves to define a specific concept for a specific need in a specific operation. We call this kind of concepts: Roles.

I love the naming schema that Udi Dahan uses for Roles: IDoSomething/ ICanDoSomething. In this approach roles are your abstractions.

Entity vs Role

Let us go back to the original issue: what it means to depend on abstractions?
To answer that we need to answer another question first: what is an abstraction?

Let’s consider the difference between the 2 kinds of abstraction we’ve seen so far: Entity and Role.

First, let’s clarify something: Entities as we have discussed so far don’t belong to the OOP paradigm, they belong to the Relational paradigm. We have discussed before that the needs addressed by a model in the relational paradigm are geared toward disk space optimization, whereas the needs of an object model, particularly an object domain model, are about representing business concepts and interactions in a way easy to change and understand.

Side note: There’s actually an Entity concept in DDD.
An Entity is an object with a unique id. Often, DDD Entity objects overlap with their counterparts on the relational world, because both represent business concepts, but restricting the domain entities to the relational ones greatly caps our thinking and designing ability.

And here we come to the big idea: an Entity (or any object for that matter) can take upon many roles.

This is because roles and entities are different kinds of abstraction. Entities represent a thing/idea whereas roles represent a capability.

And often, depend on abstraction means depend on a role.

A (silly) code example

Let us review our previous code:

class Parent: IParent{
    public void Teach(){}
    public void Work(){}
}

interface IParent{
    public void Teach();
    public void Work();
}

A lot of people are OK with creating this interface before figuring out which services are going to be provided to which client. This is a leaky abstraction. It’s weak and ambiguous on its intention. Can you tell what’s the purpose of an IParent on a glance?

Let’s now review the client code. Let’s say a basic math class can be taught by a teacher, but given the COVID-19 situation it can also be taught by a parent at home:

public class BasicMathClass{
        public BasicMathClass(Teacher teacher){
             teacher.Teach();
       }

        public BasicMathClass(Parent parent){
             parent.Teach();
       }
}

public Teacher{
       public void Teach();
}

class Parent: IParent{
    public void Teach(){}
    public void Work(){}
}

interface IParent{
    public void Teach();
    public void Work();
}

When we look at the client code it’s obvious why the parent teaches. But since we extracted the interface without even checking who was using it before, we are now in a dilemma. One way to solve this could be:

public class BasicMathClass{
        public BasicMathClass(IParent parent){
             parent.Teach();
       }
}

public Teacher: IParent{
       public void Teach(){}
       public void Work(){}
}

class Parent: IParent{
    public void Teach(){}
    public void Work(){}
}

interface IParent{
    public void Teach();
    public void Work();
}

Solved. I know, this is silly, but if you think about it, all teachers also work, so it’s not so crazy to have a work method in there.
But not all of them are parents. So what then? Should we revert the interface?

public class BasicMathClass{
        public BasicMathClass(ITeacher teacher){
             teacher.Teach();
       }
}

public Teacher: ITeacher{
       public void Teach(){}
       
}

class Parent: ITeacher{
    public void Teach(){}
    public void Work(){}
}

interface ITeacher{
    public void Teach();
}

Well, this reads better right? All parents teach, so they are teachers, right? Well, that’s not necessary true either. They can teach, but not because they study to do so, and they cannot teach in a school either.

The problem is in the role conceptualization: we are talking about what something is, instead of what it does.

public class BasicMathClass{
        public BasicMathClass(IEducate educator){
             teacher.Teach();
       }
}

public Teacher: IEducate{
       public void Teach(){}
      
}

class Parent: IEducate{
    public void Teach(){}
    public void Work(){}
}

interface IEducate{
    public void Teach();
}

The change is a subtle one but is important nonetheless: instead of depending on an entity (some thing/idea) we are now depending on a role (a capability). The mental model implications are not to be taken lightly. Once you start depending on roles, you’ll start to think more in terms of them.

So here’s the tip of the day: If you want to talk about what something is, use a class. If you want to convey what it does, use an interface.