-1

In short, I'd like to be able to group class instances by a superclass which does not implement a certain interface. But from the set of instances I'd like to call methods from the interface on those instances that implement that interface.

Some example code which might explain it.

class Building{
  String c = "white"; 
  Building(){
  }

  void printColor(){
    println("The building is " + c);
  }

  void paint( String c ){
   this.c = c;  
  }

  void printBuildQuality(){
   println("The build quality is average"); 
  }
}


class SturdyFactoryBuilding extends Building implements Factory{

 SturdyFactoryBuilding(){
  super(); 
 }

 void printBuildQuality(){
  println("The build quality is sturdy"); 
 }

 void printFactoryOutput(){
  println("This factory makes stuff"); 
 }
}

class ShakyFactoryBuilding extends Building implements Factory{

  ShakyFactoryBuilding(){
   super(); 
  }

  void printBuildQuality(){
   println("The build quality is shaky");
  }

  void printFactoryOutput(){
   println("This factory makes slightly different stuff"); 
  }
}


public interface Factory{

  public void printFactoryOutput();

}

 Building building = new SturdyFactoryBuilding();    
 building.printBuildQuality();
 building.printColor();
 building.paint("bright red");
 building.printColor();
 building.printFactoryOutput();  

Is there a way I can achieve this, perhaps by having an 'isFactory' flag in the superclass.

Thanks.

  • 3
    You can achieve it with a simple `if(building instanceof Factory)` check. However what you're trying to do would not be considered good design. – Kayaman Jun 14 '16 at 12:25
  • I see @Kayaman, so like this: `if(building instanceof Factory){ SturdyFactoryBuilding sfb = (SturdyFactoryBuilding) building; sfb.printFactoryOutput(); } ` I can see how it wouldn't be good design since I still need to downcast to a specific subclass. Is there a better way to go about this? – Bluefarmer Jun 14 '16 at 12:42
  • You could add `printFactoryOutput` to `Building` giving it a NOOP default implementation. Factories override that default. Kind of an Adapter-Approach. – Fildor Jun 14 '16 at 12:53

2 Answers2

0

I think you'll have to make a trade-off: Either you accept some anti-pattern or you open up you Building "interface" to act as an Adapter:

class Building implements Factory{

    // the other building stuff

    @Override
    public void printFactoryOutput(){ /* NO OP */ }
}

Then you can call printFactoryOutput on all Buildings having no effect up to this point.

Since your Factory-implementations extend Building they automatically inherit the NOOP-Implementation. But since you override it:

class ShakyFactoryBuilding extends Building implements Factory{

  ShakyFactoryBuilding(){
   super(); 
  }

  @Override
  public void printBuildQuality(){
   println("The build quality is shaky");
  }

  @Override
  public void printFactoryOutput(){
   println("This factory makes slightly different stuff"); 
  }
}

... you have the desired result.

Drawback is of course that all Buildings do have the printFactoryOutput visible. But that's the trade-off I was talking about. If this is not acceptable, you'll have to completely reconsider your design.

To make it clear that a Building that is not a Factory shall not be called that Method on, you could throw an UnsupportedOperationException in Building, but that would force try/catch blocks everywhere in your code. You could as well return a boolean: default=false and returning true if in fact a factory ... There are plenty possibilities.

You could also change your design to use composition over inheritance.

Fildor
  • 14,510
  • 4
  • 35
  • 67
  • Thanks for the explanation. I'll rework the superclass to implement the interface methods. It keeps the downsides ( at least without a redesign ) to a minimum for me. When the Factory interface is implemented in the superclass it is not required for the subclasses that also implement Factory to have their own method overrides. Is there a way to force is to be so or could `implements Factory` be left out from 'factory'-subclasses? I'll accept your answer since it answers my main question. – Bluefarmer Jun 14 '16 at 14:09
  • Since Building implements Factory, you could remove that from the XXXFactory classes, yes. – Fildor Jun 14 '16 at 14:10
0

I think you're getting the message that this is a bad idea. It violates generally accepted object oriented design principles. That said, there are several ways to go about it, some less odious than others.

Just cast it

The simplest thing to do is something like this:

if (building instanceof Factory)
    ((Factory)building).printFactoryOutput();

You're checking to see whether it's a Factory, then invoking the Factory-specific method after casting. It's a straightforward (and therefore easily understood) way of implementing a bad design.

Make Building aware of Factory

This has issues in that there is currently no necessary relationship between Building and Factory, but such a relationship might help you along.

class Building {
    // ...
    Factory adaptToFactory() {
        return null;
    }
}

class SturdyFactoryBuilding ... {
    // ...
    @Override
    Factory adaptToFactory() {
        return this;
    }
}

Similarly for ShakyFactoryBuilding.

Then you could write

Factory f = building.adaptToFactory();
if (f != null)
    f.printFactoryOutput();

More general adapter

If you're doing a lot of this kind of thing, you could make it into a pattern that you apply wherever needed. (Eclipse, for example, uses adapters all over the place.)

interface Adaptable {
    <T> T adapt(Class<T> clazz);
}

class Building implements Adaptable {
    // ...
    @Override
    <T> T adapt(Class<T> clazz) {
        if (clazz.isInstance(this)) {
            return clazz.cast(this);
        }
        return null;
    }
}

Then you'd write

Factory f = building.adapt(Factory.class);
if (f != null)
    f.printFactoryOutput();

There are still more places to go with this, but this is far enough for this question, I think.

Erick G. Hagstrom
  • 4,873
  • 1
  • 24
  • 38
  • Thanks, unfortunately there's no option to redesign the system. But since the interface only has a handful of methods the way Fildor described or the 'adaptToFactory' way both seem reasonable. – Bluefarmer Jun 14 '16 at 14:37