簡體   English   中英

如何強制我的類的任何子類始終調用它們覆蓋的父類的實現方法?

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

假設我有一個類,它實現了一個方法 (addThings())。 它作為子類樹的基礎:

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

現在,假設我們實現了一個子類(它也有 Thing 3)並且 Thing 3 也需要添加到 Thing 1 和 Thing 2 之上

顯而易見的 Java 解決方案似乎是讓子類的方法實現調用 super 的方法:

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

問題是實現子類的人很可能忘記這樣做,並且有一個錯誤

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

Java 中有沒有辦法強制任何擴展的子(和孫)類在覆蓋它時始終調用父類的方法? (類似於如何使方法抽象總是迫使他們實現它)。

  • 請注意,我需要將解決方案沿繼承樹向下傳播。

    換句話說,如果有人實現了需要添加 Thing4 的 GrandChildClass,他們將遭受與 ChildClass 相同的錯誤可能性。

    這意味着在 ParentClass 中有單獨的“addParentThings()”然后調用 addParentThings() 和 child-overridable 空 addThings() 的簡單修復(當你只有 1 級繼承時適用)是不夠的(因為孫子必須覆蓋非空 addThings )。

也許嘗試使用調用另一個可覆蓋方法的最終方法?

class ParentClass {

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

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

然后在子類中:

class ChildClass extends ParentClass {

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

您甚至可以使onPostDoStuff()方法抽象,因此孩子們必須覆蓋它。

如果您願意為每個類設置靜態的doStuff -Methods,這會擴展您的ParentClass並為您的ParentClass提供final public void doAllStuff() ,您可以使用反射解決問題:

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

以下方法強制調用超類的 setup 方法。 缺點或子類中可能存在的錯誤是實現者可能忘記提供合適的構造函數來為 setup 方法提供擴展。 這意味着孩子不能由孫子擴展。

它也很丑,因為子類不能在它們的擴展中放置子類特定的設置; 他們必須接受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。

我想不出有什么可以滿足您在未來的子類中仍然可以執行的條件。

Android sdk 的許多核心生命周期方法中都有“super has not been called”異常,但它是嚴格的單級繼承。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM