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