面向对象中父类的确立

标签: 架构设计

保留所有版权,请引用而不是转载本文(原文地址 https://yeecode.top/blog/29/ )。

封装、继承、多态,这是面向对象的三大特性。这三大特性中,有两个特性都与父类有着深深的关联:

可见,父类概念的区分在面向对象的设计中十分基础也十分重要。但实际上,大多数开发者对父类的概念有不小的认知偏差。本文我们将讨论这一点。

传统认知的误区

首先我们给出一个问题。

问题1:矩形是不是正方形的父类?

请先在心中给出这个问题的答案,然后在继续阅读本文。


接下来,我们给出矩形类Rectangle和正方形类Square中的方法(省略方法的具体实现),如下所示。

矩形类:

public class Rectangle {
    private double length;
    private double width;
    private String color;

    public Rectangle(double length, double width, String color) {}
    public void setLength(double length) {}
    public void setWidth(double width) {}
    public void setColor(String color) {}
    public double getArea() {}
    public String getColor() {}
}

正方形类:

public class Square {
    private double sideLength;
    private String color;
    
    public Square(double sideLength, String color) {}
    public void setSideLength(double sideLength) {}
    public void setColor(String color) {}
    public double getArea() {}
    public String getColor() {}
}

然后,请再次回答问题。

问题2:上述所示的矩形是不是正方形的父类?


在问题1中,我们往往认为矩形是正方形的父类,因为,在传统认知(主要是指几何学认知)中,正方形就是矩形的特殊情况,那矩形也必然应该是正方形的父类。

在问题2中,我们查看了矩形类Rectangle和正方形类Square的具体方法后,可以判断出矩形不是正方形的父类,因为后者的很多方式是前者不具备的。这时,如果我让你整理两者的继承关系,大家往往会遵循如下的思路:

  1. 以矩形类Rectangle作为父类,以正方形类Square作为子类。
  2. 找出矩形类Rectangle类中子父类共有的方法保留,其他方法则移动到一个新的类中。这个新的类是非正方形的矩形类NotSquare。
  3. 整理删除各个子类中可以继承得到的方法。

然后会得到下面的类图。

图

如果这样,那很不幸。你落入了传统认知的误区。

正确的父类确立方法

在面向对象的设计中,父类包含了子类的共有行为,而子类则是在父类基础上的特殊化。在执行这个思想时,我们要摆脱传统认知带来的误导。

假设存在A、B、C、D四个类,我们寻找父类的办法是找寻它们的方法的交集。如果最终的交集恰好等于某个类,则这个类恰好是父类,如左图所示。如果最终的交集小于所有现存的类,则交集方法组成的是父类,如右图所示。

图

这种父类确立方法摆脱了传统认知带来的误导,能够准确确立父类。这是,我们再分析问题2,便会发现应该是如下所示的类图。

图

这时我们会发现,矩形类Rectangle和正方形类Square是完全平级的两个子类,而不应该是将矩形类Rectangle作为父类。

不过也要注意,通过交集获取父类是一种遵循面向对象逻辑的方法,最终得到的父类可能在现实中没有对应的事物,进而带来命名上的困难。例如在这里并集区域被命名为了“可着色的区域”ColorableArea。并且,传统的父类确立方法也会面临命名困难,例如在错误的父类划分中也产生了一个难以命名的子类,被命名为了“矩形中不是正方形的部分”NotSquare。

当涉及的类逐渐增加时,这种父类确立方法的优势会更为明显。例如,矩形类、正方形类、圆形类、五边形类都放在一起,按照此方法仍然能够很快的得出各级父类。而如果受到传统认知的误导,则很有可能导致继承关系的杂乱。

本文首发于个人知乎:易哥(https://www.zhihu.com/people/yeecode),欢迎关注。

作者书籍推荐