Manwich

Posted on April 2, 2010

0


CC-by-SA

One common question we see with students (and professionals) is when interfaces and classes begin to get… chunky. That is, they begin to have an explosion of methods and attributes as developers begin to add to them everything they think "fits" there.

I have a Window interface, and this method does this something to this Window thingie machigie, so it goes there!

Then a interface (or class) begins to resemble one of those dreaded Utils artifacts rather than an artifact with well-defined behavior and structure. Pour some more in it and eventually resembles a manwich.

manwich

What all manly men should eat at least once in their lifetimes

So it begs the question: where should you stop? Let us consider the root causes of this problem and its possible solutions.

A Twisted Genesis

With the right amount of experience and CS education, it is easy to eye-ball the offending artifacts before they develop into gigantic malignant tumors. It is not that obvious, however, for the student or young graduate who didn’t learn (or didn’t pay attention to) specifics of good class design and modularity, or for the professional who has only worked around code monkeys who couldn’t tell you the difference between encapsulation and an episode of the Real Housewives of whatever and who could not code something right even if Knuth himself were to sit in their laps dictating what to code in twisted pair programming fashion.

For the sake of simplicity, let us refer to artifacts to describe any artifact that might experience bloat: classes, mixins, interfaces, procedures, modules, you name it.

The first problem is that in languages such as Java, there is no concept of a namespace different from an interface or package. So there is no discernible place to collect artifacts that seem to be related. Interfaces, packages and classes become bags of stuff.

Caveat: this problem is not easily solved when defining interfaces to remote components or services. Their implementation might be modularized, but the interfaces might not, either for simplicity or performance reasons. With distributed systems, locality no longer applies, and it is important to take something being potentially remote into consideration when designing (or re-factoring) an artifact.

Second, people tend to forget that a given interface or class, or any type of artifact for that matter, should have a well-defined structure and behavior.

Lastly, there is no specific limit on the number of artifacts (methods, constants, enumerations, etc) – say N – in an interface or class such that we can say if interface X has more than N artifacts, it is bloated. At least not without a context.

It’s All About Context

In this case, the context is what is the interface supposed to provide?, or better yet, what are implementations of this interface supposed to do? *What is the intended behavior or role of classes implementing the interface?* This is the question that you should ask yourself every time you create or extend an artifact.

When you face that type of questions, I would strongly suggest you get familiar with certain metrics like cohesion and coupling (both in general and in specifics to OO.) In particular, I’d suggest you take a look at LCOM. Once you understand it, it will help you eyeball situations like the one you are encountering now.

http://javaboutique.internet.com/tutorials/coupcoh/

One of the last things you want to do with an interface or class (or even package or module if you were doing procedural programming) is to turn them into bags of methods and functions where you throw everything but the kitchen sink. That leads to either poorly cohesion or tight coupling (or both.)

Refactorpalooza

One of the problems with interfaces is that we cannot easily compute or estimate their LCOM as one would with actual classes, which could guide you in deciding when to re-factor. So for that you have to use a bit of intuition.

Let’s assume your interface is named A for the sake of argument. Then,

Step 1: Consider grouping the interface methods by arguments: is there a subset of methods that operate on the same type of arguments? If so, are they significantly different from other method groups?

interface A
{
void method1();
void method2(someArgType x);
someOtherType y method3();

...

void doSomethingOn( someType t );
boolean isUnderSomeCondition( someType t )
someType replaceAndGetPrev( someType t, someFields ... )
}

In such a case, consider splitting that group into its own interface, B.

Step 2:

Once you extract interface B, does it look like this?

interface B
{
void doSomethingOn( someType t );
...
boolean isUnderSomeCondition( someType t )
...
someType replaceAndGetPrev( someType t, someFields ... )
}

That is, it represents methods that do things on some type?

If so, your interface is mimicking a procedural module operation on an ADT (in this case, someType) – nothing wrong with if you are using a procedural or multi-paradigm language.

Within reason and while being pragmatic, in OO, you minimize procedures that do things on other objects. You call methods in those objects to do things to themselves on your behalf. Or more precisely, you signal them to do something internally.

In such a case, consider turning B into a class encapsulating the type (and, have it extend an interface with the same signature, but only if it makes sense, if you expect different implementations of artifacts encapsulating/managing elements of that type.)

class Bclass
{
someType t;

Bclass(){ t=new someType();}
...
void doSomethingOn();
...
boolean isUnderSomeCondition()
...
someType replaceAndGetPrev( someFields ... )
}

Step 3: Determine the relationships between the interfaces and classes re-factored out from A.

If B represent things that can only exist when A does (A is a context for B, for example a servlet request exists in a servlet context in jee lingo), then have B define a method that returns A (for example A B.getContext() or something like that.)

If B represent things that are managed by A (A being a composite of things, including B), then have A define a method that returns B (B A.getBThingie())

If there is no such relationship between A and B, and they have nothing in common other than they were grouped together, then chances are that the original interface was poorly cohesive.

If you cannot disentangle one from the other without breaking a significant amount of your design, then that’s a sign that pieces of your system had poor boundaries and are tightly coupled.

Hope it helps.

ps. Also, I would also avoid trying to fit your interfaces and classes into traditional patterns UNLESS doing so serves an application/business specific purpose. I gotta throw that in there just in case. Too many people run amok with the GoF book trying to fit their classes into patterns rather than asking ‘what problem am I solving with this?’

CC-by-SA

Advertisements