简体   繁体   中英

How do I force any subclasses of my class to always call a parent's implementation method they are overriding?

Let's say I have a class, which implements a method (addThings()). It serves as a foundation of a tree of subclasses:

ParentClass {
    protected void addThings() {
        map.add(thing1);
        map.add(thing2);
    }
}

Now, let's say we implement a child class (which has Thing 3 as well) and Thing 3 also needs to be added on top of Thing 1 and Thing 2 .

The obvious Java solution seems to be to have the child's class's implementation of the method call the super's method:

ChildClass extends ParentClass {
    protected void addThings() {
        super.addThings();
        map.add(thing3);
    }
}

The problem is that whoever implements the subclass may very well forget to do that, and have a bug :

ChildClassBad extends ParentClass {
    protected void addThings() {
        // BUG!!! Forgot to call super.addThings(); !!!
        map.add(thing3); 
    }
}

Is there a way in Java to force any of the extending child (and grandchild) classes to always call a parent's method if they override it? (similar to how making a method abstract always forces them to implement it).

  • Please note that I need the solution to be propagatable down the inheritance tree.

    In other words, if someone implements GrandChildClass which needs to add Thing4, they would suffer from the same bug possibility relative to ChildClass.

    This means that the simple fix (applicable when you only have 1 level of inheritance) of having separate "addParentThings()" in ParentClass and then calling both addParentThings() and child-overridable empty addThings() is insufficient ( because the grandchild has to override non-empty addThings ).

Maybe try having a final method that calls another overridable method?

class ParentClass {

    public final void doStuff() {
        // Do stuff
        onPostDoStuff();
    }

    protected void onPostDoStuff() {
        // Override this!
    }
}

And then in the child class:

class ChildClass extends ParentClass {

    @Override
    protected void onPostDoStuff() {
        // Do extra stuff
    }
}

You could even make the onPostDoStuff() method abstract, so children have to override it.

If you are willing to make your doStuff -Methods static for each class, which extends your ParentClass and give your ParentClass a final public void doAllStuff() -Method, you can solve the problem with Reflection:

import java.lang.reflect.Method;

import java.util.ArrayList;
import java.util.List;

public class Main
{
    public static void main(String[] args) throws InterruptedException
    {
        A a = new C();
        a.doAllStuff();
    }
}

class A
{
    protected List<String> list = new ArrayList<String>();

    @SuppressWarnings("unused")
    private static void doStuff(A a)
    {
        a.list.add("I am A");
    }

    final public void doAllStuff()
    {
        List<Class<?>> list = new ArrayList<Class<?>>();
        Class<?> clazz = this.getClass();
        while (A.class.getSuperclass() != clazz)
        {
            list.add(clazz);
            clazz = clazz.getSuperclass();
        }
        System.out.println(list);
        for (Class<?> myClass : list)
        {
            try
            {
                Method method = myClass.getDeclaredMethod("doStuff"
                                                          , myClass);
                // Method is private? Make it accessible anyway.
                method.setAccessible(true);
                method.invoke(this, this);
            }
            catch (NoSuchMethodException e)
            {
                // Method not found, continue with next class.
                continue;
            }
            catch (Exception e)
            {
                e.printStackTrace();
            }
        }
        System.out.println(this.list);
    }
}

class B extends A
{
    @SuppressWarnings("unused")
    private static void doStuff(B b)
    {
        b.list.add("I am B");
    }
}

class C extends B {}

If you need to only call attributes, you can use getDeclaredField , the fields may not be static in this case.

The following approach enforces invocation of the superclass' setup method. The downside, or likely bug in subclasses, is that implementers might forget to provide a suitable constructor for providing an extension to the setup method. This means that child couldn't be extended by a grandchild.

Its also ugly because subclasses can't put subclass-specific setup in their extensions; they have to accept Parent as a parameter.

Overall, the awkwardness difficulties here suggest that enforcement is better done, if at all, in a static analyzer, rather than javac .

public class Parent
{

  private final Consumer<Parent> setup;

  protected final Collection<Object> x = new ArrayList<>();

  public Parent()
  {
    setup = Parent::setupImpl;
  }

  protected Parent(Consumer<Parent> extension)
  {
    setup = ((Consumer<Parent>) Parent::setupImpl).andThen(extension);
  }

  public final void setup()
  {
    setup.accept(this);
  }

  private static void setupImpl(Parent it)
  {
    it.x.add("thing1");
    it.x.add("thing2");
  }

}

public class Child
  extends Parent
{

  public Child()
  {
    super(Child::setupImpl);
  }

  protected Child(Consumer<Parent> extension)
  {
    super(((Consumer<Parent>) Child::setupImpl).andThen(extension));
  }

  private static void setupImpl(Parent it)
  {
    it.x.add("thing3");
  }

}

This is a slightly different approach for solving your problem, as in one of the comments to the selected answer says, you can use the decorator pattern (it is a little different from a traditional decorator, adapted to this problem), it is a cleaner solution in my opinion. I added 2 classes that add thing 3 and thing 4 to show the usage.

public interface ThingAdder {
  void addThings();
}

public abstract class AbstractAdder implements ThingAdder {
  protected List<String> map = new ArrayList<>(); // or your map impl
}

public class DefaultAdderDecorator implements ThingAdder {
  AbstractAdder decoratedThingAdder;

  public DefaultAdderDecorator(AbstractAdder decoratedThingAdder) {
    this.decoratedThingAdder = decoratedThingAdder;
  }

  @Override
  public void addThings() {
      decoratedThingAdder.map.add("thing 1");
      decoratedThingAdder.map.add("thing 2");
      decoratedThingAdder.addThings();
  }
}

public class Thing3Adder extends AbstractAdder {
  @Override
  public void addThings() {
    map.add("thing 3");
  }
}

public class Thing4Adder extends AbstractAdder {
  @Override
  public void addThings() {
    map.add("thing 4");
  }
}

public class AdderApp {
  public static void main(String args[]) {
    Thing3Adder thing3Adder = new Thing3Adder();
    Thing4Adder thing4Adder = new Thing4Adder();
    ThingAdder decoratedAdder = new DefaultAdderDecorator(thing3Adder);

    decoratedAdder.addThings();
    System.out.println("Decorated Thing3Adder map:"+thing3Adder.map);

    decoratedAdder = new DefaultAdderDecorator(thing4Adder);

    decoratedAdder.addThings();
    System.out.println("Decorated Thing4Adder map:"+thing4Adder.map);
  }
}

After running AdderApp this is printed:

Decorated Thing3Adder map:[thing 1, thing 2, thing 3]
Decorated Thing4Adder map:[thing 1, thing 2, thing 4]

The idea behind the decorator pattern is to augment existing functionality, in this case we are augmenting the addThings method by using a default decoration that adds thing 1 and thing 2 before calling the decorated object own addThings method, then whenever it is needed to have a new adder which requires the default values to be inserted first, the developer will just create a new ThingXAdder that extends the AbstractAdder.

I can't think of anything that would satisfy your condition of still being enforceable in future subclasses.

The Android sdk has the "super has not been called" exceptions in many of their core lifecycle methods, but it is strictly single level inheritance.

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