1.概念
- 一套被反复使用的、多数人知晓的、经过分类编目的、代码设计经验的总结
- 是一种用于对软件系统中不断重现的设计问题的解决方案进行文档化的技术
- 是一种共享专家设计经验的技术
- 目的:为了可重用代码、让代码更容易被他人理解、提高代码可靠性
2.要素
设计模式一般包含模式名称、问题、目的、解决方案、效果、实例代码和相关设计模式等基本要素。
4个关键要素如下:
- 模式名称 (Pattern Name)
- 问题 (Problem)
- 解决方案 (Solution)
- 效果 (Consequences)
(1)模式名称(pattern name) 一个助记名,它用一两个词来描述模式的问题、解决方案和效果。
(2)问题(problem) 描述了应该在合适使用模式。它解决了设计问题和问题存在的前后因果,它可能描述了特定的设计问题,如怎样用对象表示算法等。也可能描述了导致不灵活设计的类或对象结构。
(3)解决方案(solution) 描述了设计的组成成分,它们之间的相互关系及各自的职责和协作方式。因为模式就像一个模板,可应用于多种不同场合,所以解决方案并不描述一个特定而具体的设计或实现,而是提供设计问题的抽象描述和怎样用一个具有一般意义的元素组合(类或对象组合)来解决这个问题。
(4)效果(consequences) 描述了模式应用的效果及使用模式应权衡的问题。尽管我们描述设计决策时,并不总提到模式效果,但它们对于评价设计选择和理解使用模式的代价及好处具有重要意义。软件效果大多关注对时间和空间的衡量,它们也表述了语言和实现问题。因为复用是面向对象设计的要素之一,所以模式效果包括它对系统的灵活性、扩充性或可移植性的影响,显式地列出这些效果对理解和评价这些模式很有帮助。
3.优点
- 融合了众多专家的经验,并以一种标准的形式供广大开发人员所用
- 提供了一套通用的设计词汇和一种通用的语言,以方便开发人员之间进行沟通和交流,使得设计方案更加通俗易懂
- 让人们可以更加简单方便地复用成功的设计和体系结构
- 使得设计方案更加灵活,且易于修改
- 将提高软件系统的开发效率和软件质量,且在一定程度上节约设计成本
- 有助于初学者更深入地理解面向对象思想,方便阅读和学习现有类库与其他系统中的源代码,还可以提高软件的设计水平和代码质量
4.原则
指创造或改进设计模式所需要遵循的原则,具体包括:
- 面向对象三大特征
- 面向抽象原则
- 常用的面向对象7个设计原则
4.1面向对象三大特征
**封装:**隐藏内部实现,保护内部信息,提供一种公共的访问方式
**继承:**实现复用,归纳共性
**多态:**对象在不同的时刻可以表现出不同的状态
问题:是不是在设计程序时类的结构中使用到了封装、继承、多态就是面向对象?
4.2面向抽象原则
- 设计一个类时,不让该类面向具体的类,而是面向抽象类或接口 。
- 一个类只能继承一个父类但是可以实现多个接口。
- 如果一个类含有抽象方法,则称这个类为抽象类,抽象类必须在类前用abstract关键字修饰。
- 接口,英文称作interface,在软件工程中,接口泛指供别人调用的方法或者函数
4.2.1抽象类:
- 抽象类中含有无具体实现的方法,必须根据子类的实际需求来进行不同的实现。
- 不能用抽象类创建对象
- 包含抽象方法的类称为抽象类,但并不意味着抽象类中只能有抽象方法,它和普通类一样,同样可以拥有成员变量和普通的成员方法
- 抽象方法必须为public或者protected(因为如果为private,则不能被子类继承,子类便无法实现该方法),缺省情况下默认为public。
- 如果一个类继承于一个抽象类,则子类必须实现父类的抽象方法。如果子类没有实现父类的抽象方法,则必须将子类也定义为为abstract类
4.2.2接口:
- 接口中可以含有 变量和方法。
- 接口中的变量会被隐式地指定为public static final变量。
- 方法会被隐式地指定为public abstract方法且只能是public abstract方法
- 接口中所有的方法不能有具体的实现,也就是说,接口中的方法必须都是抽象方法。
- 从这里可以隐约看出接口和抽象类的区别,接口是一种极度抽象的类型,它比抽象类更加“抽象”,并且一般情况下不在接口中定义变量。
4.2.3抽象类和接口的区别
语法层面上的区别:
- 抽象类可以提供成员方法的实现细节,而接口中只能存在public abstract 方法;
- 抽象类中的成员变量可以是各种类型的,而接口中的成员变量只能是public static final类型的;
- 接口中不能含有静态代码块以及静态方法,而抽象类可以有静态代码块和静态方法;
- 一个类只能继承一个抽象类,而一个类却可以实现多个接口。
设计层面上的区别:
- 抽象类是对一种事物的抽象,即对整个类抽象(包括属性和行为),而接口是对类局部(行为)的抽象。
- 抽象类作为很多子类的父类,它是一种模板式设计。而接口是一种行为规范,它是一种辐射式设计。
4.3常用的面向对象7个设计原则
4.3.1单一职责原则 (SRP)
(1)单一职责原则定义:
- 就一个类而言,应该仅有一个引起它变化的原因
(2)单一职责原则分析:
- 一个类(大到模块,小到方法)承担的职责越多,它被复用的可能性就越小
- 当一个职责变化时,可能会影响其他职责的运作
- 将这些职责进行分离,将不同的职责封装在不同的类中
- 将不同的变化原因封装在不同的类中
- 单一职责原则是实现高内聚、低耦合的指导方针
4.3.2开闭原则(OCP)
(1)开闭原则定义 :
-
开闭原则是面向对象的可复用设计的第一块基石,是最重要的面向对象设计原则
-
开闭原则:软件实体应当对扩展开放,对修改关闭。
(2)开闭原则分析 :
- 开闭原则由Bertrand Meyer于1988年提出
- 对扩展开放:有新的需求或变化时,可以对现有代码进行扩展,以适应新情况。
- 对修改关闭:类一旦设计完成,就可以独立完成自己的工作,而不要再对类进行任何修改
- 开闭原则是指软件实体应尽量在不修改原有代码的情况下进行扩展
- 抽象化是开闭原则的关键
- 相对稳定的抽象层 + 灵活的具体层
4.3.3里氏代换原则
(1)里氏代换原则定义 :
- 里氏代换原则:所有引用基类的地方必须能透明地使用其子类的对象。
(2)里氏代换原则分析:
- 里氏代换原则由2008年图灵奖得主、美国第一位计算机科学女博士、麻省理工学院教授Barbara Liskov和卡内基.梅隆大学Jeannette Wing教授于1994年提出
- 在软件中将一个基类对象替换成它的子类对象,程序将不会产生任何错误和异常,反过来则不成立。如果一个软件实体使用的是一个子类对象的话,那么它不一定能够使用基类对象
- 在程序中尽量使用基类类型来对对象进行定义,而在运行时再确定其子类类型
4.3.4依赖倒转原则
(1)依赖倒转原则定义:
-
依赖倒转原则:高层模块不应该依赖低层模块,它们都应该依赖抽象。抽象不应该依赖于细节,细节应该依赖于抽象。
-
要针对接口编程,不要针对实现编程
(2)依赖倒转原则分析:
针对抽象层编程,将具体类的对象通过依赖注入(Dependency Injection, DI)的方式注入到其他对象
-
构造注入
-
设值注入(Setter注入)
-
接口注入
4.3.5接口隔离原则
(1)接口隔离原则定义:
- 接口隔离原则:客户端不应该依赖那些它不需要的接口。
(2)接口隔离原则分析:
- 当一个接口太大时,需要将它分割成一些更细小的接口
- 使用该接口的客户端仅需知道与之相关的方法即可
- 每一个接口应该承担一种相对独立的角色,不干不该干的事,该干的事都要干
- “接口”定义(1):一个类型所提供的所有方法特征的集合。一个接口代表一个角色,每个角色都有它特定的一个接口,“角色隔离原则”
- “接口”定义(2):狭义的特定语言的接口。接口仅仅提供客户端需要的行为,客户端不需要的行为则隐藏起来,应当为客户端提供尽可能小的单独的接口,而不要提供大的总接口,每个接口中只包含一个客户端所需的方法,“定制服务”
4.3.6合成复用原则
(1)合成复用原则定义:
-
合成复用原则又称为组合/聚合复用原则
-
合成复用原则:优先使用对象组合,而不是继承来达到复用的目的。
(2)合成复用原则分析 :
- 合成复用原则就是在一个新的对象里通过关联关系(包括组合关系和聚合关系)来使用一些已有的对象,使之成为新对象的一部分
- 新对象通过委派调用已有对象的方法达到复用功能的目的
- 复用时要尽量使用组合/聚合关系(关联关系),少用继承
- 继承复用:实现简单,易于扩展。破坏系统的封装性;从基类继承而来的实现是静态的,不可能在运行时发生改变,没有足够的灵活性;只能在有限的环境中使用。(“白箱”复用 )
- 组合/聚合复用:耦合度相对较低,有选择性地调用成员对象的操作;可以在运行时动态进行,新对象可以动态地引用与成员对象类型相同的其他对象。(“黑箱”复用 )
4.3.7迪米特法则
(1)迪米特法则定义:
-
迪米特法则又称为最少知识原则(Least Knowledge Principle, LKP)
-
迪米特法则:每一个软件单位对其他的单位都只有最少的知识,而且局限于那些与本单位密切相关的软件单位。
5.分类
根据目的(模式是用来做什么的)可分为创建型(Creational),结构型(Structural)和行为型(Behavioral)三类:
- 创建型模式主要用于创建对象
- 结构型模式主要用于处理类或对象的组合
- 行为型模式主要用于描述类或对象如何交互和怎样分配职责
根据范围,即模式主要是处理类之间的关系还是处理对象之间的关系,可分为类模式和对象模式两种:
- 类模式处理类和子类之间的关系,这些关系通过继承建立,在编译时刻就被确定下来,是一种静态关系
- 对象模式处理对象间的关系,这些关系在运行时变化,更具动态性
6.类图关系
6.1依赖关系
(1)定义:依赖(Dependency)关系是一种使用关系,它是对象之间耦合度最弱的一种关联方式,是临时性的关联。在代码中,某个类的方法通过局部变量、方法的参数或者对静态方法的调用来访问另一个类(被依赖类)中的某些方法来完成一些职责。
(2)理解:依赖关系其实非常广泛,只要在类中用到了对方,那么他们之间就存在依赖关系,如果没有对方,连编译都通过不了
(3)代码:
public class PersonServiceBean{
public PersonDap personDao;
public void save(Person person){};
public IDcard getIDcard(int personid){};
public void modify(){
Department department=new Department();
}
}
public class PersonDao{}
public class Person{}
public class IDcard{}
public class Department{}
(4)在 UML 类图中,依赖关系使用带箭头的虚线来表示,箭头从使用类指向被依赖的类。
6.2关联关系
(1)定义:关联(Association)关系是对象之间的一种引用关系,用于表示一类对象与另一类对象之间的联系,如老师和学生、师傅和徒弟、丈夫和妻子等。关联关系是类与类之间最常用的一种关系,分为一般关联关系、聚合关系和组合关系。我们先介绍一般关联。关联可以是双向的,也可以是单向的。
(2)理解:关联关系实际上是类与类之间的联系,是依赖关系的特例;关联具有导航性,单向或双向的关系;具有多重性;
(3)代码:
//单项关系
public class Person{
private IDcard idcard;
}
public class IDcard{}
//双向关系
public class Person{
private IDcard idcard;
}
public class IDcard{
private Person person;
}
(4)在 UML 类图中,双向的关联可以用带两个箭头或者没有箭头的实线来表示,单向的关联用带一个箭头的实线来表示,箭头从使用类指向被关联的类。也可以在关联线的两端标注角色名,代表两种不同的角色。
6.3聚合关系
(1)定义:聚合(Aggregation)关系是关联关系的一种,是强关联关系,是整体和部分之间的关系,是 has-a 的关系。聚合关系也是通过成员对象来实现的,其中成员对象是整体对象的一部分,但是成员对象可以脱离整体对象而独立存在。例如,学校与老师的关系,学校包含老师,但如果学校停办了,老师依然存在。
(2)理解:聚合关系表示的是整体和部分的关系,两者可以分开,是关联关系的特例,所以具有关联的导航性和多重性
(3)代码:
public class Compute{
private Mouse mouse; //可分离
private Monitor monitor; //可分离
public void setMouse(Mouse mouse){
this.mouse=mouse;
}
public void setMonitor(Monitor monitor){
this.monitor=monitor;
}
}
(4)在 UML 类图中,聚合关系可以用带空心菱形的实线来表示,菱形指向整体。
6.4组合关系
(1)定义:组合(Composition)关系也是关联关系的一种,也表示类之间的整体与部分的关系,但它是一种更强烈的聚合关系,是 cxmtains-a 关系。在组合关系中,整体对象可以控制部分对象的生命周期,一旦整体对象不存在,部分对象也将不存在,部分对象不能脱离整体对象而存在。例如,头和嘴的关系,没有了头,嘴也就不存在了。
(2)理解:组合关系是整体和部分的关系,两者之间不可分开
(3)代码:
public class Person{
private IDcard card;
private Head head=new Head();
}
public class IDcard{}
public class Head{}
(4)在 UML 类图中,组合关系用带实心菱形的实线来表示,菱形指向整体。
6.5泛化关系
(1)定义:泛化(Generalization)关系是对象之间耦合度最大的一种关系,表示一般与特殊的关系,是父类与子类之间的关系,是一种继承关系,是 is-a 的关系。
(2)理解:泛化关系实际上就是继承关系,是依赖关系的特例
(3)代码:
public abstract class Animal{
public void run(){};
public void eat()();
}
public class Cat extends Animal{
}
(4)在 UML 类图中,泛化关系用带空心三角箭头的实线来表示,箭头从子类指向父类。在代码实现时,使用面向对象的继承机制来实现泛化关系。
6.6实现关系
(1)定义:实现(Realization)关系是接口与实现类之间的关系。在这种关系中,类实现了接口,类中的操作实现了接口中所声明的所有的抽象操作。
(2)理解:实现关系实际上就是A类实现了B类方法,是依赖关系的特例
(3)代码:
public interface Animal{
public void eat();
}
public class Cat implements Animal{
public void eat(){};
}
(4)在 UML 类图中,实现关系使用带空心三角箭头的虚线来表示,箭头从实现类指向接口。