设计模式预备知识*
设计模式概述*
设计模式 (Design Pattern) 是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结,使用设计模式是为了可重用代码、让代码更容易被他人理解并且保证代码可靠性。
- 设计模式是从许多优秀的软件系统中总结出的成功的、能够实现可维护性复用的设计方案,可避免重复性工作。
- 设计模式提供了一套通用的设计词汇和一种通用的形式来方便开发人员之间沟通,使得设计方案更加通俗易懂。
- 大部分设计模式兼顾了系统的可重用性和可扩展性,这使得我们可以更好地重用一些已有的设计方案、功能模块甚至一个完整的软件系统,避免我们经常做一些重复的设计、编写一些重复的代码。
- 有助于初学者更加深入地理解面向对象思想。
狭义的设计模式一般分 3 大类共计 23 种,创建型模式关注对象的创建过程,结构型模式关注如何将现有类或对象组织在一起形成更加强大的结构,行为型模式关注系统中对象之间的交互研究系统在运行时对象之间的相互通信与写作,进一步明确对象的职责。
| 类型 | 种类 |
|---|---|
| 创建型模式 | 单例模式、简单工厂模式、抽象工厂模式、工厂方法模式、原型模式、建造者模式 |
| 结构型模式 | 适配器模式、桥接模式、组合模式、装饰模式、外观模式、享元模式、代理模式 |
| 行为型模式 | 职责链模式、命令模式、解释器模式、迭代器模式、中介者模式、备忘录模式、观察者模式、状态模式、策略模式、模板方法模式、访问者模式 |
UML 类图介绍*
Unified Model Language 统一建模语言,是在开发阶段,说明、可视化、构建和数学一个面向对象软件的开放方法。UML 类图是用户将所希望描述的事物抽象为类,描述类的内部结构和类之间关系的设计视图。
UML 类图关系*
UML 类图中,常有以下几种关系:泛化(Generalization)、实现(Realization)、关联(Association)、聚合(Aggregation)、组合(Composition)、依赖(Dependency)。
关系强弱关系:泛化 >= 实现 > 关联 > 聚合 > 组合 > 依赖。
UML 类的表示*
------------------------
| 类名 |
------------------------
| 成员变量 |
------------------------
| 成员方法 |
------------------------
类由一个矩形框表示,分 3 层:
- 第一层:类的名称,抽象类使用斜体;
- 第二层:类的属性,即成员变量,[可见性]名称:类型[=默认值];
- 第三次:类的方法,即成员函数,[可见性]名称([参数列表])[:返回类型]。
成员变量和成员方法前面有权限修饰符:
- "-": private
- "+": public
- "#": protected
泛化 Generalization*
表示类的继承关系。
用空心三角和实线,指向父类。
实现 Realization*
类与接口关系,表示类是接口所有特征和行为的实现。
空心三角和虚线,指向接口。
关联 Association*
拥有关系,一个类可以调用另一个类的公有的属性和方法。在类中以成员变量的方式表示。
- 单向关联:单箭头
- 双向关联:直线或双箭头
- 自关联:指向自己,如二叉树结构体
- 多重性关联:箭头,在关联直线上用一个数字或数字范围
聚合 Aggregation*
整体和部分的关系,部分离开整体后可以单独存在。常用于类的成员变量。
空心菱形和箭头,整体指向部分。
组合 Composition*
整体和部分的关系,部分离开整体后**不可以**单独存在,代表整体对象复杂部分对象的生命周期。常用于类的成员变量。
实心菱形和箭头,整体指向部分。
依赖 Dependency*
表示一种使用的关系,即一个类的实现需要另一个类的协助,常用于类方法的局部变量、方法参数等。
箭头虚线。
面向对象设计原则*
面向对象设计的原则是支持可维护性复用。
单一职责原则*
一个对象应该只包含单一的职责,并且该职责被完整地封装在一个类中。
就一个类而言,应该仅有一个引起它变化的原因。
首先要知道两个原则:高内聚和低耦合
- 高内聚:内聚是对软件系统中元素职责相关性和集中度的度量。如果元素具有高度相关的职责,除了这些职责内的任务,没有其他过多的工作,那么该元素就具有高内聚性。
- 低耦合:耦合是软件结构中各模块之间相互连接的一种度量,耦合强弱取决于模块间接口的复杂程度、进入或访问一个模块的点以及通过接口的数据。
单一职责原则用于控制类的力度大小。一个类承担的职责越多,它被复用的可能性越小。
开闭原则*
软件实体应对扩展开放,对修改关闭
开闭原则指软件实体(一个软件模块、一个由不同类组成的局部结构或一个独立的类)应该在不修改原有代码的基础上进行扩展。开闭原则的关键在于抽象化。可以为系统定义一个相对较为稳定的抽象层,将不同的实现行为放到具体的实现层中完成。
里氏代换原则*
所有引用基类的地方必须能透明地使用其子类的对象。
如果用子类对象来替换基类对象,程序不会产生异常和问题。反过来不成立,因为子类可以增加新的方法和属性。
里氏代换原则的指导意义在于:尽可能地使用基类类型来对对象进行定义,而在运行时再确定子类类型,然后用子类对象替换父类对象。
设计时应将父类设计为抽象类或接口,子类继承父类并实现在父类中声明的方法;运行时子类对象替换父类对象,可以方便扩展系统功能。
依赖倒转原则*
高层模块不应该依赖低层模块,它们都应该依赖抽象。抽象不应该依赖于细节,细节应该依赖于抽象。
高层或低层,指的是继承中的基类子类关系,基类或者越抽象的类,层次越高。简单说,依赖倒转原则要求针对接口编程,不要针对实现编程。
依赖倒转原则要求在程序代码中传递参数时,或在关联关系中,尽量引用层次高的类,即**使用接口或抽象类来声明变量类型、参数类型声明、方法返回类型声明、以及数据类型转换等,而不要使用具体类来做这些事情**。
接口隔离原则*
客户端不应该依赖那些它不需要的接口。
接口太大时,应该将它根据需要分割成多个更细小的接口,每个接口仅承担一个相对独立的角色或功能,使用该接口的客户端仅需要指导与之相关的方法即可。当然,接口不能过小,否则接口太多,不利于维护。一般而言,在接口中仅包含为某一类用户定制的方法即可。
合成复用原则*
优先使用对象组合,而不是通过继承来达到复用的目的。
根据 UML 类图关系,合成复用原则指导在软件设计时,优先使用关联、聚合和组合关系,尽量少用泛化(继承)。对象组合可以使系统更加灵活(黑箱复用),降低类与类之间的耦合度,一个类的变化尽可能不影响其他类。如果要使用继承,则需考虑里氏代换原则和依赖倒转原则。继承关系会破坏系统的封装性,将基类的实现细节暴露给子类(白箱复用),如果基类发生改变,那么子类的实现也不得不改变。
迪米特法则*
每一个软件单位对其他单位都只有最少的知识,而且局限于那些与本单位密切相关的软件单位。
迪米特法则要求一个软件实体应对尽可能少地与其他实体发生相互作用。如果一个系统负荷迪米特法则,那么当修改其中某一个模块时就会尽量少地影响其他模块。应用迪米特法则可以降低系统的耦合度。在类的设计上应该注意以下几点:类的划分上应尽量创建松耦合的类,类之间的耦合越低,越有利于复用;类的结构设计上,每一个类都应该降低其成员变量和成员函数的访问权限。