Design-Pattern-Relationships-Between-Objects

最近半个月,结合视频和书籍,把设计模式反反复复看了好几遍。

编写设计模式的笔记和分享,确实具有一定难度。首先,设计模式本身是抽象的,是对实践经验的总结。这意味着,对于每个设计模式,并没有完全确定的规范,不同的人有着不同的经验,也会有不同的实现。所以设计模式本质是一种思想,是不同实现中的相同部分,而思想本身很难用文字来完整表述。其次,笔记很容易变成网络上已有资料的复制。因为从头思考每个概念,琢磨其间关系,设计表达,本身事件很麻烦的事情,为了偷懒就会去复制别人的表述。最后,笔者也不一定保证自己的理解是准确的。但在刚刚学习完,一定是印象最深刻的。所以趁此机会做下记录。

设计模式是基于面向对象编程(Object-Oriented Programming, OOP)的。对于面向对象的思想和四大特性在此不赘述,直接从对象之间关系开始聊起。

对象之间的关系

对象/类之间(因为大部分类最后都会实例化,因此不加区分地使用类/对象)的关系包括:实现继承依赖关联聚合组合

值得一提的是,不同编程语言对于OOP的支持也不同,例如Python中要定义抽象类/接口,需要继承ABC抽象基类,而Java中可以通过abstract class来定义抽象类,通过interface来定义接口;Java中可以定义内部类,而PHP则不支持内部类。对象之间的关系是脱离于语言的,本文以Java为例,描述对象之间的关系在Java中的体现。

继承和实现

继承实现的特征比较明显,容易区分。

继承通常描述两个类中,其中一个类可以复制另一个类的属性/成员变量方法

通常来说,继承是为了提高代码复用性。如果有A、B两个类,在他们的定义中,都有一些相同的属性和方法,那么可以将这部分属性和方法抽取到C类中,并将A、B类都继承自C,那么A、B就拥有了C中定义的属性和方法。如果修改C类,那么A、B类会相应发生变动。而A/B中,可以在C的基础上新增属性方法,或者重写C中定义的方法,以此来区分C和A/B。C和A、B类的关系就是继承,称C为父类,A/B为子类

实现通常描述一个实现了另一个接口的所有方法的过程。当一个实现了一个接口时,它必须提供接口中定义的所有方法的具体实现。接口通常是一个特殊的,不能包含属性构造方法,且方法只有签名声明。

接口通常定义不同对象间交互的规范,而实现则是提供规范的具体实施过程。如果有两个类A、B,在他们定义中,包含同名且功能类似但不完全相同的方法,则可以将这些同名函数抽取到一个接口C中,且只需声明函数签名即可。为了实现接口C,A/B必须提供C中声明的所有方法的具体代码。

从此处看,实现本身没有提供额外的功能,也没有降低代码的冗余度。但可以利用多态的动态性,降低代码的耦合度。可以将代码中对A、B类的引用,都改成对接口C的引用。接口C的引用,可以指向所有实现了接口C的一堆类。调用C中声明的方法时,会动态根据对象具体类型,调用对应的实现。通过改成对接口C的引用,使得用到A、B的地方,不仅仅局限于只能使用A/B。有一种接口变量类型是一堆具体类的集合感觉。

依赖、关联、聚合、组合

依赖也是容易区分的。因为其他所有的关系,都必然是依赖关系。如果修改一个类的定义可能会造成另一个类的变化,那么这两个类之间就存在依赖关系。

在一个类的代码中,只要见到了另一个类的代码,通常这两个类就是依赖关系。但不包含另一个类的代码,也可能是依赖关系,例如对象A依赖了对象B,而对象B依赖了对象C。根据定义,修改C的代码,可能会导致B的行为变动,从而引起对象A的变化。这样看大部分类之间都包含依赖关系。幸运的是,对于大部分的依赖关系我们不需要刻意关注。

关联可视为一种特殊类型的依赖,即一个对象总是拥有访问与其交互的对象的权限,而简单的依赖关系并不会在对象间建立永久性的联系。

Java中,方法形参、局部变量是存在于栈中,方法返回后,其引用就会随着弹出而销毁。因此这种关系通常是临时的,通常使用依赖描述。而成员变量,在对象整个生命周期、不同方法中,都可以访问,关系是永久性的,因此使用关联描述。

关联中,又可以细分为聚合组合。在许多教材中,组合是一种特殊的聚合,即聚合包含组合。但在实际术语使用中,聚合通常指聚合中不包含组合的那部分,因此通俗地说两者不是包含关系,而是并列互斥的关系。

关联描述了一个对象A,通过成员变量的形式,拥有了另一个对象B的访问权限。在此基础上,如果对象A控制着对象B的生命周期,则A与B是组合关系,否则则是聚合关系。

常见的控制生命周期的形式,便是在构造函数中对成员变量进行实例化赋值,并且不提供setter方法。如此对象A在实例化时,便会完成对成员变量B的实例化,并且外部无法去替换成员变量的引用。对象A销毁时,由于全局不会再有对B的引用,B也会被销毁。组合体现出A和B“同生共死”的关系。

如果成员变量可以被外部替换,或者两者的生命周期可以不一致时,通常就是聚合关系。值得一提的是,Spring框架中,通过@Autowired注解自动注入的成员变量,通常是聚合关系,因为其生命周期不一致,被引用对象也可以作为其他对象的成员变量,并且也可以通过某些方式替换这些成员变量。

总结

对象之间的关系包括实现继承依赖关联聚合组合

其中实现继承容易区分和识别。

依赖是最弱的关系表示,剩下五种关系都是关联关系。

关联Java中,通常使用成员变量来表示。他包含两种具体情况。如果两个对象同生共死,则为组合关系,否则则是聚合关系。


Design-Pattern-Relationships-Between-Objects
http://dracoyus.github.io/2023/06/12/Design-Pattern-Relationships-Between-Objects/
作者
DracoYu
发布于
2023年6月12日
许可协议