2

I got an interface

public interface Details {
   // nothing needed until now
}

which is used in a class like the following:

public class Value {
    // many fields
    private Details details;

    public Value(SomeType type) {
        switch (type) {
        case TYPE_1:
        case TYPE_2:
            this.details = new DetailsA();
            break;
        case TYPE_3:
            this.details = new DetailsB();
            break;
        default:
            throw new NotImplementedException("not yet implemented");
        }
    }

    public Details getDetails() {
        return this.details;
    }
}

The interface has two implementations

public class DetailsA implements Details {

    private BigDecimal betragA;

    public DetailsA() {
    }

    public BigDecimal getBetragA() {
      return this.betragA;
    }

    public void setBetragA(BigDecimal betragA) {
      this.betragA = betragA;
   }

}

public class DeailsB implements Details {

    private BigDecimal betragB;
    private boolean booleanB;

    public BetragB() {
    }

    public BigDecimal getBetragB() {
        return this.betragB;
    }

    public void setBetragB(BigDecimal betragB) {
        this.betragB = betragB;
    }

    public boolean isBooleanB() {
        return this.booleanB;
    }

    public void setBooleanB(boolean booleanB) {
        this.booleanB = booleanB;
    }

    // some more fields

}

I got a model class in which I want to use those details, depending on the instance.

 public class Model extends AbstractModel {

    private Details details;

    public void init(StoerungValue stoerung) {
        setDetails(stoerung.getSchaden().getDetails());
    }

    private void setDetails(Details details) {
        this.details = details;
    }
    // ...

In there I have some operations like the following

    // ...  
    public void setBooleanB(boolean booleanB) {
        if (details instanceof DetailB) {
            ((DetailB) details).setBooleanB(booleanB);
        }
    }
    // ...

How can I avoid this casting and instanceOf stuff? Is any of the design patterns applicable here?

Chris311
  • 3,794
  • 9
  • 46
  • 80
  • 2
    have you considered adding the isBoolean and setBoolean functions to the interface and adding them to both classes, where the implementation for class A does nothing? – xception Sep 14 '15 at 13:41
  • 3
    You may have an abstraction problem here. If your operation requires to know what is the implementation of Details, it should rather work directly with the supported implementation. Else, each time you'll add a new implementation, you'll have to update your operation code, which goes against the abstraction model. Alternatively, your operation could deal only with what's common to all Details implementations, and delegate the implementation specific processing to a dedicated method in the interface. – Nicolas Riousset Sep 14 '15 at 14:33
  • 1
    Interfaces with no methods are a design smell. The entire purpose of an interface is to define an interaction protocol that an object must adhere to. If that protocol contains no methods, then it provides nothing over just using `Object`. Either all of your `Details` classes have a common interaction pattern, or they don't. If they don't they're not really related, and their types should reflect that. If they do, then the interface should define that relationship. – Ian McLaird Sep 14 '15 at 14:43
  • I edited my initial post and now you can see why I use the interface. The class 'Value' has the details encapsulated. I don't want the value to have all the different Detail-types (like getDetailsA(), getDetailsB(), ...). – Chris311 Sep 14 '15 at 15:10
  • "polymorphism only makes sense when the polymorphic behavior is really a behavior of the target. When it's the behavior of the observer, you need runtime typing." https://sites.google.com/site/steveyegge2/when-polymorphism-fails – b4da Sep 15 '15 at 07:12
  • So, what would you suggest to do in this situation? – Chris311 Sep 15 '15 at 12:41
  • possible duplicate of [Alternative to instanceof?](http://stackoverflow.com/questions/5721622/alternative-to-instanceof) – PsiX Sep 15 '15 at 22:42
  • What do you think of the solution putting all methods of DetailsB and DetailsA into the interface? The methods that are not needed in one of the implementations throws an UnsupportedOperationException. – Chris311 Sep 16 '15 at 06:55
  • That depends on if you think getting an `UnsupportedOperationException` is better than getting a `ClassCastException`. I would argue that it is not better, and the dilemma here is that your `Model` class is actually dependent on a specific implementation of `Details` rather than the interface. `Model`'s implementation *needs* a `Details` that understands (and properly answers) `setBooleanB`. – Ian McLaird Sep 16 '15 at 17:40
  • @IanMcLaird interfaces with no methods are known as _marker interfaces_ and are advocated by Effective Java Item 37. For an example, see [Serializable](http://docs.oracle.com/javase/8/docs/api/java/io/Serializable.html). – jaco0646 Sep 16 '15 at 18:47
  • Not all methodless interfaces are automatically markers (this particular case appears not to be a proper marker, since the asker is trying to access data through them), and marker interfaces could be considered an outdated technique, better expressed as metadata (annotations). See also http://stackoverflow.com/questions/7552677/are-empty-interfaces-code-smell – Ian McLaird Sep 16 '15 at 18:57
  • @IanMcLaird, agreed. This particular question is about poor design. I simply wanted to provide a counterexample to the statement about, _"The entire purpose of an interface..."_. Effective Java specifically lists advantages of marker interfaces over annotations. – jaco0646 Sep 16 '15 at 21:47

1 Answers1

4

I think the problem you have here is a collection of design smells. You've painted yourself into a corner, and there may not be an easy way out. I don't know if this solution will work for you or not, but you can at least consider this.

The first design smell is that you have created an inheritance relationship where none actually exists. In short, the hierarchy rooted at Details violates the Liskov Substitution Principle. When a class claims (as Model does) to support the Details interface, it's making the claim that any implementation of Details will do. The program's correctness and behavior shouldn't change whether it's given a DetailsA, a DetailsB, or some FooDetails class that hasn't even been invented yet.

The reality is that DetailsA and DetailsB are not actually related. You can see this because Details has no methods, and thus may as well be Object which any two classes already inherit from.

The second design smell is "Feature Envy." It seems that many methods of Model are just pass-through calls to its underlying details property. You could consider, rather than having setBooleanB on Model to just provide a getDetails method, and then let the caller work directly on the Details object. This won't remove the instanceof checks or casting, but it will move them out of this class.

The third thing here is related two the first two. Model depends not on Details as its property types would tell you, but rather on (at least) DetailsB. If that's the case, then its property type should say so. Now, it's possible that sometimes you need a Model with a DetailsA and sometimes you need a Model with a DetailsB, but it can't be both at the same time. In that case, you can work around the issue with generics.

First, make the Model class generic, with a type parameter that tells what its underlying Details must actually be.

public abstract class Model<T extends Details> {
    private T details;

    public void init(T dets) {
        setDetails(dets);
    }

    public void setDetails(T dets) {
        this.details = dets;
    }

    public T getDetails() {
        return this.details;
    }
}

Then, create two subclasses that are bound to different Details types, and can thus promise to do the right thing without requiring casting or instanceof calls.

public class ModelA extends Model<DetailsA> {
    public BigDecimal getBetragA() {
        return this.getDetails().getBetragA();
    }
}

public class ModelB extends Model<DetailsB> {
    public boolean getBooleanB() {
        return this.getDetails().isBooleanB();
    }

    public void setBooleanB(boolean boolB) {
        this.getDetails().setBooleanB(boolB);
    }
}

I'm not sure if that'll fix your problem or not, but it's something to consider.

Ian McLaird
  • 5,507
  • 2
  • 22
  • 31