1、设计模式概述
1.1 设计模式的定义与分类
模式的定义:
- 模式是在特定环境下人们解决某类重复出现问题的一套成功或有效的解决方案。
设计模式的目的:
-
为了可重用代码、让代码更容易被他人理解、提高代码可靠性。
设计模式(Desgin Pattern)的定义: -
是一套被反复使用的、多数人知晓的、经过分类编目的代码设计经验的总结,使用设计模式是为了可以重用代码,让代码更容易被他人理解并且提高代码的可靠性。
设计模式的基本要素
- 设计模式一般包含模式名称、问题、目的、解决方案、效果、实例代码和相关设计模式等基本要素,其中4个关键要素如下:
- 模式名称(Pattern Name),通过一两个词为模式命名,便于交流;
- 问题(Problem),描述在何时使用模式,它包含设计中存在的问题以及问题存在的原因;
- 解决方案(Solution),描述了设计模式的组成部分,以及这些组成部分之间的相互关系、各自职责和协作方式;
- 效果(Consequences),描述模式的优缺点以及在使用模式时应该权衡的问题。
设计模式的分类
- 根据目的(模式是用来做什么的)可以分为创建型(Creational),结构型(Structural)和行为型(Behavioral)三类:
- 创建型模式主要用于创建对象;
- 结构型模式主要用于处理类或对象的组合;
- 行为型模式主要用于描述类或对象如何交互和怎样分配职责。
1.2 实例目录
类型 | 模式名称 |
---|---|
创建型模式 (Creational Pattern) | 单例模式 (Singleton Pattern) |
简单工厂模式 (Simple Factory Pattern) | |
工厂方法模式 (Factory Method Pattern) | |
抽象工厂模式 (Abstract Factory Pattern) | |
原型模式 (Prototype Pattern) | |
创建者模式 (Builder Pattern) | |
结构型模式 (Structural Pattern) | 适配器模式 (Adapter Pattern) |
桥接模式 (Bridge Pattern) | |
组合模式 (Composite Pattern) | |
装饰模式 (Decorator Pattern) | |
外观模式 (Facade Pattern) | |
享元模式 (Flyweight Pattern) | |
代理模式 (Proxy Pattern) | |
行为型模式 (Behavioral Pattern) | 职责链模式 (Chain of Responsibility Pattern) |
命令模式 (Command Pattern) | |
解释器模式 (Interpreter Pattern) | |
迭代器模式 (Iterator Pattern) | |
中介者模式 (Mediator Pattern) | |
备忘录模式 (Memento Pattern) | |
观察者模式 (Observer Pattern) | |
状态模式 (State Pattern) | |
策略模式 (Strategy Pattern) | |
模板方法模式 (Template Method Pattern) | |
访问者模式 (Visitor Pattern) |
1.3 设计模式的作用
- 设计模式是从许多优秀的软件系统中总结出的成功的、能够实现可维护性复用的设计方案,使用这些方案可以避免重复性的工作,有助于提高设计和开发的效率;
- 设计模式提供了一套通用的设计词汇和一种通用的形式便于开发人员之间进行沟通和交流,使得设计方案更加的通俗易懂;
- 大部分设计模式兼顾了系统的可重用性和可拓展性,能帮助开发人员更好的完成设计方案,避免做一些重复的设计,编辑一些重复的代码。
- 合理使用设计模式并对设计模式的使用情况进行文档化,将有助于别人更快地理解系统。
2、UML类图
2.1 UML概述
UML结构:
- 试图(View):UML视图用于从不同的角度来表示待建模的系统。
- 图(Diagram):UML图是描述UML视图内容的图形。
- 模型元素(Model Element)
- 通用机制(General Mechanism)
2.2 类与类的图示
2.2.1 类
类(Class封装了数据和行为,是面向对象的重要组成部分,它是具有相同属性、操作、关系的对象集合的总称。
在软件系统运行时,类将被实例化成对象(Object),对象对应于某个具体的事物,是类的实例(Instance)。
类图(Class Diagram是用出现在系统中的不同类来描述系统的静态结构,主要用来描述不同的类以及他们之间的关系。
2.2.2 类的UML图示
在UML中,类使用包含类名、属性和操作缺带有分隔线的长方形来表示,如定义一个Student类,它包含属性no,name,school,totalScore,以及操作display(),对应的UML类图如图所示:
对应的Java代码片段如下:
public class Student {
private long no;
private int age;
private String school;
private float totalScore;
public void display(){
...
}
}
在UML类图中,类一般由三个部分组成。
- 类名(Name):每个类都必须有一个名字,类名是一个字符串;例如上面的Student;
- 类的属性(Attribute):属性是指类的性质,即类的成员变量,一个类可以有任意多个属性,也可以没有属性。
UML规定属性的表达方式为:
可见性 名称:类型 [ = 默认值]
其中:
-
“可见性”表示该属性对于类外的元素而言是否可见,包括公有(public)、私有(private)和受保护(protected)3种,在类图种分别用符号+、-和#表示。
-
“名称”表示属性名,用一个字符串表示。
-
“类型”表示属性的数据类型,可以是基本数据类型,也可以是用户自定义的类型。
-
“默认值”是一个可选项,即属性的初始值。
-
类的操作(Operations):操作时类的一个任意一个实例对象都i=可以使用的行为,时类的成员方法。
UML规定操作的表示方式为:
可见性 名称([参数列表])[ :返回类型]
其中:
- “可见性”的定义于属性的可见性定义相同。
- “名称”即方法名,用一个字符串表示。
- “参数列表”表示方法的参数,其语句与属性的定义相似,参数个数时任意的,多个参数之间用逗号“,”分隔。
- “返回类型”是一个可选项,表示方法分返回值类型,依赖于具体的编程语言,可以是基本数据类型,也可以时用户自定义类型,还可以时空类型(viod)。如果时构造方法,则无返回类型。
2.3 类之间的关系
类与类之间有聚合、组合、关联、泛化、依赖、实现六种关系。
2.3.1 关联关系
关联(Association)关系是类与类之间最常用的一种关系,它是一种结构化关系,用于表示一类对象与另一类对象之间有联系。
例子:
在一个登录界面类LoginForm中包含一个JButton类型的注册按钮loginButton,它们之间可以表示为关联关系。代码实现时可以在LoginForm中定义一个名为loginButton的属性对象,其类型为JButton。
对应Java代码片段如下:
public class LoginButton {
private JButton loginButton; //定义为成员变量
...
}
public class JButton {
...
}
2.3.2 聚合关系
聚合(Aggregation)关系表示整体与部分的关系。在聚合关系中,成员对象是整体对象的一部分,但是成员对象是整体对象的一部分,但是成员对象可以脱离整体对象独立存在。
例子:
汽车发动机(Engine)是汽车(Car)的组成部分,但汽车发动机可以对立存在,因此,汽车和发动机是聚合关系。
在代码实现聚合关系时,成员对象通常作为构造方法、Setter方法或业务方法的参数注入整体对象中。对应Java代码片段如下:
public class Car{
private Engine engine;
//构造注入
public Car(Engine engine){
this.engine = engine;
}
//设值注入
public void setEngine(Engine engine){
this.engine = engine;
}
...
}
public class Engine{
...
}
2.3.3 组合关系
组合(Composition)关系也表示类之间整体和部分的关系,但是在组合关系中整体对象可以控制成员对象的生命周期。一旦整体对象不存在,成员对象也将不存在,成员对象与整体对象之间同生共死的关系。
例子:
人的头(Head)与嘴巴(Mouth),嘴巴是头的组成部分之一,而且如果头没了,嘴巴也就没了,因此头和嘴巴是组合关系。
在代码实现组合关系时,通常在整体类的构造方法中直接实例化成员类。对应Java代码片段如下:
public class Head{
private Mouth mouth;
public Head(){
mouth = new Mouth(); //实例化成员类
}
...
}
public class Mouth{
...
}
2.3.4 依赖关系
依赖(Dependency)关系是一种使用关系,特定事物的改变有可能会影响到使用该事物的其他事物,在需要表示一个事物使用另一个事物时使用依赖关系。大多数情况下,依赖关系体现在某个类的方法使用另一个类的对象作为参数。
例子:
驾驶员在开车,在Driver类的driver( )方法中将Car类型的对象car作为一个参数传递,以便在driver( )方法中能够调用Car类的move( )方法,且驾驶员的driver( )方法依赖车的move( )方法,因此类Driver依赖类Car。
对应Java代码片段如下:
public class Driver{
public void driver(Car car){
car.move();
}
...
}
public class Car{
public void move(){
...
}
...
}
2.3.5 泛化关系
泛化(Generalization)关系也就是继承关系,用于描述父类与子类之间的关系,父类又称作基类或者超类,子类又称作派生类。
例子:
Student类和Teacher类都是Person类的子类,Student类和Teacher类都继承了Person类的属性和方法,Person类的属性包含姓名(name)和年龄(age)
对应Java代码片段如下:
public class Person{
protected String name;
protected int age;
public void speak(){
...
}
}
public class Student extends Person{
private long studentNo;
public void student(){
...
}
}
public class Teacher extends Person{
private long teacherNo;
public void teaching{
...
}
}
2.3.6 实现关系
实现(Implementation)关系是用来规定接口和实现接口的类或者构建结构的关系,接口是操作的集合,而这些操作就用于规定类或者构建的一种服务。
public interface Vehicle{
public void move();
}
public class Car implements Vehicle{
public void move(){
...
}
}
public class Ship implements Vehicle{
public void move(){
...
}
}
3、面向对象设计原则
软件的可维护性和可复用性是两个非常重要的用于衡量软件质量的属性,面向对象设计原则是为了支持可维护性复用而诞生的。这些原则蕴含在很多的设计模式中,并且每个设计模式都符合某一个或者多个设计原则。
设计原则名称 | 定义 |
---|---|
单一职责原则 | |
SRP | 一个对象应该包含单一的职责,并且该职责被完整封装在一个类中。 |
开闭原则 | |
OCP | 软件实体应该对扩展开放,对修改关闭。 |
依赖倒置原则 | |
DIP | 所有引用基类的地方必须能够透明的使用其子类的对象。 |
接口隔离原则 | |
ISP | 高层模块不应该依赖低层模块,他们都应该依赖抽象,抽象不应该依赖细节,细节应该依赖抽象。 |
里氏替换原则 | |
LSP | 所有引用基类的地方必须能够透明的使用其子类的对象。 |
合成复用原则 | |
CRP | 优先使用对象组合,而不是通过继承来达到复用的目的。 |
迪米特法则 | |
LoD | 每一个软件单元对其它单位都应该只有最少的知识,而且局限于于那些本单位密切相关的软件单位。 |
3.1 开闭原则
开闭原则是面向对象的可复用设计的第一块基石,是最重要的面向对象设计原则。
开闭原则软件实体应当对扩展开放,对修改关闭。
实现方法可以通过“抽象约束、封装变化”来实现开闭原则,即通过接口或者抽象类为软件实体定义一个相对稳定的抽象层,而将相同的可变因素封装在相同的具体实现类中。
3.2 里氏替换原则
里氏代换原则所有引用基类的地方必须能透明地使用其子类的对象。
实现方法子类可以扩展父类的功能,但不能改变父类原有的功能。
【例】里氏代替原则在“几维鸟不是鸟”实例中的应用。
分析:鸟一般都会飞行,如燕子的飞行速度大概是每小时可达120公里。但是新西兰的“几维鸟”,由于翅膀退化,因此无法飞行。
3.3 依赖倒置原则
依赖倒转原则高层模块不应该依赖低层模块,它们都应该依赖抽象。抽象不应该依赖于细节,细节应该依赖于抽象。
【例】依赖倒置原则在“顾客购物程序”中的应用。
分析:如顾客类的shopping(ShaoguanShop shop)方法只访问韶关网店,如果该顾客想从另外一家商店(如:婺源网店WuyuanShop)购物,就要修改该方法的参数类型,这违背了“依赖倒置”原则。
解决方法是:定义一个商店接口Shop,顾客类面向该接口编程,其类图如下。
3.4 单一职责原则
单一职责原则是最简单的面向对象设计原则,用于控制类的粒度大小。
单一职责原则一个对象应该只包含单一的职责,并且该职责被完整地封装在一个类中。
单一职责原则的实现方法:需要设计人员发现类的不同职责并将其分离,再封装到不同的类或模块中。
【例】大学学生工作管理程序。
分析:大学学生工作主要包括学生生活辅导和学生学业指导两个方面的工作,其中生活辅导主要包括班委建设、出勤统计、心理辅导、费用催缴、班级管理等工作,学业指导主要包括专业引导、学习辅导、科研指导、学习总结等工作。如果将这些工作交给一位老师负责显然不合理,正确的做法是生活辅导由辅导员负责,学业指导由学业导师负责。
3.5 接口隔离原则
接口隔离原则客户端不应该依赖那些它不需要的接口。
【例】学生成绩管理程序。
分析:学生成绩管理程序一般包含插入成绩、删除成绩、修改成绩、计算总分、计算均分、打印成绩信息、查询成绩信息等功能,如果将这些功能全部放到一个接口中显然不太合理,正确的做法是将它们分别放在输入模块、统计模块和打印模块等3个模块中。
3.6 迪米特原则
迪米特法则又称为最少知识原则(Least Knowledge Principle, LKP)
迪米特法则每一个软件单位对其他的单位都只有最少的知识,而且局限于那些与本单位密切相关的软件单位。
实现方法:
【例】明星与经纪人的关系实例。
分析:明星由于全身心投入艺术,所以许多日常事务由经纪人负责处理,如:与粉丝的见面会,与媒体公司的业务洽淡等。这里的经纪人是明星的朋友,而粉丝和媒体公司是陌生人,所以适合使用迪米特法则。
3.7 合成复用原则
合成复用原则又称为组合/聚合复用原则(Composition/ Aggregate Reuse Principle, CARP)
合成复用原则优先使用对象组合,而不是继承来达到复用的目的。
实现方法:
某软件公司开发人员在初期的CRM系统设计中,考虑到客户数量不多,系统采用Access作为数据库,与数据库操作有关的类,例如CustomerDAO类等都需要连接数据库,连接数据库的方法getConnection()封装在DBUtil类中,由于需要重用DBUtil类的getConnection()方法,设计人员将CustomerDAO作为DBUtil类的子类,使用合成复用原则对其进行重构。