Class Design – Encapsulation vs Single Responsibility/Separation of Concerns

classdesignencapsulationseparation-of-concernssingle-responsibility

I'm working on a class that represents an object with multiple representations – one is an XML type representation used by an automatic ordering system, the other is a POJO-based representation used by a monitoring tool.

The problem I'm running into is that I seem to have to make trade-off between encapsulating the object's internal data and designing my application according to the principle of "separation of concerns" or "single responsibily per class".

Let's look at the class in more detail:

public class MyObject {
    private Object field1;
    private Object field2;
    private Object field3;
    ...
    private Object field100;
}

(Yes it's quite a big ugly monster, something I have no control over).

If I follow the principle of separation of concerns (the way I understand it anyway), I will have to add getters for every field in this class and have a separate XMLBuilder class for the XML representation, and a PojoBuilder for the Monitoring tool representation:

public class XMLBuilder {

    public XML asXml(MyObject obj) {
        return "<tag1>" + object.getField1() + "</tag1>"
               "<tag2>" + object.getField2() + "</tag2>"
               ...
               ;
        }
}

This seems to me to break the principle of encapsulation – I have to expose a lot of the internal data on my class just to provide a representation for it. If I pass my object to other systems, I have no control whether these sub-systems may become dependent on arbitrary fields (i.e. field45), which means that future changes to the structure of this object may become difficult as there are now other possible dependencies on it.

An alternative approach, advocated by Allen Holub (http://www.javaworld.com/article/2073723/core-java/why-getter-and-setter-methods-are-evil.html) for example, is to do the representation inside the class:

public class MyObject {

    ...

    public XML asXml() {
        return "<tag1>" + field1 + "</tag1>"
               "<tag2>" + field2 + "</tag2>"
               ...
               ;
    }

    public Object asMonitor() {
        ...
    }

 }

To me, this solution seems great, as the class never exposes any private fields. The representation generators have access to all the information it needs, and the object itself can be safely passed to other sub-systems without the worry that another module would depend on internals.

Now, when I implement this, my colleagues all raise the issue of "Separation of concerns". The problem, they say, is that the MyObject class now have knowledge of things like XML representations and POJO represantions, which means that if the XML representation changes, you now have to make modifications inside the actual class itself, instead of just updating an "XML ruleset" or some other external configuration.

Holub's solution is to use a Builder pattern:

public class MyObject {

...

    public void buildContent(MyObjectBuilder builder) {
        builder.setField1(field1);
        builder.setField2(field2);
    }
 }

 public class XMLBuilder implements MyObjectBuilder {

     ...

     public void setField1(Object field1) {
         content += "<tag1>" + field1 + "</tag1>";
     }

     public XML asXML() {
         return content;
     }

     ...
 }

but this seems hardly better than the first approach – instead of exposing data as getters, it is now implicitly exposed via a builder. OK, I understand that with the builder I may have additional control over how my object represents itself, but is that iota of control worth the increased complexity in the code base? Changes to the structure of MyObject will still likely result in downstream dependencies requiring updates, so I'm not entirely sure if much have been gained here (except that there are technically no getters).

Is this a case where you simply have to choose your poison? Or is there a magical middle ground that reduces all these issues? What are the common strategies for dealing with this?

Best Answer

Is this a case where you simply have to choose your poison?

Yes, basically.

One of software developer's job is risk management when it comes to change. And there is lots of theory about risk management. Generally, you should first identify risks. You already did that : change of object itself, change of XML, change of POJO, etc.. All of those are possible risks. Next step would be to identify what are probabilities of the occurring. You can get some idea from looking back at changes you had to do in the past, but only real experience can help you here. Then, you have to identify impact of each risk. You say that changes can propagate through the dependencies, so the more dependencies the risk affects, the more costly it is when it occurs. Taking all of this in, you should make a design that minimizes risks that have high probability and high cost.

I can't tell you right now, because the way you described your problem is way too generic. And different systems would encounter different risks at different probabilities and costs. For example, if XML is used as communication between your systems, then updating it's schema might not be a problem. But if it is used for persistence, then maintaining backwards compatibility is a must. Etc..

Only thing I can suggest is to get more experience. More experience will give you more ideas about possible risks and their properties and ways to design to avoid those risks.

Related Topic