How to solve code duplication in this example 我引入了继承来实际解决代码重复

How to solve code duplication in this example where I introduce inheritance to actually solve code duplication

假设我有一个 Scenes 不同的应用程序。每个场景都有一个由多个 ScreenElement 构建的布局,例如,将绘制到 Canvas 的矩形框。对于某些 Scenes,将使用相同的框。因此,鉴于DRY,我想使用继承。

我想做的是将 ScreenElements 存储在 HashMap(或最终是 EnumMap)中,以便我可以调用 ScreenElements在我的代码的其他地方更改它们的属性。

这是我现在想到的...

基本布局如下:

Public class BasicLayout {
    private HashMap<String, ScreenElement> screenElements;

    public BasicLayout() {
        screenElements = new HashMap<>();
        screenElements.put("BACKGROUND", new ScreenElement(...));
        screenElements.put("BOXONE", new ScreenElement(...));
        screenElements.put("BOXTWO", new ScreenElement(...));
    }

    public HashMap<String, ScreenElement> getScreenElements() {
        return screenElements;
    }

    public ScreenElement getScreenElement(String elementName) {
        return screenElements.get(elementName);
    }

    public void draw(Canvas canvas) {
        for (ScreenElement screenElement: screenElements) {
            screenElement.draw(canvas);
        }
    }
}

那么另一个布局可能是这样的:

Public class OtherLayout extends BasicLayout {      
    private HashMap<String, ScreenElement> screenElements;

    public OtherLayout() {
        screenElements = new HashMap<>(super.getScreenElements);
        screenElements.put("BOXTHREE", new ScreenElement(...));
        screenElements.put("BOXFOUR", new ScreenElement(...));
    }

    @Override
    public HashMap<String, ScreenElement> getScreenElements() {
        return screenElements;
    }

    @Override
    public ScreenElement getScreenElement(String elementName) {
        return screenElements.get(elementName);
    }

    @Override
    public void draw(Canvas canvas) {
        for (ScreenElement screenElement: screenElements) {
            screenElement.draw(canvas);
        }
}

所以问题是通过解决代码重复(我不再需要在我的OtherLayout中添加BACKGROUND,BOXONE和BOXTWO),我引入了代码重复!

我现在需要复制 getScreenElementsgetScreenElementdraw 方法。在这种情况下,您需要覆盖它们,因为如果您不这样做,例如 getScreenElements,总是 return BACKGROUND、BOXONE 和 BOXTWO,即使您实际上想要 BACKGROUND、BOXONE、BOXTWO、BOXTHREE 和BOXFOUR(例如在 Scene 中使用 `OtherLayout'。

希望这是有道理的,希望有人有巧妙的解决方案!

澄清一下:

这应该有效:

public class BasicLayout {
    protected HashMap<String, ScreenElement> screenElements = new HashMap<>();

    public BasicLayout() 
    {
        screenElements.put("BACKGROUND", new ScreenElement(...));
        screenElements.put("BOXONE", new ScreenElement(...));
        screenElements.put("BOXTWO", new ScreenElement(...));
    }

    public HashMap<String, ScreenElement> getScreenElements() {
        return screenElements;
    }

    public ScreenElement getScreenElement(String elementName) {
        return screenElements.get(elementName);
    }

    public void draw(Canvas canvas) {
        for (ScreenElement screenElement: screenElements) {
            screenElement.draw(canvas);
        }
    }
}

public class OtherLayout extends BasicLayout {     
    public OtherLayout() {
        screenElements.put("BOXTHREE", new ScreenElement(...));
        screenElements.put("BOXFOUR", new ScreenElement(...));
    }
}

根据示例代码,只需要一个 class 即可在构造函数中获取它将负责的元素。例如:

public class Layout {
    private final Map<String, ScreenElement> screenElements;

    public Layout(Map<String, ScreenElement> screenElements) {
        this.screenElements = screenElements;
    }

    // other methods
}

根据您的示例,代码库中的某处会知道创建 BasicLayoutOtherLayout。重构该代码并提取地图,然后传递给构造函数。例如:

 // in some method that knows when to create a `BasicLayout` or `OtherLayout`
 Map<String, ScreenElement> basicScreenElements = new HashMap<>();
 basicScreenElements .put("BACKGROUND", new ScreenElement(...));
 basicScreenElements .put("BOXONE", new ScreenElement(...));
 basicScreenElements .put("BOXTWO", new ScreenElement(...));
 Layout basicLayout = new Layout(basicScreenElements);

// ....
 Map<String, ScreenElement> otherScreenElements = new HashMap<>();
 otherScreenElements .put("BOXTHREE", new ScreenElement(...));
 otherScreenElements .put("BOXFOUR", new ScreenElement(...));
 Layout otherLayout = new Layout(otherScreenElements);

我不认为解决继承是避免重复的好选择。你总是违反 Liskov 替换原则是某种解决方式(在你的情况下不是这样解决:))。这会导致更大的问题。

并且第二点继承应该只用于共享行为而不是数据。

我认为 delegation/composition 更适合您的情况:

public OtherLayout() {
    screenElements = new HashMap<>();
    screenElements.putAll(new BasicLayout().getScreenElements());
    screenElements.put("BOXTHREE", new ScreenElement());
    screenElements.put("BOXFOUR", new ScreenElement());
}

因为从外观上看,两者唯一的共同点 类 是源元素映射中的元素。

还有一个更笼统的注释。不要像害怕错误的抽象一样害怕重复。引自现代计算机科学先驱 (Edsger Dijkstra):

The purpose of abstraction is not to be vague, but to create a new semantic level in which one can be absolutely precise