Decoupling from Concreteness and Abstractness

August 16, 2017 — Posted by Scott Bain

Coupling in OO is an interesting beast.  On the one hand, system’s behavior is largely created through component interactions, objects messaging each other in various ways.  To do that, you must create coupling between them (the coupling, in a sense, creates the system).  But at the same time, coupling can be the source of maintenance problems when it is excessive, hidden, or otherwise creates destructive side effects when the system is changed.

Anything that impedes maintenance also impedes scalability, extensibility, performance tuning, enhancing, debugging, basically anything we do that alters an existing system (with the intent to improve it) and can thus propagate change in an undesirable way through unpredicted/unexpected side effects.

We seek to limit and control coupling.  That’s a big topic, but I wanted to investigate one aspect of this, namely the ability to hide whether a given type reference is abstract or concrete.

Object-Oriented design consists of many different types of relationships between entities (usually objects) in a system.  But the beginning, the “atom” of OO, as it were, is when one object messages another in order to trigger a service behavior:

The moment we decide that a behavior should belong in its own class, the “oriented” part of OO begins.  Why would we decide to so this?  Perhaps the behavior (here represented by the “algorithm()” method) needs to be reusable by several objects, to avoid redundancy in their code.  Perhaps we want to test it on its own, using a very small, granular test.  Perhaps we wish to adhere to the Single Responsibility Principle to keep the design more cohesive.  At any rate, once we are motivated to pull the algorithm out of the client class and move it to its own, service class, we have moved from object-based to object-oriented.  Of course, there is much more to OO than this, but it is a fundamental aspect.

But is this good enough?  Should the client (or clients, potentially) directly couple to the service, or should we abstract that service?  Certainly, we know how to do this, but the question is, should we?

If we hide the implementing class under an abstraction… in this case let’s say an abstract class, what would the benefit be?  It would appear that we have decoupled the client (and potential future clients) from the implementing class.  If, in future, another version of this service emerges due to changed requirements, then the client(s) would be unaffected by the change.

Or would they be?  Not if we did this:

If, in other words, client objects directly build their instances of the service object, then the abstraction does not really buy us anything.  If, in future, there are multiple versions of the service and some logic needed to determine which one to use, then all the clients will have to be changed anyway, and be given the knowledge to enable them to build the right version.  That knowledge will also be held in multiple places in the system, and we’ll suffer the cost of this redundancy.

Part of this problem comes from mixing two different things in one place, namely the way an object is made and the way an object is used.  The client(s) here contain both pieces of information in that they make the service object and then use it.  What if we wanted to separate them?  One way would be to add another object to do the creation part.  This is typically called a “factory” by those who study design patterns, or as the Gang of Four termed it, a “creational” entity.

Now if we get a variation at some unknown point in the future, then only the factory will have to be maintained.  Clients to a service often grow in number, but factories almost never do.  So, we would still have maintenance work but it would be in a single place, and a place where nothing much else is going on.  This also eliminates the redundant object creation code that would otherwise be present in every client object.

But what if the Service never develops a variation?  How do we know if it will or not?  Should we put in separate factories and abstract classes for every class we ever create in a system, just automatically?  Is it worth creating a factory to encapsulate one simple line of code to create an object?  What if, over time, some critical business rules emerge regarding how the service is built?  Then we’ll be glad we have that factory to contain those rules in a single place… but how do we know if that will happen or not?

This is a quintessential design question.  How far do we take things?  How much design infrastructure is warranted, and when does it lead us to over-design and over-complicate things? 

Of course, sometimes we know.  Sometimes we have a variation right from the beginning, in the initial requirements for a system.  In calculating a tax write-off for a purchased asset, for example, I know from previous experience that the federal government and the different state governments require that this calculation be done in different ways, also depending on the kind of asset we are writing off (cars, real estate, computers, etc...).  I would put in an abstraction and a factory immediately, to hide this variation and protect my system from legislative changes.  I’ve been here before, I know this extra preparation is appropriate.

The difficulty arises when we don’t have this kind of certainty.  We should not, I submit, put in the extra infrastructure for every class we ever create.  But adding it later can be a lot of work.  I have worked with organizations that adopt the “separate interfaces everywhere” and they usually end up regretting it.  It can certainly cause a lot of controversy.  There is a simple way around this, however, and one that you’ve likely read about already if you follow this blog.  I just wanted to point out yet another thing that this simple idiom accomplishes.

In his book “Effective Java”, Joshua Bloch [1] suggested a small change to how we make simple objects, something that we can do automatically because the cost is so low, essentially nothing.

Instead of this:

public class Client {
    private Service myService;
    public Client() {
        myService = new Service();
    }
    // then, use myService in some way
}

Josh suggests we do this:

public class Client {
    private Service myService;
    public Client() {
        myService = Service.getInstance();
    }
    // then, use myService in some way
}

public class Service {
    public static Service getInstance() {
        return new Service();
    }
    // Etc...
}

This does not appear to be much of anything.  All we have done is move the simple creation code “new Service()” from the client and put it into a static method on the service class itself.  Why do that, and what does it accomplish?

First of all, if there are many clients, we have eliminated redundant code within them.  It also creates the separation we wanted in that the clients now only use, they do not also create.  But there is a subtle, additional difference that is just as important. 

In the first bit of code, it’s clear that the Client is coupled to the Service object.  That always happens when you build something, you’re coupled to its existence.  This is equally true in the second case; when you call a static method on a class, you’re coupled to the existence of the class just as much if you call the method on the class, as we are doing.  This seems like no difference at all.

But, in the first case the client is coupled to the fact that the class is concrete.  You can only use “new” on a concrete type.  In the second case this is not true, because both concrete classes and abstract classes can contain static methods.  This means that by adding this small change, we have decoupled the client(s) from the concreteness/abstractness of the service, and thus can change the design as needed without impacting the clients.

If the service never varies, what has this cost us?  A line of code and a trivial change to client code.  That’s not a lot of work.  But what if, again at some unknown and unpredictable point in the future, new requirements mean that we need different versions of the service under different circumstances?  Now we can do this:

public abstract class Service {
    public static Service getInstance() {
        switch (Config.SERVICETYPE) {
        case VERSION1:
            return new Service_V1();
        case VERSION2:
            return new Sercice_V2();
        }
    }
    // Etc...
}

Service_V1 and Service_V2 are now two different versions of the algorithm, as the new requirement dictates, and Service is now the abstraction they both derive from.  The clients were never coupled to the fact that Service was concrete (even though it was), and so now that it has become abstract they are unaffected by the change.  The static method getInstance() has now evolved into a bit of business logic (in this case using a configurator to make the decision).  The clients are decoupled from:

  • The fact that there is a variation now, when there was not before
  • What specific variations exist
  • How many variations are supported
  • The usage of the configuration object to select the right one
  • The implementation of all services

…which means any of these things can change.  If, for example, the business logic to select the correct service grows more complexity in the future, to the extent that we feel an actual, separate factory is warranted, we can “grow” one at that point, and simply delegate to it from our static methods.  The clients, in other words, will also be decoupled from the fact that we are using a factory, and the nature of that entity entirely.

There is more to say here, but I just wanted to get this conversation started.  Separating the creation of something from its use has many, many advantages and yet does not mean adding a lot of burden or complexity to your designs.

Please note that this is not sufficient for unmanaged code, because we have to deal with object destruction as well as object creation if we have no runtime garbage collector.  There are various ways of handling this, but perhaps the simplest (and my preference) is to simply add a “returnInstance(obj)” static method as well.  Whether the instance is retained/reused, or kept in a pool of resources, or the memory freed up when it is returned is something the client(s) really shouldn’t couple to, allowing that to be changed in the future as well.

 

For more information on our technical approach, please visit http://www.netobjectives.com/training/technical-agility

-

[1] Effective Java, Joshua Bloch. ISBN-10: 0321356683

 

 

 

 

 

Subscribe to our blog Net Objectives Thoughts Blog

Share this:

About the author | Scott Bain

Scott Bain is an consultant, trainer, and author who specializes in Test-Driven Development, Design Patterns, and Emergent Design.



        

Blog Authors

Al Shalloway
Business, Operations, Process, Sales, Agile Design and Patterns, Personal Development, Agile, Lean, SAFe, Kanban, Kanban Method, Scrum, Scrumban, XP
Cory Foy
Change Management, Innovation Games, Team Agility, Transitioning to Agile
Guy Beaver
Business and Strategy Development, Executive Management, Management, Operations, DevOps, Planning/Estimation, Change Management, Lean Implementation, Transitioning to Agile, Lean-Agile, Lean, SAFe, Kanban, Scrum
Israel Gat
Business and Strategy Development, DevOps, Lean Implementation, Agile, Lean, Kanban, Scrum
Jim Trott
Business and Strategy Development, Analysis and Design Methods, Change Management, Knowledge Management, Lean Implementation, Team Agility, Transitioning to Agile, Workflow, Technical Writing, Certifications, Coaching, Mentoring, Online Training, Professional Development, Agile, Lean-Agile, SAFe, Kanban
Ken Pugh
Agile Design and Patterns, Software Design, Design Patterns, C++, C#, Java, Technical Writing, TDD, ATDD, Certifications, Coaching, Mentoring, Professional Development, Agile, Lean-Agile, Lean, SAFe, Kanban, Kanban Method, Scrum, Scrumban, XP
Marc Danziger
Business and Strategy Development, Change Management, Team Agility, Online Communities, Promotional Initiatives, Sales and Marketing Collateral
Max Guernsey
Analysis and Design Methods, Planning/Estimation, Database Agility, Design Patterns, TDD, TDD Databases, ATDD, Lean-Agile, Scrum
Scott Bain
Analysis and Design Methods, Agile Design and Patterns, Software Design, Design Patterns, Technical Writing, TDD, Coaching, Mentoring, Online Training, Professional Development, Agile
Steve Thomas
Business and Strategy Development, Change Management, Lean Implementation, Team Agility, Transitioning to Agile
Tom Grant
Business and Strategy Development, Executive Management, Management, DevOps, Analyst, Analysis and Design Methods, Planning/Estimation, Innovation Games, Lean Implementation, Agile, Lean-Agile, Lean, Kanban