【问题标题】:have an issue with designing in the class in proper manner在课堂上以适当的方式设计有问题
【发布时间】:2012-01-18 18:27:41
【问题描述】:

以下有什么问题,我将如何使用 OO 原则更好地实现它?

我的应用程序包含一堆形状类,它们都继承自 Shape - CircleRectangleTriangle 等。其中一些需要显示在屏幕上,在这种情况下,它们需要利用通用屏幕逻辑,因此有一个包含通用逻辑的ScreenShape 超类,以及ScreenCircleScreenTriangle 子类。

【问题讨论】:

  • 您对这个设计的批评是什么?
  • @DOK 经理不满意......虽然他让我继续设计,但他仍然说你应该研究一下。
  • 他有没有说什么具体的?他为什么不满意?

标签: java oop design-patterns ooad


【解决方案1】:

我建议创建一个接口 Shape ,它提供有关几何形状形状的基本蓝图,并且您的所有类都实现形状接口并创建一个单独的类 ScreenShape(或抽象类),您的所有类都将扩展它,并在 ScreenShape 类中提供了在屏幕上显示的方法。例如你的矩形类会有点像这样

class rectangle extends ScreenShape implements Shape
{

// provide implementation of Shape interace methods.


// over-ride ScreenShape methods

public void draw()
{
// actual logic of drawing the objects on screen

}

}

【讨论】:

    【解决方案2】:

    您的经理在设计中遇到的问题可能是您将关注点泄漏到您的对象模型中。您将形状的概念与渲染方式混合在一起。那么现在如果您需要在绘图程序中使用形状怎么办?根据您当前的设计,您需要定义 PlottedShapePlottedCirclePlottedSquare 等。

    您应该保持 Shape 对象层次结构干净,然后定义一个单独的类来处理将形状渲染到输出设备。

    【讨论】:

      【解决方案3】:

      我看到的问题是您不能从 Java 中的多个类继承(尽管您可以实现多个接口)。所以你最好将ScreenShape 的功能合并到Shape 中,假设ScreenShape 在它的方法中有一些具体的代码。

      【讨论】:

      • 感谢您的回复,您能否详细说明什么应该是接口,什么应该是超类?
      • 这种方法的问题是每个扩展 Shape 的类现在都具有“屏幕”功能,据我了解,这不应该发生。
      【解决方案4】:

      关于您在回复@DOC 时发布的内容:

      @DOK 经理不满意......虽然他让我继续设计,但他仍然说你应该研究一下。

      我想您的经理对当前设计不满意的原因是它遭受了名为Parallel Inheritance Hierarchiescode smell 的影响。基本上,它描述了您正在经历的事情:每次创建新的子类时,您都必须在并行层次结构中创建其对应的子类。 这将使您的设计更难更改,从而更难维护。

      当前答案审核

      我想评论一些我不同意当前答案的事情。 基本上建议您:

      1. ScreenShape 类继承
      2. Shape的每个子类中实现绘图逻辑

      我可以看到选项 n° 1 存在两个问题:

      • 它将 Shape 定义为一个接口,因此您必须在每个子类中重新实现它(Rectangle 等)
      • 要克服以前的问题,您可以让ScreenShape 实现它,因为所有类型的形状都会继承自它。这很糟糕,@Perception 的答案解释了原因。
      • 假设现在要求您将形状导出到 XML 文件(这是对形状的另一个操作,就像显示它们一样)。按照这种方法,您需要在XMLShape 类上实现该行为并从它继承,但您已经从ScreenShape 继承。看到问题了吗?

      选项 n° 2 迫使您用表示问题来膨胀您的域模型,正如@Perception 所说:)

      要实现的一些目标

      从前面我们可以看出:

      • 您会希望您的类在您的域模型发生变化时发生变化,而不是在您显示形状或将它们导出为 XML 的方式发生变化时发生变化。
      • 这会将您带到Single Responsibility Principle,它指出一个类应该只有一个改变的理由。
      • 我们发现显示以及导出到 XML(在我给您的示例中)是您将对形状执行的操作,并且您希望以后轻松添加新操作而不更改形状类。李>

      建议的解决方案

      首先,清理你的Shape 层次结构,只留下属于你的问题域的东西。

      然后,您需要不同的方式来显示形状,而无需在Shape 层次结构中实现该行为。如果您不处理嵌套形状(包含其他形状的形状),您可以使用一种称为Double Dispatch 的技术。它允许您根据接收方的类型分派方法调用,方法是将方法名称中的信息编码为它接收的参数,如下所示:

      public class Circle extends Shape {
          public void DisplayOn(IShapeDisplay aDisplay) {
              aDisplay.DisplayCircle(this);
          }
      }
      
      public class Rectangle extends Shape {
          public void DisplayOn(IShapeDisplay aDisplay) {
              aDisplay.DisplayRectangle(this);
          }
      }
      
      interface IShapeDisplay {
          void DisplayCircle(Circle aCircle);
          void DisplayRectangle(Rectangle aRectangle);
      }
      

      实现IShapeDisplay 的类将负责显示各种形状。这样做的好处是您设法从Shape 层次结构中清除了那些讨厌的细节,将它们封装在自己的类中。因此,现在您无需修改​​ Shape 子类即可更改该类。

      最终评论

      您可以在 Martin Fowler 的书中阅读有关代码异味和并行继承层次结构的更多信息:Refactoring: Improving the Design of Existing Code

      此外,如果您需要处理形状嵌套:复合和访问者模式,您想查看Design Patterns: Elements of Reusable Object-Oriented Software 书籍。

      希望对你有所帮助!

      【讨论】:

        【解决方案5】:

        从最通用到最具体。

        所以应该是这样的:

        。形状(包含任何类型的“形状”的代码 - 可能是抽象类或接口)

        .. ScreenShape(包含在屏幕上绘制的逻辑)

        ...圆(包含在屏幕上绘制圆的逻辑)

        ...正方形(包含在屏幕上绘制正方形的逻辑)

        所以:

        public abstract class Shape {
          // ... generic Shape stuff here, possibly an interface
          public abstract void getCoordinates();
        }
        
        public abstract class ScreenShape extends Shape {
          public void drawOnScreen() {
            // logic for drawing on a screen here
            // likely invoking 'getCoordinates()' 
          }
        }
        
        public class Circle extends ScreenShape {
          public void getCoordinates() {
            // circle specific stuff here, implementation of stuff inherited from Shape
          }
          // also inherits whatever 'drawOnScreen()' implementation 
          // is provided in ScreenShape
        }
        

        【讨论】:

        • 这个解决方案提出了一个新问题:如果我需要一个既可以是屏幕形状也可以是普通形状的圆形,它应该扩展什么?我应该开设 2 个新课程吗?
        【解决方案6】:

        你可以实现 draw() 方法来实现通用逻辑。

        public abstract class Shape{
        
            public void draw(){
                //common logic here
        
                drawImpl();
        
                //more logic here if needed
            }
        
            public abstract void drawImpl();        
        
        }
        

        然后您的实现将各自实现 drawImpl 类。

        或者,您可以实现一个类 ScreenBehaviour 并使用组合。然后您的每个实现都可以使用不同的 ScreenBehaviour 实现,如下所示:

        public abstract class Shape{
            private ScreenBehaviour screenBehaviour;
        
            public final void draw(){
                screenBehaviour.execute();
            }
        }
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2020-08-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2016-11-20
          • 1970-01-01
          相关资源
          最近更新 更多