我如何强制我的 class 的任何子 class 始终调用它们正在覆盖的父级实现方法?

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

假设我有一个 class,它实现了一个方法 (addThings())。它作为子classes:

树的基础
ParentClass {
    protected void addThings() {
        map.add(thing1);
        map.add(thing2);
    }
}

现在,假设我们实现了一个子项 class(它也有事物 3)并且事物 3 也需要添加 在事物 1 和事物 2 之上.

显而易见的 Java 解决方案似乎是让子 class 的方法实现调用父类的方法:

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

问题是,谁实现了 subclass 很可能会忘记这样做,并且有一个错误:

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

Java有没有办法强制任何扩展子(和孙)class总是调用父方法,如果他们覆盖它? (类似于使方法抽象总是强制他们实现它的方式)。

也许尝试使用调用另一个可重写方法的最终方法?

class ParentClass {

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

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

然后在child class:

class ChildClass extends ParentClass {

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

您甚至可以将 onPostDoStuff() 方法抽象化,因此 children 可以覆盖它。

我想不出有什么可以满足你在未来的子类中仍然可执行的条件。

Android sdk 的许多核心生命周期方法都有"super has not been called"异常,但它是严格的单级继承。

如果您愿意为每个 class 设置静态的 doStuff-Methods,这会扩展您的 ParentClass 并给您的 ParentClass 一个 final public void doAllStuff() -方法,可以用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 {}

如果只需要调用属性,可以使用getDeclaredField,这种情况字段可以不是static

以下方法强制调用超类的设置方法。缺点或子类中可能存在的错误是,实现者可能会忘记提供合适的构造函数来为设置方法提供扩展。这意味着 child 无法扩展 grandchild.

它也很难看,因为子类不能在它们的扩展中设置 subclass-specific;他们必须接受 Parent 作为参数。

总的来说,这里的尴尬困难表明,如果有的话,最好在静态分析器中执行,而不是 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");
  }

}

这是解决您的问题的一种略有不同的方法,正如所选答案的评论之一所说,您可以使用装饰器模式(它与传统的装饰器略有不同,适用于此问题) ,在我看来这是一个更清洁的解决方案。我添加了 2 类 来添加事物 3 和事物 4 以显示用法。

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);
  }
}

在 运行 AdderApp 之后打印:

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

装饰器模式背后的想法是增强现有功能,在这种情况下,我们通过使用默认装饰来增强 addThings 方法,该装饰在调用装饰对象自己的 addThings 方法之前添加事物 1 和事物 2,然后每当它需要有一个需要首先插入默认值的新加法器,开发人员将只创建一个扩展 AbstractAdder 的新 ThingXAdder。