1

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?

  • 1
    If caller A has a Foo variable referring to a Foo object, and caller B has an ImmutableFoo variable referring to that same Foo object, what's stopping caller A from mutating the object that should, from caller B's point of view, be immutable? – BenM May 28 '14 at 1:12
  • Nothing, however, the mutable fields will be protected by locking the same as if they were immutable, and in truth only one field is immutable, most of the other objects are truly immutable, they simply point to a child object(s) which can be mutated which would have caused lots of re-building of objects if that child was immutable. – dsollen May 28 '14 at 12:55
  • Immutable objects would be problematic due to the expense of rebuilding almost all of my domain to make a single change to one class. This is probably not true. Look into persistent data structures. Additionally, implementation inheritance will probably bring you pain in the long run. – Doval May 28 '14 at 18:02
  • 1
    Did you mean composite, or is there a meaning of compost besides the "the stuff used to make plants grow, usually derived from animal droppings" :) – Ciro Santilli 新疆改造中心996ICU六四事件 Mar 13 '15 at 15:14
4

I think that calling those interfaces Immutable is source of confusion. They are not Immutable, they are read only view of possibly muttable objects. This alone can lead to your design to be misunderstood. Call them ReadOnlySomething or ReadableSomething (this convention allows for WritableSomething and ReadWriteableSomething (which is ugly long, so maybe not so gud idea)), or SomethingView (or anything else that conveys meaning that these things are not necessarily immutable, but does not allow mutation thourg them).

About inheritance: use it if there actually is 'IS A' rellationship and caller does not need to know actual type to use it meaningfully. If caller needs to inspect it, use composition.

  • Yup. Immutability is stronger than saying "a particular caller can't mutate an object". It's a guarantee to all callers that the object won't be mutated by anything. There is certainly a place for read-only views of mutable objects in well-designed systems, but they shouldn't be called "immutable". Otherwise callers might make incorrect assumptions based upon the immutability. – BenM May 28 '14 at 21:15
  • I would suggest IImmutableFoo:IReadOnlyFoo:IReadableFoo. Code which receives an IReadableFoo but is forbidden from exposing it to code that might mutate it would be required to wrap it before exposing it to the outside world unless it implements IReadOnlyFoo, in which case it could be exposed directly. Such conditional wrapping may be facilitated by having IReadableFoo include AsReadOnly and AsImmutable methods. – supercat Jul 21 '14 at 21:44

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.