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,我想使用继承。
我想做的是将 ScreenElement
s 存储在 HashMap
(或最终是 EnumMap
)中,以便我可以调用 ScreenElement
s在我的代码的其他地方更改它们的属性。
这是我现在想到的...
基本布局如下:
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),我引入了代码重复!
我现在需要复制 getScreenElements
、getScreenElement
和 draw
方法。在这种情况下,您需要覆盖它们,因为如果您不这样做,例如 getScreenElements
,总是 return BACKGROUND、BOXONE 和 BOXTWO,即使您实际上想要 BACKGROUND、BOXONE、BOXTWO、BOXTHREE 和BOXFOUR(例如在 Scene
中使用 `OtherLayout'。
希望这是有道理的,希望有人有巧妙的解决方案!
澄清一下:
- 任何时候
BasicLayout
应该有BACKGROUND、BOXONE和BOXTWO
- 任何时候
OtherLayout
应该有BACKGROUND, BOXONE, BOXTWO,
BOXTHREE 和 BOXFOUR。
这应该有效:
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
}
根据您的示例,代码库中的某处会知道创建 BasicLayout
或 OtherLayout
。重构该代码并提取地图,然后传递给构造函数。例如:
// 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
假设我有一个 Scenes
不同的应用程序。每个场景都有一个由多个 ScreenElement
构建的布局,例如,将绘制到 Canvas
的矩形框。对于某些 Scenes
,将使用相同的框。因此,鉴于DRY,我想使用继承。
我想做的是将 ScreenElement
s 存储在 HashMap
(或最终是 EnumMap
)中,以便我可以调用 ScreenElement
s在我的代码的其他地方更改它们的属性。
这是我现在想到的...
基本布局如下:
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),我引入了代码重复!
我现在需要复制 getScreenElements
、getScreenElement
和 draw
方法。在这种情况下,您需要覆盖它们,因为如果您不这样做,例如 getScreenElements
,总是 return BACKGROUND、BOXONE 和 BOXTWO,即使您实际上想要 BACKGROUND、BOXONE、BOXTWO、BOXTHREE 和BOXFOUR(例如在 Scene
中使用 `OtherLayout'。
希望这是有道理的,希望有人有巧妙的解决方案!
澄清一下:
- 任何时候
BasicLayout
应该有BACKGROUND、BOXONE和BOXTWO - 任何时候
OtherLayout
应该有BACKGROUND, BOXONE, BOXTWO, BOXTHREE 和 BOXFOUR。
这应该有效:
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
}
根据您的示例,代码库中的某处会知道创建 BasicLayout
或 OtherLayout
。重构该代码并提取地图,然后传递给构造函数。例如:
// 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