0

I have some class inheritance SubClass < MidClass < SuperClass and want to perform some TASK upward for all these classes. TASK is quite complex with only minor changes in the 3 classes, which I moved into the private methods m2().

My current solution is very boiler plate:

class SuperClass {
  protected void m1() {
    //TASK (calls m2())
  }

  private void m2() {
    //...
  }
}

class MidClass extends SuperClass {
  protected void m1() {
    //same TASK (calls m2())
    super.m1();
  }

  private void m2() {
    //...
  }
}

class SubClass extends MidClass {
  protected void m1() {
    //same TASK (calls m2())
    super.m1();
  }

  private void m2() {
    //...
  }
}

Can I exploit some code reuse mechanism instead of copying TASK?

Something like the following, with m1() only in SuperClass, does not work:

class SuperClass {
  protected final void m1() {
    //TASK (calls m2())
    if (!(this.getClass().equals(SuperClass.class))) {
      super.m1();
  }
}

because super.m1() does not refer to execution of the same inherited method in the context of a super class, but to the overridden method implementation. Since m1() does not exist in Object, I additionally get a compiler error...

Putting TASK in a protected final helper() method in SuperClass and calling helper() instead of copying TASK won't work, since then always SuperClass.m2() gets called.

The only alternative I can think of is slow, complicated and unsafe: using a type token as parameter, i.e. protected final void m1(Class<? extends SuperClass> clazz) in SuperClass, and fulfilling TASK via reflection (requires to make m2() public static or use setAccessible(true) on m2()).

Do you know some better solution? AOP? Maybe some framework where you can inject a method into classes (as in C#)? Or am I missing something???

DaveFar
  • 7,078
  • 4
  • 50
  • 90
  • Could you elaborate a bit on how the tasks would differ? I suspect there's more a design issue that a code problem. – Thomas Aug 04 '11 at 14:41
  • The task differs in which values are compared: those fields that are added by that subclass. In detail: I have implemented a mixed-type equal() with default value constraints instead of ignoring the subclass value field. For details, see http://www.angelikalanger.com/Articles/JavaSolutions/SecretsOfEquals/Equals-2.html. In Angelika's terminology, m1() would be _navigateClassHierarchy(), m2() would be _compareFields(). – DaveFar Aug 04 '11 at 18:45
  • 1
    If I understand you correctly, `_compareFields()` would just also compare the additional fields in the subclass. Thus the `super.m2()` approach I proposed should be fine for this (something like `if( super._compareFields() ) { /*compare additional fields*/}`). – Thomas Aug 05 '11 at 07:40
  • Yes, Thomas, I could do that, thanks. It would simplify the code strongly: For o1.equals(o2), I could simply call this._compareFields(o2) and o2._compareFields(this), without the need for _navigateClassHierarchy(). See my own answer on how to avoid that some checks are performed twice, which was the reason to implement _navigateClassHierarchy() in the 1st place. Getting a general answer on how to solve my question with Java, not with redesigns, would still be interesting: for later similar situation, and solving this problem with Java would also give more insights into the language... – DaveFar Aug 05 '11 at 17:44

3 Answers3

4

How about this?

class SuperClass {
  protected void m1() {
    //TASK (calls m2())
  }

  protected void m2() {
    //...
  }
}

class MidClass extends SuperClass {

  protected void m2() {
    //...
  }
}

class SubClass extends MidClass {
  protected void m2() {
    //...
  }
}

The m1 method is inherited, and will always call the m2 method. Since m2 is protected, it's called polymorphically. So, if invoked on a SubClass instance, SubClass.m2() will be called.

JB Nizet
  • 678,734
  • 91
  • 1,224
  • 1,255
  • Thanks for the quick reply :) Since I do not want to perform TASK once, but chaining upward the inheritance hierarchy, I would need something like `if (!(this.getClass().equals(SuperClass.class))) { super.m1(); }`, which leads to the first alternative I tried (see my second, smaller code example). – DaveFar Aug 04 '11 at 10:29
  • @user750378 why don't you simply call `super.m2()` inside `protected void m2()`? That would handle the chaining and you could define the order yourself (call the super implementation first, in between or last) – Thomas Aug 04 '11 at 11:10
  • @Thomas: I want to chain m1(), with the slight differences in TASK swapped out to the methods m2(). If I make m2() protected, dynamic dispatch will always pick this.m2() when I make the chain upwards in m1() - hence private m2(). – DaveFar Aug 04 '11 at 12:08
0

Expanding on my comment to JB's answer:

class SuperClass {
 protected void m1() {
   m2();
 }

 protected void m2() {
   System.out.println("start super task");
   System.out.println("end super task");
 }
}

class MidClass extends SuperClass {
  protected void m2() {
   super.m2();
   System.out.println("start mid task");
   System.out.println("end mid task");
  }
}

class SubClass extends MidClass {
 protected void m2() {
  System.out.println("start sub task");
  super.m2();
  System.out.println("end sub task");
 }
}

new SubClass().m1() yields this result:

start sub task
start super task
end super task
start mid task
end mid task
end sub task

Note that all 3 versions of m2() are executed in the defined order: sub is started, then execution continues with super and mid and finished with sub again.

Thomas
  • 87,414
  • 12
  • 119
  • 157
  • Thanks. This kind of chaining is exactly what I'm doing (with m1() in my longer code sample). If you now not only want to perform some println(), but a quite complex TASK with only minor changes in the 3 classes, how would you do that the most effectively? – DaveFar Aug 04 '11 at 12:00
  • @user750378 You might break each complex task into smaller methods that could selectively be overridden (e.g. for each portion that might be changed individually). – Thomas Aug 04 '11 at 12:15
  • Could you give a code example for that? What do you mean by selectively be overridden? Selectively for each class, so that different versions will be executed when chaining up the class hierarchy? I do not know how that could be done! (For clarification on dynamic dispatch in these situations, see for instance http://stackoverflow.com/questions/4595512/java-calling-a-super-method-which-calls-an-overridden-method). – DaveFar Aug 04 '11 at 14:03
  • @user750378 well, you might split m2() into several protected methods that can be overridden individually. However, if `MidClass` would override such a method, but if `SubClass` would require the orignal version provided by `SuperClass` you might have a design problem. In that case you might use a composite pattern, i.e. create "Function"-Objects (functors) for each part and compose each task representation from those functors. – Thomas Aug 04 '11 at 14:39
  • Thank you for your effort, Thomas. Unfortunately, I still do not see how I can have the code-reuse and avoid dynamic dispatch: If the method that is chained upward the class hierarchy calls another method that is protected, or calls functors, dynamic dispatch will always pick the same one, i.e. the one that this points to will be called 3 times... – DaveFar Aug 04 '11 at 16:25
  • @user750378 Yes, if you override a protected method in a subclass you'd get this always called - which would be good in terms of consistency. If the overridden and the overriding method should both be called they probably should better be completely separate methods. As for the functors: you could put several functors of the same type, i.e. multiple comparators for one field, or just use a special one. In the functor case you might not have a class hierarchy but the objects differ in their setup - the functors they contain. – Thomas Aug 05 '11 at 07:45
0

Solution for my concrete example of mixed-type equals() with default value constraints instead of ignoring the subclass value fields. Instead of Angelika Langer's solution (see http://www.angelikalanger.com/Articles/JavaSolutions/SecretsOfEquals/Equals-2.html) with private methods _compareFields() and a protected method _navigateClassHierarchy() that has to be copied into each subclass, only a protected method compareOwnFields() is used, which has to be overridden correctly in each subclass.

class SuperClass {
    // ...

    @Override
    public final boolean equals(final Object other) {
        if (other == this) { return true; }
        if (!(other instanceof SuperClass)) {
            return false;
        }
        final SuperClass otherSuperClass = (SuperClass) other;

        return compareOwnFields(otherSuperClass, false)  
        && otherSuperClass.compareOwnFields(this, true);
    }

    protected boolean compareOwnFields(final SuperClass other, 
        final boolean firstTraversal) {
        if (!firstTraversal) {
            return true;
        }
        if (field1 != other.getField1()) {
           return false;
        } 
        // compare other fields similarly ...
        return true;
    }

}    

class SubClass {
    // ...

    @Override
    protected boolean compareOwnFields(final SuperClass other, 
        final boolean firstTraversal) {
        if (other instanceof SubClass && !firstTraversal) {
            return true;
        if (other instanceof SubClass) {
            if (field1 != ((SubClass) other).getField1()) {
                return false;
            }
            // compare other fields similarly ...
            return super.compareOwnFields(other, firstTraversal);
        } else {
            if (field1 != DEFAULT_FIELD1) {
                return false;
            }
            // check other fields for default values similarly ..
            return super.compareOwnFields(other, firstTraversal);
        }
    }
}

But this does not answer my question in general, it's rather a redesign that avoids the problem. So further answers on how to solve the problem with the Java language features are very welcome!

DaveFar
  • 7,078
  • 4
  • 50
  • 90
  • Just a question: why are you comparing the fields twice - I mean it's like 'this.equals(other) && other.equals(this)`? Note that the contract for `equals()` states that if `this.getClass() != other.getClass()` the objects are not equal. Thus you could do something like `if( super.compareOwnFields((SuperClass)other) ) { compareOwnFields((SubClass)other); }` – Thomas Aug 05 '11 at 18:54
  • If you are interested in equals(), I strongly recommend reading Angelikas articles about it. Just in short: this.getClass() != other.getClass() is not part of equal's contract. Many do it, but it is not conform to Liskov's substitution principle (see e.g. Effective Java item 8). To allow mixed-type equality, you do not have that constraint. I am calling o1.compareOwnFields(o2) and o2.compareOwnFields(o1) to cover the complete class hierarchy: E.g. SuperClass>SubA1>SubA2, SuperClass>SubB1>SubB2, o1 instanceof SubA2, o2 instanceof SubB2. By doing both calls, I compare all relevant fields. – DaveFar Aug 05 '11 at 21:00