Clean way to use mutable implementation of Immutable interfaces for encapsulation
- by dsollen
My code is working on some compost relationship which creates a tree structure, class A has many children of type B, which has many children of type C etc. The lowest level class, call it bar, also points to a connected bar class. This effectively makes nearly every object in my domain inter-connected. Immutable objects would be problematic due to the expense of rebuilding almost all of my domain to make a single change to one class.
I chose to go with an interface approach. Every object has an Immutable interface which only publishes the getter methods. I have controller objects which constructs the domain objects and thus has reference to the full objects, thus capable of calling the setter methods; but only ever publishes the immutable interface. Any change requested will go through the controller. So something like this:
public interface ImmutableFoo{
public Bar getBar();
public Location getLocation();
}
public class Foo implements ImmutableFoo{
private Bar bar;
private Location location;
@Override
public Bar getBar(){
return Bar;
}
public void setBar(Bar bar){
this.bar=bar;
}
@Override
public Location getLocation(){
return Location;
}
}
public class Controller{
Private Map<Location, Foo> fooMap;
public ImmutableFoo addBar(Bar bar){
Foo foo=fooMap.get(bar.getLocation());
if(foo!=null)
foo.addBar(bar);
return foo;
}
}
I felt the basic approach seems sensible, however, when I speak to others they always seem to have trouble envisioning what I'm describing, which leaves me concerned that I may have a larger design issue then I'm aware of. Is it problematic to have domain objects so tightly coupled, or to use the quasi-mutable approach to modifying them?
Assuming that the design approach itself isn't inherently flawed the particular discussion which left me wondering about my approach had to do with the presence of business logic in the domain objects.
Currently I have my setter methods in the mutable objects do error checking and all other logic required to verify and make a change to the object. It was suggested that this should be pulled out into a service class, which applies all the business logic, to simplify my domain objects. I understand the advantage in mocking/testing and general separation of logic into two classes.
However, with a service method/object It seems I loose some of the advantage of polymorphism, I can't override a base class to add in new error checking or business logic. It seems, if my polymorphic classes were complicated enough, I would end up with a service method that has to check a dozen flags to decide what error checking and business logic applies. So, for example, if I wanted to have a childFoo which also had a size field which should be compared to bar before adding par my current approach would look something like this.
public class Foo implements ImmutableFoo{
public void addBar(Bar bar){
if(!getLocation().equals(bar.getLocation())
throw new LocationException();
this.bar=bar;
}
}
public interface ImmutableChildFoo extends ImmutableFoo{
public int getSize();
}
public ChildFoo extends Foo implements ImmutableChildFoo{
private int size;
@Override
public int getSize(){
return size;
}
@Override
public void addBar(Bar bar){
if(getSize()<bar.getSize()){
throw new LocationException();
super.addBar(bar);
}
My colleague was suggesting instead having a service object that looks something like this (over simplified, the 'service' object would likely be more complex).
public interface ImmutableFoo{
///original interface, presumably used in other methods
public Location getLocation();
public boolean isChildFoo();
}
public interface ImmutableSizedFoo implements ImmutableFoo{
public int getSize();
}
public class Foo implements ImmutableSizedFoo{
public Bar bar;
@Override
public void addBar(Bar bar){
this.bar=bar;
}
@Override
public int getSize(){
//default size if no size is known
return 0;
}
@Override
public boolean isChildFoo
return false;
}
}
public ChildFoo extends Foo{
private int size;
@Override
public int getSize(){
return size;
}
@Override
public boolean isChildFoo();
return true;
}
}
public class Controller{
Private Map<Location, Foo> fooMap;
public ImmutableSizedFoo addBar(Bar bar){
Foo foo=fooMap.get(bar.getLocation());
service.addBarToFoo(foo, bar);
returned foo;
}
public class Service{
public static void addBarToFoo(Foo foo, Bar bar){
if(foo==null)
return;
if(!foo.getLocation().equals(bar.getLocation()))
throw new LocationException();
if(foo.isChildFoo() && foo.getSize()<bar.getSize())
throw new LocationException();
foo.setBar(bar);
}
}
}
Is the recommended approach of using services and inversion of control inherently superior, or superior in certain cases, to overriding methods directly? If so is there a good way to go with the service approach while not loosing the power of polymorphism to override some of the behavior?