简体   繁体   中英

In Java: Code reuse possible for a chain of method calls up an inheritance hierarchy?

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, ie 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???

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.

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.

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!

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM