【问题标题】:How to solve code duplication in this example where I introduce inheritance to actually solve code duplication在这个例子中如何解决代码重复,我介绍了继承来实际解决代码重复
【发布时间】:2018-11-16 13:07:59
【问题描述】:

假设我有一个具有不同Scenes 的应用程序。每个场景都有一个由多个ScreenElements 构建的布局,例如,将绘制到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 总是返回背景、BOXONE 和 BOXTWO,即使您实际上需要背景、BOXONE、BOXTWO、BOXTHREE 和 BOXFOUR(例如在Scene 使用 `OtherLayout'。

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

澄清一下:

  • 在任何时候BasicLayout 都应该有背景、BOXONE 和 BOXTWO
  • 在任何时候OtherLayout应该有背景,BOXONE,BOXTWO, BOXTHREE 和 BOXFOUR。

【问题讨论】:

    标签: java android inheritance dry


    【解决方案1】:

    这应该可行:

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

    【讨论】:

    • 如果你创建一个BasicLayout你有BACKGROUND、BOXONE和BOXTWO,如果你创建一个OtherLayout你有BACKGROUND、BOXONE、BOXTWO、BOXTHREE、BOXFOUR。
    • 返回是什么意思?如果你创建一个子类的实例并不意味着你创建了两个实例,所以根本没有共享的 screenElements。
    • 我重构了我的代码,它就像一个魅力!非常感谢!
    【解决方案2】:

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

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

    我认为在你的情况下委托/组合更合适:

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

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

    还有一个更一般的注释。不要害怕重复和错误的抽象。现代计算机科学先驱 (Edsger Dijkstra) 的名言:

    抽象的目的不是模糊,而是创造一个新的 可以绝对精确的语义级别

    【讨论】:

    • 你说得对,我主要是共享数据(以及一些行为)。在您的解决方案中,每种不同类型的布局(我不仅有OtherLayout,还有OtherLayout2OtherLayout3、...)都需要自己的gettersdraw 方法,对吧?你不认为这是一个问题吗?特别是如果你有很多布局? Meini 的回答很好用,代码也很简洁。
    • 该解决方案在行数方面可能看起来不错,但对我来说意图不清楚。我喜欢查看代码并立即知道发生了什么。这就是为什么我说继承不是为了共享数据。突然之间,您必须遵循这种现在跨越 2 个类别的状态突变的模糊逻辑。随着时间的推移,类层次结构树往往会变得更大,而不是更小。
    • @Meini 提出的解决方案非常巧妙。但在我看来,这太聪明了:)
    【解决方案3】:

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

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

    【讨论】:

    • 在您的示例中,otherLayout 不包含背景、BOXONE 和 BOXTWO。如果你想要这个,你需要添加它们,这会导致代码重复。
    • ScreenElement 对象的创建可以移动到工厂类,或者提供ScreenElement 复制构造函数。
    猜你喜欢
    • 1970-01-01
    • 2019-02-20
    • 2020-11-18
    • 2010-12-13
    • 1970-01-01
    • 2023-03-04
    • 2015-05-09
    • 2019-01-18
    • 2015-10-26
    相关资源
    最近更新 更多