1

Assume:

  1. There is some module whose interface is IA.
  2. There is some module B, that takes in a parameter an instance of IA, and whose behavior depends upon the type of that IA,

meaning (pseudo code, no specific language, [even though I use Python])

class B{
    IA ia;
    B(IA ia){
        this.ia = ia;
    }
    doStuff(){
        if(type(this.ia)==A1){
             print("A1");
        }
        if(type(this.ia)==A2){
             print("A2");
        }
    }
}

I realize I could add some public method foo to ia, and thus the code would simplify to

class B{
    IA ia;
    B(IA ia){
        this.ia = ia;
    }
    doStuff(){
        this.ia.foo();
    }
}

My question is twofold:

  1. What is the correct design to achieve this if I (for some reason) can't change IA, meaning, I can't add foo()?

  2. What is the correct (scalable) design if I am allowed to change IA but the same problem now repeats for A1, A2, and so on, meaning

the final desired behavior is

class B{
    IA ia;
    B(IA ia){
        this.ia = ia;
    }
    doStuff(){
        if(type(this.ia)==A1){
             if(type(this.ia.iz)==Z1){
                 print("A1Z1");
                 print("unique treatment");
             }
             if(type(this.ia.iz)==Z2){
                 print("A1Z2");
                 print("special treatment");
             }
        }
        if(type(this.ia)==A2){
             if(type(this.ia.iz)==Z1){
                 print("Z1A2");
                 print("one of a kind treatment");
             }
             if(type(this.ia.iz)==Z2){
                 print("Z2A2");
                 print("Wow treatment");
             }
        }
    }
}

and can repeat more times.

Please notice Z1 and Z1 are the same for A1 and A2!. And again, the same can go on, IZ can contain IX of several types, with unique behaviors

I wonder if case 2 is at all separate of modular, in the sense that, the behavior is unique for every type-combination, and no behavior can really be extracted to a more abstract level.

I still don't like the type checking, and wonder if there is something that can be done which looks better.

Gulzar
  • 23,452
  • 27
  • 113
  • 201

2 Answers2

1

You may want to take a look at the Visitor pattern (https://en.wikipedia.org/wiki/Visitor_pattern). This does assume that you can change IA and friends to implement the accept methods

David Soroko
  • 8,521
  • 2
  • 39
  • 51
0

I think what you're looking for is Tagged Union Types. Consider also Do union types actually exist in python?

Some examples might be Kotlin's Sealed Classes and When Statement. Or Scala's Case Classes and Sealed Traits.

If you're using Java, it's going to be a little wordy to set up using Paguro.

interface Wheeled { }

class Beetle implements Wheeled { }

class Truck implements Wheeled { }

// Makes a union class.
static class Beetle_Truck extends OneOf2<Beetle,Truck> {
    private Beetle_Truck(Object o, int n) {
        super(o, Beetle.class, Truck.class, n);
    }

    static Beetle_Truck ofBeetle(Beetle b) {
        return new Beetle_Truck(b, 0);
    }
    static Beetle_Truck ofTruck(Truck t) {
        return new Beetle_Truck(t, 1);
    }
}

// Here's where we use it.
@Test public void testBeetleTruck() {
    Beetle_Truck bt1 = Beetle_Truck.ofBeetle(new Beetle());
    Beetle_Truck bt2 = Beetle_Truck.ofTruck(new Truck());

    assertEquals("b", bt1.match(beetle -> "b",
                                truck -> "t"));

    assertEquals("t", bt2.match(beetle -> "b",
                                truck -> "t"));
}

Note that Beetle and Truck do not need to implement a common interface at all. For your second question, you could just do a OneOf4 similar to OneOf2.

GlenPeterson
  • 4,866
  • 5
  • 41
  • 49
  • Thanks! Actually, I am using Python mostly. I was looking for an answer that doesn't rely on a specific language, but rather on OOP principles. About the solution using union classes - It seems to me this just moves the same problem to another place. Why is this scalable? Meaning - What if I now add another `A3` and another `Z3`? Worse - What if now the tree is 5 levels deep? I would then have `Beetle_Truck_Player_Glass_Notebook extends OneOf32`. Did I get it wrong? – Gulzar Feb 23 '19 at 23:13
  • 1
    If I understand you correctly, you just want to do something different depending on the type of the object. And you don't want to do `if (myObj is TypeA) then ... else if (myObj is TypeB) then...` The point of the match statement is that it's ensured at compile time to cover all the possible types. Python doesn't have this built in, but some other languages do. I think "Tagged Union Types" is your answer though. – GlenPeterson Feb 23 '19 at 23:23