一.设计模式的概述
模式是在特定环境下人们解决某类重复出现问题的一套成功或有效的解决方案。
软件模式:在一定条件下的软件开发问题及其解法。
目的:为了可重用代码、让代码更容易被他人理解、提高代码可靠性(设计复用)
设计模式的基本要素:模式名称、问题、目的、解决方案、效果。实例代码和相关的设计模式。
4个关键要素:
- 模式名称(Pattern Name) :抽象描述一个设计
- 问题(Problem) :描述了庒用场景,可以解决哪些问题
- 解决方案(Solution) :设计的细节,给出原理图示(参不者、
类图、时序图、程序示例等) - 效果 (Consequences) :优缺点。面向软件的质量属性,如可
扩展性、可复用性
设计模式的分类:
1.根据目的(模式是用来做什么的)可分为
• 创建型模式主要用于创建对象
• 结构型模式主要用于处理类或对象的组合
• 行为型模式主要用于描述类或对象如何交互和怎样分配职责
2.根据范围(即模式主要是处理类之间的关系还是处理对象之间的
关系),可分为
类模式:处理类和子类之间的关系 继承 静态
对象模式:处理对象间的关系 关联 动态性
GoF设计模式简介(23种)
二、面向对象设计原则
面向对象编程优点:
可维护(Maintainability)
可复用(Reusability)
可扩展(Extensibility)
灵活性(Flexibility)
面向对象的三大特性:
封装
继承
多态
OOP编程七大原则:
2.1单一职责原则
——职责要单一(仅有一个引起它变化的原因)
单一职责原则好处:
类的复杂性降低,实现什么职责都有清晰明确的定义;
可读性提高;
可维护性提高;变更引起的风险降低;
单一职责适用于接口设计、类的设计、方法的设计
2.2开闭原则
——总原则 ,对扩展开放,对修改关闭
开闭原则的关键:抽象化
设计模式优缺点评价的重要依据,判断基于该模式设计的系统是否具有良好的灵活性和可扩展性。
2.3里氏代换原则
——继承 (所有引用基类的地方必须能透明地使用其子类的对象)
*****正方形/长方形问题不符合里氏替换原则
改进:构造一个抽象的四边形类
2.4依赖倒转原则
—— 针对抽象编程(抽象不应该依赖于细节,细节应该依赖于抽象)
针对抽象层编程,将具体类的对象通过依赖注入(Dependency Injection,
DI)的方式注入到其他对象:
构造注入
设值注入(Setter注入)
接口注入(通过参数传入)
关系:
开闭原则是目标,里氏代换原则是基础,依赖倒转原则是途径,相辅相成。
2.5 接口隔离原则
——接口细化
与单一职责角度不同:
单一职责要求类和接口职责单一,注重业务逻辑划分的职责
接口隔离要求接口的方法尽量少
接口隔离原则是对接口进行规范:4层含义
- 接口要尽量小,不臃肿。
- 接口要高内聚。
- 定制服务。
- 接口的设计是有限度的。
2.6 合成复用原则
——复用时要尽量使用组合/聚合关系(关联关系,少用继承
合成/聚合复用的优点:
该复用支持封装;
该复用所需的依赖较少;
每个新的任务可将焦点集中在一个任务上。
合成/聚合复用的缺点:
通过这种复用建造的系统会有较多的对象需要管理;
为了能将多个不同的对象作为组合块来使用,必须仔细地对接口迚行定义
2.7迪米特法则
——尽可能少地与其他实体发生相互作用
对于一个对象,其朋友包含:
- 当前对象本身;
- 以参数形式传入到当前对象方法中的对象;
- 当前对象的成员对象;
- 如果当前对象的成员对象是一个集合,集合中的元素也是朋友;
- 当前对象所创建的对象。
迪米特法则根本思想强调类之间的松耦合
三、 简单工厂模式
——非23种模式之一 又称静态工厂方法,属于类创建型模式
3.1模式动机与定义
动机
- 只需要知道参数的名字则可得到相应的对象
- 专门定义一个类(工厂)负责创建这些类的实例。
定义
专门定义一个类来负责创建其他类的实例,可以根据参数的不同返回不同类的实例,被创建的实例通常都具有共同的父类。
3.2模式结构与分析
模式结构
核心类是工厂类
模式实例
某电视机厂与为各知名电视机品牌代工生产各类电视机,
当需要海尔牌电视机时只需要在调用该工厂的工厂方法
时传入参数“Haier”,需要海信电视机时只需要传入参
数“Hisense”,工厂可以根据传入的丌同参数返回丌同
品牌的电视机。现使用简单工厂模式来模拟该电视机工
厂的生产过程。
通过分析得到类图
工厂类代码:
public class TVFactory
{
public static TV produceTV(String brand) throws Exception
{
if(brand.equalsIgnoreCase("Haier"))
{
System.out.println("电视机工厂生产海尔电视机!");
return new HaierTV();
}
else if(brand.equalsIgnoreCase("Hisense"))
{
System.out.println("电视机工厂生产海信电视机!");
return new HisenseTV();
}
else
{ throw new Exception("对不起,暂不能生产该品牌电视机!");
} } }
3.3模式效果与应用
- 简单工厂模式优点:
- 对象创建和使用的分离
- 客户端无须知道所创建的具体产品类的类名,只需要知道具体产
品类所对应的参数即可 - 通过引入配置文件,可以在丌修改任何客户端代码的情况下更换
和增加新的具体产品类,在一定程度上提高了系统的灵活性
- 缺点:
- 工厂类职责过重;
- 增加系统中类的个数
- 系统扩展困难,一旦添加新产品不得不修改工厂逻辑 (违背开闭原则)
- 由于使用了静态工厂方法,造成工厂角色无法形成基于继承的等
级结构,工厂类丌能得到很好地扩展
- 适用场景
- 工厂类负责创建的对象比较少,由于创建对象比较少,不会造成
工厂方法中的逻辑太过复杂。 - 客户端只知道传入工厂类的参数,对于如何创建对象不关心:客
户端既不需要关心创建细节,甚至于连类名都不需要记住,只需
要知道类型所对应的参数即可。
注:
Java语言创建对象的几种方式
– 使用new关键字直接创建对象
– 通过工厂类创建对象
– 通过反射机制创建对象
– 通过克隆方法创建对象
四、工厂模式
——又称为虚拟构造器(Virtual Constructor)模式或者多态工厂(Polymorphic
Factory)模式,它属于类创建型模式
4.1模式动机与定义
动机
解决简单工厂模式的不足,工厂类职责过重,不利于扩展的缺点。
定义
工厂父类负责定义创建产品对象的公共接口, 而工厂子类则负责生成具体的产品对象。
目的:
是将产品类的实例化操作延迟到工厂子类中完成,即通过工厂子类来确定
究竟应该实例化哪一个具体产品类。
4.2模式结构与分析
模式结构
核心类是抽象工厂类
模式分析
- 工厂方法模式是简单工厂模式的迚一步抽象和推广
- 工厂方法模式保持了简单工厂模式的优点,并克服了它的缺点
- 核心的工厂类不再负责所有产品的创建,而是将具体创建工作交 给其子类去完成
- 可以允许系统在丌修改工厂角色的情况下引迚新产品
- 增加具体产品–>增加具体工厂,符合“开闭原则”
4.3模式效果与应用
- 优点:
1.向客户隐藏了哪种具体产品类将被实例化这一细节
2.让工厂自主确定创建何种产品对象
3.加入新产品时,完全符合开闭原则 - 缺点:
1.类的个数将成对增加
2.增加了系统的抽象性和理解难度 - 适用场景:
1.客户端不知道它所需要的对象的类
2.抽象工厂类通过其子类来指定创建哪个对象
注:简单工厂模式和工厂方法模式的对比
相同:返回类型都是超类类型|
项目 | 简单工厂模式 | 工厂方法 |
---|---|---|
中心不同 | 工厂类 | 抽象工厂类 |
工厂方法不同 | 静态 | 动态 |
开闭原则 | 不支持 | 支持 |
五、抽象工厂模式
————应用广泛,又称为Kit模式,属于对象创建型模式。只生产一种产
品的抽象工厂模式,而抽象工厂模式可以看成是工厂方法模式的一种推广
5.1 模式动机与定义
动机
需要一个工厂可以生产多个产品对象。
产品等级结构:产品等级结构即产品的继承结构
产品族:在抽象工厂模式中,产品族是指由同一个工厂生产的,
位于不同产品等级结构中的一组产品。
定义
提供一个创建一系列相关或相互依赖对象的接口,而无须指定它们具体的类。
5.2模式结构与分析
模式结构
模式实例
计算机包含内存(RAM),CPU等硬件设备,根据如图所示的“产品等级结构-产品族示意图”,使用抽象工厂模式实现计算机设备创建过程并绘制类图。
根据题意,明白相应的产品族与产品等级结构,建立相应的抽象类,同一产品族的实现共同工厂类,同一产品等级结构继承共同的产品类。画出相应的类图,写出相应的代码
代码:
AbstractFactory类
package abstractFactory;
public interface AbstractFactory {
public CPU produceCPU();
public RAM produceRAM();
}
MacFactory类
package abstractFactory;
public class MacFactory implements AbstractFactory {
@Override
public CPU produceCPU() {
return new MacCPU();
}
@Override
. public RAM produceRAM() {
return new MacRAM();
}
}
PcFactory类
package abstractFactory;
public class PcFactory implements AbstractFactory {
@Override
public CPU produceCPU() {
return new PcCPU();
}
@Override
public RAM produceRAM() {
return new PcRAM();
}
}
CPU类
package abstractFactory;
public interface CPU {
public void use();
}
RAM类
package abstractFactory;
public interface RAM {
public void store();
}
PcRAM类
package abstractFactory;
public class PcRAM implements RAM {
@Override
public void store() {
System.out.println("正在使用PcRAM储存");
}
}
MacRAM类
package abstractFactory;
public class MacRAM implements RAM {
@Override
public void store() {
System.out.println("正在使用MacRAM储存");
7. }
8.}
PcCPU类
package abstractFactory;
public class PcCPU implements CPU {
@Override
public void use() {
System.out.println("正在使用PcCPU");
}
}
MacCPU类
package abstractFactory;
public class MacCPU implements CPU {
@Override
public void use() {
System.out.println("正在使用MacCPU");
}
}
Client类
package abstractFactory;
public class Client
{
public static void main(String args[])
{
try
{
AbstractFactory factory;
CPU cpu;
RAM ram;
factory=(AbstractFactory) XMLUtil.getBean();
cpu=factory.produceCPU();
cpu.use();
ram=factory.produceRAM();
ram.store();
}
catch(Exception e)
{
System.out.println(e.getMessage());
}
}
}
编写相应的XML文件与工具类
5.3模式应用与效果
- 优点
- 隔离了具体类的生成
- 能够保证客户端始终只使用同一个产品族中的对象
- 增加新的产品族很方便,无须修改已有系统,符合开闭原则
- 缺点
增加新的产品等级结构麻烦,需要对原有系统进行较大的修改,
甚至需要修改抽象层代码,这显然会带来较大的不便,违背了开
闭原则 - 适用场景
- 一个系统不应当依赖于产品类实例如何被创建、组合和表达的细
节。 - 系统中有多于一个的产品族,但每次只使用其中某一产品族。
- 属于同一个产品族的产品将在一起使用,这一约束必须在系统的
设计中体现出来。 - 产品等级结构稳定,在设计完成之后不会向系统中增加新的产品
等级结构或者删除已有的产品等级结构。
5.4三种工厂模式的比较
相同:
三个设计模式名字中都含有“工厂”二字,其含义是使用工厂(一
个或一系列方法)去生产产品(一个或一系列类的实例)。
不同
项目 | 简单工厂 | 工厂模式 | 抽象工厂模式 |
---|---|---|---|
工厂类的实现 | 拥有一个工厂方法(create),接受了一个参数,通过不同的参数实例化不同的产品类 | 工厂方法针对每一种产品提供一个工厂类。通过不同的工厂实例来创建不同的产品实例 | 抽象工厂对应的是产品族概念。 |
优点 | 很明显,简单工厂的特点就是“简单粗暴”,通过一个含参的工厂方法,我们可以实例化任何产品类,上至飞机火箭,下至土豆面条,无所不能。所以简单工厂有一个别名:上帝类。 | 1.工厂方法模式就很好的减轻了工厂类的负担,把某一类/某一种东西交由一个工厂生产;(对应简单工厂的缺点1) 2.同时增加某一类”东西“并不需要修改工厂类,只需要添加生产这类”东西“的工厂即可,使得工厂类符合开放-封闭原则。 | 针对产品族场景,实现比较工整 |
缺点 | 1任何“东西”的子类都可以被生产,负担太重。当所要生产产品种类非常多时,工厂方法的代码量可能会很庞大。2.在遵循开闭原则(对拓展开放,对修改关闭)的条件下,简单工厂对于增加新的产品,无能为力。因为增加新产品只能通过修改工厂方法来实现。 | 1.相比简单工厂,实现略复杂。2.对于某些可以形成产品族的情况处理比较复杂 | 比较复杂,只适用于产品族场景 |
适用的场景 | 用于生产同一等级结构中的任意产品(不支持拓展增加产品) | 用于生产同一等级结构中的固定产品(支持拓展增加产品) | 用于生产不同产品族的全部产品(不支持拓展增加产品;支持增加产品族) |
六、建造者模式
——最复杂的创建型模式,对象创建型模式
6.1模式动机与定义
动机
创建一个包含多个组成部分的复杂对象,可以返回一个完整的
产品对象给用户。它通过将客户端与包含多个组成部分的复杂对
象的创建过程分离,使得客户端无需知道复杂对象的内部组成部
分与装配方式,只需要知道建造者的类型即可
定义
将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
一步一步创建一个复杂的对象,它允许用户只通过指定复杂对象的类型和内容就可以构建它们,用户不需要知道内部的具体构建细节。
6.2模式结构与分析
模式结构
模式实例
计算机组装工厂可以将CPU,内存,硬盘,主机,显示器等硬件设备组装在一起构成一台完整的计算机,且构成的计算机可以是笔记本电脑,也可以是台式机,还可以是不提供显示器的服务器主机。对于用户来言,无需关心计算机的组成设备和组装过程,工厂返回给用户的是完整的计算机对象。所以我们可以使用建造者模式来实现计算机的组成过程,请绘制出类图并编程实现。
实现步骤:
- 根据题意,使用建造者模式并画出类图。类图中应包含抽象建造者类ComputerBuilder,复合产品类Computer,具体建造者类Notebook,Desktop和Server,其中台式机和服务器主机使用相同的CPU,内存,硬盘和主机,但是服务器不包含显示器,而笔记本使用自己独自的一套硬件设备。此外还需要指挥者类ComputerAssembleDirector,此类中应有将硬件设备组合在一起的建造方法assemble()并返回用户需要的具体计算机
- 根据类图,实现上述类的具体代码以及用户类Client和辅助类XMLUtil以实现通过XML文件来制造不同的计算机
- 更改XML中的属性,观察用户类是否能够获取到不同的计算机以及这些计算机的组装是否符合要求
类图:
代码:
Computer类:
package builder;
public class Computer {
private String CPU;
private String host;
public String getCPU() {
return CPU;
}
public void setCPU(String CPU) {
this.CPU = CPU;
}
public String getHost() {
return host;
}
public void setHost(String host) {
this.host = host;
}
}
ComputerBuilder类:
package builder;
public abstract class ComputerBuilder {
protected Computer computer=new Computer();
public abstract void buildCPU();
public abstract void buildHost();
public Computer getComputer(){
return computer;
}
}
Notebook类:
package builder;
public class Notebook extends ComputerBuilder {
@Override
public void buildCPU() {
computer.setCPU("设置Notebook的CPU");
}
@Override
public void buildHost() {
computer.setHost("设置Notebook的主机");
}
}
Desktop类:
package builder;
public class Desktop extends ComputerBuilder {
@Override
public void buildCPU() {
computer.setCPU("设置Desktop的CPU");
}
@Override
public void buildHost() {
computer.setHost("设置Desktop的主机");
}
}
Server类:
package builder;
public class Server extends ComputerBuilder {
@Override
public void buildCPU() {
computer.setCPU("设置Server的CPU");
}
@Override
public void buildHost() {
computer.setHost("设置Server的主机");
}
}
ComputerAssembleDirector类
package builder;
public class ComputerAssembleDirector {
private ComputerBuilder cb;
public void setComputerBuilder(ComputerBuilder cb){
this.cb=cb;
}
public Computer assemble(){
cb.buildCPU();
cb.buildHost();
return cb.getComputer();
}
}
Client类
package builder;
public class Client
{
public static void main(String args[])
{
ComputerBuilder cb=(ComputerBuilder) XMLUtil.getBean();
ComputerAssembleDirector director=new ComputerAssembleDirector();
director.setComputerBuilder(cb);
Computer computer=director.assemble();
System.out.println("查看电脑构成:");
System.out.println(computer.getCPU());
System.out.println(computer.getHost());
}
}
6.3模式应用与效果
- 优点:
- 客户端不必知道产品内部组成的细节,将产品本身与产品的创建
过程解耦,使得相同的创建过程可以创建不同的产品对象 - 每一个具体建造者都相对独立,与其他的具体建造者无关,因此
可以很方便地替换具体建造者或增加新的具体建造者,扩展方便,
符合开闭原则 - 可以更加精细地控制产品的创建过程
- 缺点
建造者模式所创建的产品一般具有较多的共同点,其组成部分相似,如果产品之间的差异性很大,不适合使用建造者模式,因此其==使用范围受到一定的限制 ==
如果产品的内部变化复杂,可能会需要定义很多具体建造者类来
实现这种变化,导致系统变得很庞大,增加了系统的理解难度和运行成本
- 适用场景
需要生成的产品对象有复杂的内部结构,这些产品对象通常包含
多个成员变量
需要生成的产品对象的属性相互依赖,需要指定其生成顺序
对象的创建过程独立于创建该对象的类。在建造者模式中通过引
入了指挥者类,将创建过程封装在指挥者类中,而不在建造者类和客户类中
隔离复杂对象的创建和使用,并使得相同的创建过程可以创建不
同的产品
6.4模式扩展
简化:
- 省略抽象建造者角色-(系统中只需要1个建造者时)
- 省略指挥者角色-(系统中只需要1个建造者时,省略抽象建造者、
还可继续省略指挥者,让Builder角色扮演指挥者与建造者双重角色)
七、原型模式
——克隆 ,对象创建型模式
7.1 模式动机与定义
动机
当需要创建大量相同或者相似对象时,可以通过对一个已有对象的复制获取更多对象。
定义
用原型实例指定创建对象的种类,并且通过复制这些原型创建新
的对象。
原型模式允许通过一个原型对象创建一个或多个同类型的其他对
象,而无须知道任何创建的细节。
工作原理:将一个原型对象传给要发动创建的对象(即客户端对象),
这个要发动创建的对象通过请求原型对象复制自己来实现创建过程
创建新对象(也称为克隆对象)的工厂就是原型类自身,工厂方法由
负责复制原型对象的克隆方法来实现
通过克隆方法所创建的对象是全新的对象,它们在内存中拥有新的地
址,每一个克隆对象都是独立的
通过不同的方式对克隆对象进行修改以后,可以得到一系列相似但不
完全相同的对象
7.2模式结构与分析
模式结构
- 在Java中可以直接使用Object提供的clone()方法来实现对象的克
隆(浅克隆(Shallow Clone):当原型对象被复制时,只复制它本身和
其中包含的值类型的成员变量,而引用类型的成员变量并没有复制)
- 深克隆(Deep Clone):除了对象本身被复制外,对象所包含的所有
成员变量也将被复制(可以通过序列化等方式来实现,其类必须实现Serializable接口)
- 能够实现克隆的Java类必须实现一个标识接口Cloneable,表示
这个Java类支持复制
模式分析
Java语言提供的clone()方法将对象复制了1份并返回给调用者,一般而言,clone()方法满足以下几点。
(1)对任何的对象x,都有x.clone()!=x,即克隆对象与原对象不是同一个对象。
(2)对任何的对象x,都有x.clone().getClass()==x.getClass(), 即克隆对象与原对象的类型一样。
(3)如果对象x的equals()方法定义恰当,那么x.clone().equals(x)应该成立。
邮件复制(浅克隆):实例说明
• 由于邮件对象包含的内容较多(如发送者、接收者、标题、内容、
日期、附件等),某系统中现需要提供一个邮件复制功能,对于已
经创建好的邮件对象,可以通过复制的方式创建一个新的邮件对象,
如果需要改变某部分内容,无须修改原始的邮件对象,只需要修改
复制后得到的邮件对象即可。使用原型模式设计该系统。
• 在本实例中使用浅克隆实现邮件复制,即复制邮件(Email)的同时
不复制附件(Attachment)。
类图
7.3模式效果与应用
- 优点
1.简化对象的创建过程,通过复制一个已有实例可以提高新实例的
创建效率
2.扩展性较好
3.简化创建结构,原型模式中产品的复制是通过封装在原型类中的
克隆方法实现的,无须专门的工厂类来创建产品
4.可以使用深克隆的方式保存对象的状态,以便在需要的时候使用,
可辅助实现撤销操作 - 缺点
1.需要为每一个类配备一个克隆方法,而且该克隆方法位于一个类
的内部,当对已有的类进行改造时,需要修改源代码,违背了开
闭原则。
2.在实现深克隆时需要编写较为复杂的代码,而且当对象之间存在
多重的嵌套引用时,为了实现深克隆,每一层对象对应的类都必
须支持深克隆,实现起来可能会比较麻烦。 - 适用场景
1.创建新对象成本较大,新对象可以通过复制已有对象来获得,如
果是相似对象,则可以对其成员变量稍作修改
2.系统要保存对象的状态,而对象的状态变化很小
3.需要避免使用分层次的工厂类来创建分层次的对象
7.4模式扩展
原型管理器(Prototype Manager)将多个原型对象存储在一个集合中供客户端使用,它是一个专门负责克隆对象的工厂,其中定义了一个集合用于存储原型对象,如果需要某个原型对象的一个克隆,可以通过复制集合中对应的原型对象来获得。
原型管理器中针对抽象类型编程,以便扩展。
public class PrototypeManager {
private Hashtable prototypeTable=new Hashtable();
//Hashtable存储原型对象
public PrototypeManager() {
prototypeTable.put("A", new ConcretePrototypeA());
prototypeTable.put("B", new ConcretePrototypeB());
}
public void add(String key, Prototype prototype) {
prototypeTable.put(key,prototype);
}
public Prototype get(String key) {
Prototype clone = null;
clone = ((Prototype)prototypeTable.get(key)).clone(); //克隆方法创建新对象
return clone;
} }
八、单例模式
——结构最简单的设计模式,核心结构中只包含一个被称为单例类的特殊类,对象创建型模式。
8.1模式动机与定义
动机
如何确保一个类叧有一个实例并且这个实例易于被访问?
定义一个全局变量可以确保对象随时都可以被访问,但是不能防止实例化多个对象。(代码实现)
更好的办法是让类自身负责创建和保存它的唯一实例,并保证不能创建其他实例,并且提供一个访问该实例的方法。(机制实现)
定义
确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例,这个类称为单例类,它提供全局访问的方法。
单例模式的要点有三个:
• 某个类只能有一个实例
• 必须自行创建这个实例
必须自行向整个系统提供这个实例
8.2模式结构与分析
模式结构
模式分析
单例类
public class Singleton {
//静态私有成员变量
private static Singleton instance=null;
//私有构造函数
private Singleton() { }
//静态公有工厂方法,返回唯一实例
public static Singleton getInstance() {
if(instance==null)
instance=new Singleton();
return instance;
} }
客户类
public class Client
{
public static void main(String a[])
{
Singleton s1=Singleton.getInstance();
Singleton s2=Singleton.getInstance();
System.out.println(s1==s2);
} }
客户端程序如何在不使用构造函数的情况下创建类的对象?
设计一个getInstance()方法,它将在被调用时创建并返回一个实例。
getInstance()方法必须是一个静态方法,否则客户端程序无法调用
它
不管调用getInstance()方法多少次,调用者都将得到相同的对象
案例:
在实际的运用中,我们有时一个类不止需要产生一个对象,可能需要两个或者三个。在课上我们讲过,使用单例模式的思想可以实现多例模式,从而确保系统中某个类的对象只能存在有限个,请设计并实现代码,从而实现多例模式。
类图
Multiton类:
package singleton;
import java.util.Date;
import java.util.Random;
public class Multiton {
// 定义一个数组用于存储四个实例
private static Multiton[] array={new Multiton(),new Multiton(),new Multiton(),new Multiton()};
// 私有构造函数
private Multiton(){
}
// 静态工厂方法,随机返回数组中的一个实例
public static Multiton getInstance(){
return array[random()];
}
Date d=new Date();
Random random=new Random();
int value=Math.abs(random.nextInt());
value=value%4;
return value;
}
public static void main(String[] args) {
Multiton m1,m2,m3,m4;
m1=Multiton.getInstance();
m2=Multiton.getInstance();
m3=Multiton.getInstance();
m4=Multiton.getInstance();
System.out.println(m1==m2);
System.out.println(m1==m3);
System.out.println(m1==m4);
}
}
8.3模式效果与应用
- 优点
1.提供了对唯一实例的受控访问
2.可以节约系统资源,提高系统的性能
3.允许可发数目的实例(多例类) - 缺点
1.扩展困难(缺少抽象层)
2.单例类的职责过重。单例模式不单一职责原则有冲突。一个类应
该只实现一个逻辑,而不关心它是否是单例的,是不是要单例取决于环境,单例模式把“要单例”和业务逻辑融合在一个类中
3.由于自动垃圾回收机制,可能会导致共享的单例对象的状态丢失 - 适应场景
1.系统只需要一个实例对象,或者因为资源消耗太大而叧允许创建
一个对象
2.客户调用类的单个实例只允许使用一个公共访问点,除了该公共
访问点,不能通过其他途径访问该实例。
8.4模式扩展
单例模式的分类:
懒汉式:该模式叧在你需要对象时才会生成单例对象(比如调用getInstance方法)
public class LazySingleton {
private static LazySingleton instance = null;
private LazySingleton() { }
public static LazySingleton getInstance() {
if (instance == null) { //s1
instance = new LazySingleton(); //s2
}
return instance; //s3 } }
多线程同时访问:
1.锁方法:
public class LazySingleton {
private static LazySingleton instance = null;
private LazySingleton() { }
//加锁
synchronized public static LazySingleton getInstance() {
if (instance == null) {
instance = new LazySingleton();
}
return instance; } }
2.锁代码段:
……
public static LazySingleton getInstance() {
if (instance == null) {
synchronized (LazySingleton.class) {
instance = new LazySingleton();
}
}
return instance; }
……
3.Double-Check Locking双重检查锁定
public class LazySingleton {
private volatile static LazySingleton instance = null;
private LazySingleton() { }
public static LazySingleton getInstance() {
//第一重判断
if (instance == null) {
//锁定代码块
synchronized (LazySingleton.class) {
//第二重判断
if (instance == null) {
instance = new LazySingleton(); //创建单例实例
} } }
return instance;
} }
volatile 修饰的变量确保了线程不会将该发量拷贝到自己的工作线程中,所有线程对该发量的操作都是在主存中进行的,所以 volatile 修饰的发量对所有线程可见。
public class Singleton {
private Singleton() { }
private static class HolderClass {
private final static Singleton instance = new Singleton();
}
public static Singleton getInstance() {
return HolderClass.instance;
}
public static void main(String args[]) {
Singleton s1, s2; s1 = Singleton.getInstance();
s2 = Singleton.getInstance();
System.out.println(s1==s2);
} }
饿汉式:顾名思义,该模式在类被加载时就会实例化一个对象
饿汉式单例类(Eager Singleton)
public class EagerSingleton {
private static final EagerSingleton instance = new EagerSingleton();
private EagerSingleton() { }
public static EagerSingleton getInstance() {
return instance;
} }
类被加载时就会实例化一个对象
两种单例模式对比
九、适配器模式
——适配器的实现就是把客户类的请求转化为对适配者的相应接口的调用。又称包装器(Wrapper)模式。既可作为类结构型模式,也可以作为对象结构型模式
注结构型模式(Structural Pattern)的主要目的就是将不同的类和对象组合在一起,形成更大或者更复杂的结构体。提供它们之间的关联方式。
类结构型模式关心类的组合,存在继承关系和实现关系。
对象结构型模式关心类与对象的组合,通过关联关系在一个类中定义另一个类的实例对象,然后通过该对象调用相应的方法。根据合成复用原则,尽量使用关联代替继承。
9.1模式动机与定义
动机
解决系统中存在的不兼容问题(例如方法名不一致)。适配器模式可看做是一种对现有系统进行补救以及对现有类进行重用的模式。
包装类(适配器):包装不兼容接口的对象
它所包装的对象就是适配者(Adaptee)
适配器可以使由于接口不兼容而不能交互的类可以一起工作,这就是适配器模式的模式动机。
定义
将一个接口转换成客户希望的另一个接口,适配器模式使接口(广义接口:表示一个方法或方法的集合)不兼容的那些类可以一起工作。
9.2模式结构与分析
模式结构
模式分析
典型的类适配器示例代码:
public class Adapter extends Adaptee implements Target {
public void request() {
super.specificRequest();
} }
典型的对象适配器示例代码:
public class Adapter extends Target {
private Adaptee adaptee; //维持一个对适配者对象的引用
public Adapter(Adaptee adaptee) {
this.adaptee=adaptee;
}
public void request() {
adaptee.specificRequest(); //转发调用
} }
适配器模式使用的前提是不能或不想修改原来的适配者接口和抽象目标类接口
强调对代码的组织,而不是功能的实现。
类适配器模式违背了合成复用原则。
== 案例==:
在课堂上我们学习了单向适配器的使用和实现,现在我们需要实现一个双向适配器,编写代码,使用Java语言实现双向适配器,使猫可以学狗叫,狗可以学猫抓老鼠,请绘制相应类图并实现。(课本167页第三题)
(2)实现步骤:
根据题意,画出双向适配器的类图,类图中应该包含一个适配器类Adapter;两个抽象类Cat类和Dog类,Cat类中有发出叫声的方法cry()和捉老鼠的方法catchMouse(),Dog类中有发出狗叫声的方法wang()和动作方法action();两个具体适配者类ConcreteCat类和ConcreteDog类,两个抽象类互为抽象目标和抽象适配者,如果客户端针对Cat类编程,则Cat类充当抽象目标,Dog类充当抽象适配者,ConcreteCat类充当具体适配者,反之同理。
根据类图,实现上述类的具体代码以及用户类Client,由于本题中只有一个适配器类Adapter,所以不需要通过XML文件来改变用户类的操作
编译并运行代码,观察是否能让猫发出狗叫声和让狗实现抓老鼠的动作。
类图
代码:
Adapter
1.package adapter;
2.
3.public class Adapter implements Cat, Dog{
4. private Dog dog;
5. private Cat cat;
6.
7. public void setCat(Cat cat) {
8. this.cat = cat;
9. }
10.
11. public void setDog(Dog dog) {
12. this.dog = dog;
13. }
14.
15. @Override
16. public void cry() {
17. System.out.print("猫学狗叫,即目标类调用适配者中的方法:");
18. dog.wang();
19.
20. }
21.
22. @Override
23. public void catchMouse() {
24. System.out.println("猫在抓老鼠");
25.
26. }
27.
28. @Override
29. public void wang() {
30. System.out.println("狗在汪汪叫");
31. }
32.
33. @Override
34. public void action() {
35. System.out.print("狗学猫抓老鼠,即适配者调用目标类中的方法:");
36. cat.catchMouse();
37.
38. }
39.}
Dog
1.package adapter;
2.
3.public interface Dog {
4. public abstract void wang();
5. public abstract void action();
6.}
Cat
1.package adapter;
2.
3.public interface Cat {
4. public abstract void cry();
5. public abstract void catchMouse();
6.}
ConcreteCat
1.package adapter;
2.
3.public class ConcreteCat implements Cat{
4. @Override
5. public void cry() {
6. System.out.println("喵喵喵~~");
7. }
8.
9. @Override
10. public void catchMouse() {
11. System.out.println("抓老鼠");
12. }
13.}
ConcreteDog
1.package adapter;
2.
3.public class ConcreteDog implements Dog {
4. @Override
5. public void wang() {
6. System.out.println("汪汪汪~~");
7. }
8.
9. @Override
10. public void action() {
11. System.out.println("小狗跑");
12. }
13.}
Client
1.package adapter;
2.
3.public class Client {
4. public static void main(String[] args) {
7. Adapter adapter=new Adapter();
8. Cat concreteCat =new ConcreteCat();
9. adapter.setCat(concreteCat);
10. adapter.cry();
11. Dog concreteDog=new ConcreteDog();
12. adapter.setDog(concreteDog);
13. adapter.action();
14.
15. }
16.}
9.3模式的应用与效果
- 优点
1.目标类和适配者类解耦
2.增加了类的透明性和复用性
3.灵活性和扩展性非常好,符合开闭原则
4.类适配器模式:由于继承关系,置换一些适配者的方法很方便
5.对象适配器模式:可以把多个不同的适配者适配到同一个目标,还可以适配一个适配者的子类 - 缺点
1.类适配器模式:
• (1) 一次最多叧能适配一个适配者类,不能同时适配多个适配者
• (2) 适配者类不能为最终类(final)
• (3) 目标抽象类只能为接口,不能为类
2.对象适配器模式:在适配器中置换适配者类的某些方法比较麻烦 - 适用场景
1.需要使用一些现有的类,而这些类的接口不符合系统的需要
2.创建一个可以重复使用的类,用于和一些彼此之间没有太大关联的类,包括一些可能在将来引进的类一起工作
9.4模式扩展
1、缺省适配器模式(Default Adapter Pattern)又称为单接口适配器模式。
不需要实现一个接口所提供的所有方法时,可先设计一个抽象类实现该接口,并为接口中每个方法提供一个默认实现(空方法),那么该抽象类的子类可以选择性地覆盖父类的某些方法来实现需求。
适用于不想使用一个接口中的所有方法的情况。
缺省适配器类的典型代码片段:
public abstract class AbstractServiceClass implements ServiceInterface {
public void serviceMethod1() { } //空方法
public void serviceMethod2() { } //空方法
public void serviceMethod3() { } //空方法
}
2、双向适配器
public class Adapter implements Target,Adaptee {
private Target target;
private Adaptee adaptee;
public Adapter(Target target) {
this.target = target; }
public Adapter(Adaptee adaptee) {
this.adaptee = adaptee; }
public void request() {
adaptee.specificRequest(); }
public void specificRequest() {
target.request(); } }
十、桥接模式
----对象结构型模式
桥接模式用一种巧妙的方式处理多层继承存在的问题,用抽象关联取代了传统的多重继承,将类之间的静态继承关系转换为动态的对象
组合关系。
10.1 模式动机与定义
定义
将抽象部分不它的实现部分分离,使它们都可以独立地变化。
10.2模式结构与分析
模式结构
模式分析
桥接模式中体现了很多OOP的思想,包括开闭原则、合成复用原则、
里氏代换原则、依赖倒转原则等。
桥接模式可以从接口中分离实现功能,设计更具有扩展性。
桥接模式减少了子类个数,代码简洁。
重点理解如何将抽象化(Abstraction)和实现化(Implementation)
脱耦,使得两者可以独立变化。
典型的实现类接口代码:
public interface Implementor
{
public void operationImpl();
}
典型的具体实现类代码:
public class ConcreteImplementor implements Implementor {
public void operationImpl()
{
//具体业务方法的实现
} }
典型的抽象类代码:
public abstract class Abstraction{
protected Implementor impl;
public void setImpl(Implementor impl) {
this.impl=impl;
}
public abstract void operation();
}
典型的细化抽象类代码:
public class RefinedAbstraction extends Abstraction {
public void operation()
{
// 代码
impl.operation();
//代码
} }
10.3模式应用与效果
优点:
- 将抽象类与实现类分离
- 极大的减少了类的个数
- 提高系统的可扩展性,符合开闭原则
缺点: - 增加系统设计和理解的难度
- 正确识别出系统中两个独立变化的维度比较困难。
适用环境:
- 避勉两个层次之间建立静态继承关系
- 有两个或多个独立变化的维度,且独立扩展
- 抽象部分和实现部分可以以继承的方式独立扩展而互不影响
- 不希望使用继承或多重继承导致系统中类的个数增加
10.4模式扩展
桥接模式与适配器模式配合使用
十一、组合模式
— 对象结构型模式
组合模式用面向对象的方式来处理树形结构或部分-整体的层次模式
11.1模式动机与定义
动机
如何将容器对象和叶子对象迚行递归组吅,使得用户在使用时无须对它们进行区分,可以一致地对待容器对象和叶子对象?
组合模式通过一种巧妙的设计方案使得用户可以一致性地处理整个树形结构或者树形结构的一部分,它描述了如何将容器对象和叶子对象进行递归组合,使得用户在使用时无须对它们迚行区分,可以一致地对待容器对象和叶子对象
定义
组合多个对象形成树形结构以表示“部分-整体”的结构层次。组合模式对单个对象(即叶子对象)和组合对象(即容器对象)的使用具有一致性。
11.2模式结构与分析
模式结构
模式分析
抽象构件示例代码:
public abstract class Component {
public abstract void add(Component c); //增加成员
public abstract void remove(Component c); //删除成员
public abstract Component getChild(int i); //获取成员
public abstract void operation(); //业务方法
}
叶子构件
public class Leaf extends Component {
public void add(Component c) {
//异常处理或错误提示 }
public void remove(Component c) {
//异常处理或错误提示 }
public Component getChild(int i) {
//异常处理或错误提示
return null; }
public void operation() {
//叶子构件具体业务方法的实现 }
}
容器构件示例 代码:
public class Composite extends Component {
private ArrayList<Component> list = new ArrayList<Component>( );
public void add(Component c) {
list.add(c);
}
public void remove(Component c) {
list.remove(c); }
public Component getChild(int i) {
return (Component)list.get(i); }
public void operation() {
//容器构件具体业务方法的实现,将递归调用成员构件的业务方法
for(Object obj:list) {
((Component)obj).operation();
}
} }
案例:
在操作系统中,一个文件夹中可能存放着图像文件,视频文件,文本文件,也可能存放其他的文件夹,而对不同类型的文件进行的浏览操作也不一样,使用透明组合模式,绘制类图并编程实现文件的浏览(课本197页第二题)。
(2)实现步骤:
根据题意,画出组合模式的类图,类图中应包含抽象文件类AbstractFile,具体的图像文件类ImageFile,视频文件类VideoFile,文本文件类TextFile以及文件夹类Folder,对每个文件都有display()方法,而对文件夹可以进行add()方法和remove()方法。
根据类图,实现上述类的具体代码以及用户类Client,在用户类中需要将不同类型的文件放入文件夹中。
编译并运行程序,使程序能够输出对文件的浏览过程。
类图:
AbstractFile
1
.package composite;
2.
3.public abstract class AbstractFile {
4. public abstract void add(AbstractFile element);
5. public abstract void remove(AbstractFile element);
6. public abstract void display();
7.}
ImageFile
1.package composite;
2.
3.public class ImageFile extends AbstractFile {
4. @Override
5. public void add(AbstractFile element) {
6. System.out.println("图片文件不支持添加功能");
7. }
8.
9. @Override
10. public void remove(AbstractFile element) {
11. System.out.println("图片文件不支持删除功能");
12. }
13.
14. @Override
15. public void display() {
16. System.out.println("正在浏览图片");
17. }
18.}
VideoFile
1.package composite;
2.
3.public class VideoFile extends AbstractFile{
4. @Override
5. public void add(AbstractFile element) {
6. System.out.println("视频文件不支持添加功能");
7. }
8.
9. @Override
10. public void remove(AbstractFile element) {
11. System.out.println("视频文件不支持删除功能");
12. }
13.
14. @Override
15. public void display() {
16. System.out.println("正在观看视频");
17. }
18.}
TextFile
1.package composite;
2.
3.public class TextFile extends AbstractFile {
4. @Override
5. public void add(AbstractFile element) {
6. System.out.println("文本文件不支持添加功能");
7. }
8.
9. @Override
10. public void remove(AbstractFile element) {
11. System.out.println("文本文件不支持删除功能");
12. }
13.
14. @Override
15. public void display() {
16. System.out.println("正在浏览文本");
17. }
18.}
Folder
.package composite;
2.
3.import java.util.ArrayList;
4.
5.public class Folder extends AbstractFile {
6. private ArrayList fileList=new ArrayList();
7. private String fileName;
8. public Folder(String fileName){
9. this.fileName=fileName;
10. }
11. @Override
12. public void add(AbstractFile element) {
13. fileList.add(element);
14. }
15.
16. @Override
17. public void remove(AbstractFile element) {
18. fileList.add(element);
19. }
20.
21. @Override
22. public void display() {
23. System.out.println(">>>>>文件夹"+fileName+"正在运行");
24. for (Object obj:fileList){
25. ((AbstractFile)obj).display();
26. }
27. }
28.}
Client
1.package composite;
2.
3.public class Client {
4. public static void main(String[] args) {
5. AbstractFile file1,file2,file3,file4,file5,folder1;
6.
7. folder1=new Folder("资料");
8.
9. file1=new ImageFile();
10. file2=new TextFile();
11. file3=new VideoFile();
12. folder1.add(file1);
13. folder1.add(file2);
14. folder1.remove(file2);
15. folder1.display();
16. file3.display();
17. file1.remove(file3);
18. file1.add(file3);
19. }
20.}
11.3模式效果与应用
优点:
- 定义分层次的复杂对象,让客户端可以忽略层次差异
- 一致的使用一个组合结构或单个对象,简化了客户端的代码
- 增加新的容器和叶子构件符合开闭原则
- 为树形结构的面向对象实现提供了解决方案
缺点:
- 增加了设计难度
- 增加新的构件很难对容器中构件类型进行限制
适用环境
- 具有整体和部分的结构中客户端可以一致的对待他们
- 面向对象语言开发系统中需要处理一个树形结构
- 能够分离出叶子和容器构件,而且类型固定。
11.4模式扩展
1.更复杂的组合模式——抽象叶子结点和抽象容器结点
2.透明组合模式
3.安全组合模式
比较:
类别 | 透明组合模式 | 安全组合模式 |
---|---|---|
抽象构件方法偏向 | 容器对象 | 叶子对象 |
安全性 | 不安全 | 叶子对象 |
客户端是否一致对待(透明性) | 是 | 否 |
优点 | 确保所有的构件类都有相同的接口 | 安全、使用频率更高 |
缺点 | 不够安全,因为叶子对象和容器对象在本质上是有区别的 | 是不够透明 |
十二、装饰模式
— 对象结构型模式
装饰模式降低了系统的耦合度,可以动态增加或删除对象的职责,并使得需要装饰的具体构件类和具体装饰类可以独立变化
12.1模式动机与定义
动机
- 在不改变对象本身功能的基础上给对象增加额外行为
- 用于代替继承的技术,动态的给对象增加职责,用关联关系取代继承
- 在装饰类中既可以调用装饰类的原有类的方法还可以增加新的方法
定义
动态地给一个对象增加一些额外的职责,就增加对象功能来说,装饰模式比生成子类实现更为灵活
12.2 模式结构与分析
模式结构
模式分析
抽象构件类示例代码:
public abstract class Component {
public abstract void operation();
}
具体构件类示例代码:
public class ConcreteComponent extends Component {
public void operation() {
//实现基本功能
} }
抽象装饰类示例代码:
public class Decorator extends Component {
private Component component; //维持一个对抽象构件对象的引用
//注入一个抽象构件类型的对象
public Decorator(Component component) {
this.component=component;
}
public void operation() {
component.operation(); //调用原有业务方法
} }
具体装饰类示例代码:
public class ConcreteDecorator extends Decorator {
public ConcreteDecorator(Component component) {
super(component);
}
public void operation() {
super.operation(); //调用原有业务方法
addedBehavior(); //调用新增业务方法
}
//新增业务方法
public void addedBehavior() {
……
} }
比较:
透明装饰模式
- 客户端完全针对抽象编程,全部声明为抽象构件类型
- 可以对一个已装饰过的对象进行多次装饰
- 无法在客户端单独调用新增方法
- 客户端代码
Component component_o,component_d1,component_d2;
//全部使用抽象构件定义
component_o = new ConcreteComponent();
component_d1 = new ConcreteDecorator1(component_o);
component_d2 = new ConcreteDecorator2(component_d1);
component_d2.operation();
//无法单独调用component_d2的addedBehavior()方法
……
半透明装饰模式
- 具体装饰类型来定义装饰之后的对象,而具体构件使用抽象构件类型来定义
- 具体构件类型无须关心,是透明的;但是具体装饰
类型必须指定,这是不透明的 - 可以单独调用新增的方法
- 不能实现对同一个对象的多次装饰
- 客户端代码:
……
Component component_o; //使用抽象构件类型定义
component_o = new ConcreteComponent();
component_o.operation();
ConcreteDecorator component_d; //使用具体装饰类型定义
component_d = new ConcreteDecorator(component_o);
component_d.operation();
component_d.addedBehavior(); //单独调用新增业务方法
……
12.3模式效果与应用
优点:
- 装饰模式比继承更加灵活,并不会导致类的个数剧增
- 动态的方式扩展一个对象的功能
- 可以多次装饰
- 用户可以根据需要增加新的具体构建类和具体装饰类,符合开闭原则
缺点:
- 产生很多小对象,在一定程度上影响了程序的性能
- 比继承更容易出错,排错也比较困难
适用环境
- 以动态、透明的方式给单个对象增加职责
- 不能采用继承的方式对系统进行扩展或者采用继承不利于系统的扩展和维护
十三、外观模式
— 对象结构型模式(相当于一个公司的前台)
为复杂的子系统调用提供一个统一的入口,使子系统与客户端的耦合度降低,且客户端调用非常方便。
13.1模式动机与定义
动机
为多个业务类的调用提供了一个统一的入口,简化了类与类之间的交互
定义
外部与子系统的通信通过一个统一的外观对象进行,为子系统中的一组接口提供一个统一的入口
13.2模式结构与分析
模式结构
模式分析
迪米特法则的一种具体实现
子系统是一个广义的概念,它可以是一个类、一个功能模块、系统的一个组成部分或者一个完整的系统
子系统类示例代码:
public class SubSystemA {
public void methodA() {
//业务实现代码
} }
public class SubSystemB {
public void methodB() {
//业务实现代码
} }
外观类示例代码:
public class Facade {
private SubSystemA obj1 = new SubSystemA();
private SubSystemB obj2 = new SubSystemB();
private SubSystemC obj3 = new SubSystemC();
public void method() {
obj1.method();
obj2.method();
obj3.method();
} }
客户类示例代码:
public class Client {
public static void main(String args[]) {
Facade facade = new Facade();
facade.method();
} }
案例:
(1)案例背景:
在计算机主机(Mainframe)中,只需要按下主机的开机按钮(on()),即可调用其他硬件设备和软件的启动方法,如内存(Memory)的自检(check())、CPU的运行(run())、硬盘的(HardDisk)的读取(read())、操作系统(OS)的载入(load())等,如果某一过程发生错误则计算机启动失败。使用外观模式模拟该过程,绘制类图并编程实现。(课本230页第二题)
(2)实现步骤:
根据题意,画出外观模式的类图,使主机类Mainframe充当外观角色,内存类Memory,CPU类CPU,硬盘类HardDisk和操作系统类OS充当子系统角色
根据类图,编写并实现代码
编译并运行代码,使代码能够输出模拟出来的电脑开机过程
== 类图==
代码
Mainframe
1.package facade;
2.
3.public class Mainframe {
4. private Memory memory=new Memory(); //关联关系建立联系
5. private CPU cpu=new CPU();
6. private HardDisk hardDisk=new HardDisk();
7. private OS os=new OS();
8.
9. public void on(){
11. memory.check();
12. cpu.run();
13. hardDisk.read();
14. os.load();
15. }
16.
17.}
Memory
1.package facade;
2.
3.public class Memory {
4. public void check(){
5. System.out.println("内存自检正常");
6. }
7.}
CPU
1.package facade;
2.
3.public class CPU {
4. public void run(){
5. System.out.println("CPU运行正常");
6. }
7.}
HardDisk
1.package facade;
2.
3.public class HardDisk {
4. public void read(){
5. System.out.println("硬盘读取正常");
6. }
7.}
OS
1.package facade;
2.
3.public class OS {
4. public void load(){
5. System.out.println("操作系统加载正常");
6. }
7.}
Client
1.package facade;
2.
3.public class Client {
4. public static void main(String[] args) {
5.
6. Mainframe f = new Mainframe();
7. f.on();
8. }
9.}
13.3 模式效果与应用
优点:
- 减少了客户端处理类的个数
- 实现了子系统和客户端之间的松耦合关系
- 一个子系统的修改对其他子系统没有影响,一个子系统的内部变化也不会影响到外观对象
缺点
- 不能更好的限制客户端直接使用子系统类
- 若设计不当,增加子系统可能需要修改外观类,不符合开闭原则
适用环境:
- 一系列复杂的子系统提供一个简单的入口
- 客户端与多个子系统之间存在很大的依懒性
- 使用外观模式定义系统中的每一层的入口
- 通过外观类建立联系,降低层之间的耦合度
13.4模式扩展
- 一个系统中有多个外观类
- 不要试图通过外观类为子系统增加新行为
- 外观模式与迪米特法则
- 抽象外观类的引入(一定程度上解决了不符合开闭原则)
类图
十四、代理模式
— 对象结构型模式
当直接访问某些对象存在问题时可以通过一个代理对象来间接访问,为了保证客户端使用的透明性,所访问的真实对象与代理对象需要实现相同的接口。
14.1模式动机与定义
动机
过引入一个新的对象(如小图片和进程代理对象)来实现对真实对象的操作,戒者将新的对象作为真实对象的一个替身
引入代理对象来间接访问一个对象
定义
给某一个对象提供一个代理,并由代理对象控制对原对象的引用
14.2模式结构与分析
模式结构
模式分析
抽象主题类示例代码:
public abstract class Subject {
public abstract void request(); }
真实主题类示例代码
public class RealSubject extends Subject{
public void request() {
//业务方法具体实现代码
} }
代理类示例代码
public class Proxy extends Subject {
private RealSubject realSubject = new RealSubject();
//维持一个对真实主题对象的引用
public void preRequest() { …... }
public void request() {
preRequest();
realSubject.request(); //调用真实主题对象的方法
postRequest();
}
public void postRequest() { …… }
}
常见的代理模式
远程代理(Remote Proxy)、 虚拟代理(Virtual Proxy)、保护代理(Protect Proxy):不同的用户提供不同级别的使用权限
缓冲代理(Cache Proxy)、智能引用代理(Smart Reference Proxy)
14.3模式效果与应用
优点:
- 协调调用者和被调用者,降低了系统的耦合度
- 增加和更换代理类无须修改源代码,符合开闭原则
缺点:
- 有些类型的代理模式可能会造成请求的速度变慢(保护代理)
- 有些代理模式的实现过程较为复杂(远程代理)
十五、命令模式
—对象行为型模式
将请求发送者与请求接收者解耦
相同的发送者对应不同的接收者,也可以将多个命令对象组合成宏命令,还可以在命令类中提供用来撤销请求的方法。
15.1模式动机与定义
动机
将发送者的请求封装在命令对象中,再通过命令对象来调用接收者的方法
定义
将一个请求封装为一个对象,从而使我们可用不同的请求对客户进行参数化;对请求排队戒者记录请求日志,以及支持可撤销的操作。
15.2模式结构与分析
模式结构
模式分析
抽象命令类示例代码:
public public abstract class Command {
public abstract void execute( ); }
接收类示例代码:
public class Receiver {
public void action( ) {
//具体操作
} }
调用者(请求发送者)示例代码:
public class Invoker {
private Command command;
public Invoker(Command command) {
this.command = command;
}
public void setCommand(Command command) {
this.command = command;
}
public void call() {
command.execute( );
} }
具体命令类示例代码:
public class ConcreteCommand extends Command {
private Receiver receiver; //维持一个对请求接收者对象的引用
public void execute() {
receiver.action( ); //调用请求接收者的业务处理方法action( )
} }
15.3模式效果与应用
优点:
- 降低系统的耦合度
- 可以支持宏命令或请求队列
- 撤销和恢复操作
缺点:
导致系统中的具体类过多
适用范围:
- 将请求者和接受者解耦
- 不同的时间中,将请求排队和执行
- 支持命令的撤销和恢复操作
- 宏命令
十六、迭代器模式
— 对象行为型模式
16.1模式动机与定义
动机
如何访问一个聚合对象中的元素但又不需要暴露它的内部结构,还能提供多种不同的遍历方式(迭代器模式)
定义
提供一种方法来访问聚合对象, 而不用暴露这个对象的内部表示。
16.2模式结构与分析
模式结构
模式分析
聚合对象的两个职责:
• 存储数据,聚合对象的基本职责
• 遍历数据,既是可变化的,又是可分离的
抽象迭代器示例代码:
public interface Iterator {
public void first(); //将游标指向第一个元素
public void next(); //将游标指向下一个元素
public boolean hasNext(); //判断是否存在下一个元素
public Object currentItem(); //获取游标指向的当前元素
}
具体迭代器示例代码:
public class ConcreteIterator implements Iterator {
private ConcreteAggregate objects; //维持一个对具体聚合对象
的引用,以便于访问存储在聚合对象中的数据
private int cursor; //定义一个游标,用于记录当前访问位置
public ConcreteIterator(ConcreteAggregate objects) {
this.objects=objects;
}
public void first() { ...... }
public void next() { ...... }
public boolean hasNext( ) { ...... }
public Object currentItem( ) { ...... } }
抽象聚合类示例代码:
public interface Aggregate {
Iterator createIterator( ); }
具体聚合类示例代码:
public class ConcreteAggregate implements Aggregate {
......
public Iterator createIterator( ) {
return new ConcreteIterator(this);
}
......
}
案例
电视机遥控器就是一个迭代器的实例,通过它可以实现对电视机频道集合的遍历操作,本实例我们将模拟电视机遥控器的实现
类图
代码:
SkyworthIterator类:
1.package iterator;
2.
3.public class SkyworthIterator implements TVIterator {
4. private int currentIndex=0;
5. private SkyworthTelevision Skyworth;
6. public SkyworthIterator(SkyworthTelevision Skyworth) {
7. this.Skyworth=Skyworth;
8. }
9.
10. public void next()
11. {
12. if(currentIndex<Skyworth.length())
13. {
14. currentIndex++;
15. }
16. }
17.
18. public void previous()
19. {
20. if(currentIndex>0)
21. {
22. currentIndex--;
23. }
24. }
25.
26. public void setChannel(int i)
27. {
28. currentIndex=i;
29. }
30.
31.
32. public Object currentChannel()
33. {
34. return Skyworth.get(currentIndex);
35. }
36.
37. public boolean isLast()
38. {
39. return currentIndex==Skyworth.length();
40. }
41.
42. public boolean isFirst()
43. {
44. return currentIndex==0;
45. }
46.}
SkyworthTelevision类:
1.package iterator;
2.
3.
4.
5.public class SkyworthTelevision extends Television
6.{
7. private Object[] Skyworth={"CCTV-1","CCTV-2","CCTV-3","CCTV-4","CCTV-5","CCTV-6","CCTV-7","CCTV-8"};
8.
9. public SkyworthTelevision(Object[] Skyworth) {
10. super(Skyworth);
11. }
12.
13. public TVIterator createIterator()
14. {
15. return new SkyworthIterator(this);
16. }
17.
18.
19.}
TCLIterator类
1.package iterator;
2.
3.public class TCLIterator implements TVIterator {
4. private TCLTelevision TCL;
5. private int currentIndex=0;
6. public TCLIterator( TCLTelevision TCL) {
7. this.TCL=TCL;
8. }
9. public void next()
10. {
11. if(currentIndex<TCL.length())
12. {
13. currentIndex++;
14. }
15. }
16.
17. public void previous()
18. {
19. if(currentIndex>0)
20. {
21. currentIndex--;
22. }
23. }
24.
25. public void setChannel(int i)
26. {
27. currentIndex=i;
28. }
29.
30.
31. public Object currentChannel()
32. {
33. return TCL.get(currentIndex);
34. }
35.
36. public boolean isLast()
37. {
38. return currentIndex==TCL.length();
39. }
40.
41. public boolean isFirst()
42. {
43. return currentIndex==0;
44. }
45.}
TCLTelevision类
1.package iterator;
2.
3.
4.public class TCLTelevision extends Television{
5. private Object[] TCL={"湖南卫视","山西卫视","湖北卫视","天津卫视","陕西卫视","北京卫视"};
6. public TCLTelevision(Object[] TCL) {
7. super(TCL);
8. }
9. public TVIterator createIterator()
10. {
11. return new TCLIterator(this);
12. }
13.}
Television类
1.package iterator;
2.
3.public abstract class Television
4.{
5. public Object[] objects;
6. public Television(Object[] objects) {
7. this.objects = objects;
8. }
9. public abstract TVIterator createIterator();
10.
11. public int length(){
12. return objects.length;
13. };
14. public Object get(int currentIndex) {
15. return this.objects[currentIndex];
16. }
17.}
TVIterator类
1.package iterator;
2.
3.public interface TVIterator
4.{
5. void setChannel(int i);
6. void next();
7. void previous();
8. boolean isLast();
9. Object currentChannel();
10. boolean isFirst();
11.}
Client类
1.package iterator;
2.
3.public class Client
4.{
5. public static void display(TVIterator iterator)
6. {
7. System.out.println("电视机频道");
8. while(!iterator.isLast())
9. {
10. System.out.println(iterator.currentChannel().toString());
11. iterator.next();
12. }
13. }
14.
15. public static void reverseDisplay(TVIterator iterator)
16. {
17.
18. iterator.setChannel(5);
19. System.out.println("逆向遍历");
20. while(!iterator.isFirst())
21. {
22. iterator.previous();
23. System.out.println(iterator.currentChannel().toString());
24. }
25. }
26.
27. public static void main(String[] args) {
28. Television tv;
29. TVIterator iterator;
30. tv=(Television)XMLUtil.getBean();
31. iterator=tv.createIterator();
32. display(iterator);
34. System.out.println("--------------------------");
35. reverseDisplay(iterator);
36. }
16.3 模式效果与应用
优点:
- 以不同的方式遍历同一个对象,在同一个聚合对象上可以定义多种遍历方式
- 简化了聚合类
- 增加新的聚合类和迭代器类都很方便,符合开闭原则
缺点:
- 类的个数增加,增加了系统的复杂性
- 抽象迭代器的设计难度大,设计一个考虑全面的抽象迭代器比较困难
适用情况
- 访问一个聚合对象的内容而无须暴露他的内部表示
- 提供对一个聚合类的多种遍历方式
- 为遍历不同的聚合结构提供了有个统一的接口,客户端可以一致的访问某个接口
十七、观察者模式
—对象行为型模式
描述对象之间的依赖关系,一对多
17.1 模式动机与定义
动机
个对象的状态或行为的变化将导致其他对象的状态或行为也发生改变,它们之间将产生联劢
定义
定义对象间的一种一对多依赖关系,使得每当一个对象状态发生改变时,其相关依赖对象皆得到通知并被自动更新。
17.2模式结构与分析
模式结构
模式分析
抽象目标类示例代码:
import java.util.*;
public abstract class Subject {
protected ArrayList observers<Observer> = new ArrayList();
public void attach(Observer observer) {
observers.add(observer); }
public void detach(Observer observer) {
observers.remove(observer); }
public abstract void notify( );
}
具体目标类示例代码:
public class ConcreteSubject extends Subject {
//实现通知方法
public void notify() {
for(Object obs:observers) {
((Observer)obs).update();
}
} }
抽象观察者类示例代码:
public interface Observer {
public void update(); }
具体观察者类示例代码:
public class ConcreteObserver implements Observer {
public void update() {
……
} }
客户类示例代码:
……
Subject subject = new ConcreteSubject();
Observer observer = new ConcreteObserver();
subject.attach(observer); //注册观察者
subject.notify();
……
案例:
某在线股票系统需要提供以下功能:当股票购买者所购买的某只股票价格变化幅度达到 5%时,系统
将自动发送通知(包括新价格)给购买该股票的股民。现使用观察者模式设计该系统,绘制类图并编程实
现
类图:
代码
Investor类
1.package visitor;
2.
3.public interface Investor {
4. public void response(Stock stock);
5.}
ConcreteInvestor类
1.package visitor;
2.
3.public class ConcreteInvestor implements Investor {
4. private String name;
5.
6. public ConcreteInvestor(String name) {
7. super();
8. this.name = name;
9. }
10.
11. @Override
12. public void response(Stock stock) {
13. System.out.println(name+": ");
14. System.out.println(stock.getStockName()+"涨幅超过5%");
15. System.out.println("新价格"+stock.getPrice());
16. }
17.}
Stock类
1.package visitor;
2.
3.import java.util.ArrayList;
4.
5.public class Stock {
6. private ArrayList<Investor> investors;
7. private String stockName;
8. private double price;
9. public Stock(String stockName, double price) {
10. super();
11. this.stockName = stockName;
12. this.price = price;
13. investors = new ArrayList<Investor>();
14. }
15. public void attach(Investor investor){
16. investors.add(investor);
17. }
18. public void detach(Investor investor){
19. investors.remove(investor);
20. }
21. public void notifyInvestor(){
22. for(Object obj : investors){
23. ((Investor)obj).response(this);
24. }
25. }
26. public String getStockName() {
27. return stockName;
28. }
29. public void setStockName(String stockName) {
30. this.stockName = stockName;
31. }
32. public double getPrice() {
33. return price;
34. }
35. public void setPrice(double price) {
36. double range = Math.abs(price-this.price)/this.price;
37. this.price = price;
38. if(range>=0.05){
39. this.notifyInvestor();
40. }
41. }
42.}
Client类
1.package visitor;
2.
3.public class Client {
4. public static void main(String[] args) {
5. Investor investor1,investor2;
6. investor1 = new ConcreteInvestor("软件");
7. investor2 = new ConcreteInvestor("呵呵");
8.
9. Stock haier = new Stock("haier", 20.00);
10. haier.attach(investor1);
11. haier.attach(investor2);
12. haier.setPrice(25.00);
13. }
14.}
17.3模式效果与应用
优点:
- 表示层与逻辑层的分离
- 实现广播通信
- 符合开闭原则
- 建立了观察者和观察目标之间建立一个抽象的耦合
缺点:
- 花费很多时间
- 观察者过多会导致系统崩溃
- 没有相应的机制让观察者知道观察对象是怎样变化
适用情况
- 一个方面依赖于另一方面
- 一个对象的变化会导致一个或多个对象发生改变,并不知道有多少对象发生改变,也不知道这些对象是谁
- 创建一个触发链
十八、状态模式
—对象创建模式
状态之间的转换
18.1模式动机与定义
定义
允许一个对象在其内部状态改变时改变它的行为,对象看起来似乎修改了它的类
18.2模式结构与分析
模式结构
模式分析
典型的抽象状态类示例代码:
public abstract class State {
//声明抽象业务方法,丌同的具体状态类可以有丌同的实现
public abstract void handle( );
}
典型的具体状态类示例代码:
public class ConcreteState extends State {
public void handle( ) {
//方法具体实现代码
} }
典型的环境类示例代码:
public class Context {
private State state; //维持一个对抽象状态对象的引用
private int value; //其他属性值,其变化可能会导致对象的状态变化
public void setState(State state) {
this.state = state;
}
public void request() {
…
state.handle( ); //调用状态对象的业务方法
} }
18.3模式效果与应用
优点:
- 封装了状态转换规则
- 将所有与某个状态有关的行为放到一个类中
- 允许状态转换逻辑与状态对象合成一体
- 共享状态对象,减少系统中类的个数
缺点
- 增加了类和对象的个数
- 如果使用不当将导致程序结构和代码混乱,增加设计难度
- 对开闭原则支持不好
适用情况
- 对象的行为依赖于他的状态,状态改变将导致行为变化
- 代码中包含大量与对象状态有关的语句
十九、策略模式
—对象行为型模式
该算法族中任选一个算法解决某一问题,同时可以方便地更换算法或者增加新的算法
19.1模式动机与定义
动机
可根据实际情况选择一条合适的途径
定义
定义一系列算法,将每一个算法封装起来,并让它们可以相互替换。
19.2 模式结构与分析
模式结构
模式分析
不使用策略模式时的示例代码:
public class Context {
… …
public void algorithm( String type) {
… …
if (type==“strategyA”) { // 算法A }
else if (type==“strategyB”) { // 算法B }
else if (type==“strategyC”) { // 算法C }
… …
} }
典型的抽象策略类示例代码:
public abstract class Strategy {
public abstract void algorithm( ); //声明抽象算法
}
典型的具体策略类示例代码:
public class ConcreteStrategyA extends Strategy {
//算法的具体实现
public void algorithm( ) {
//算法A
} }
典型的环境类示例代码:
public class Context {
private Strategy strategy; //维持一个对抽象策略类的引用
//注入策略对象
public void setStrategy(Strategy strategy) {
this.strategy= strategy;
}
//调用策略类中的算法
public void algorithm( ) {
strategy.algorithm( );
} }
-典型的客户类示例代码:
……
Context context = new Context( );
Strategy strategy;
strategy = new ConcreteStrategyA( );
//可在运行时指定类型,通过配置文件和反射机制实现
context.setStrategy(strategy);
context.algorithm( );
……
案例:
在介绍策略模式时,我们讲到了从不同角度出发,可以使用不同的出行策略的例子,教材中已经提供
了“旅游出行策略”的类图,用 Java 代码模拟实现“旅游出行策略”实例,要求使用配置文件存储具体策略类的类名。在此基础上再增加一种新的旅游出行方式,如徒步旅行(WalkStrategy),修改原有类图及
方法,并编程实现。
类图
代码
Person类:
1.package strategy;
2.
3.public class Person {
4. private TravelStrategy strategy;
5. public void setStrategy(TravelStrategy strategy) {
6. this.strategy = strategy;
7. }
8. public void travel(){
9. strategy.travel();
10. }
11.}
TravelStrategy类
1.package strategy;
2.
3.public interface TravelStrategy {
11. public void travel();
5.}
AirplaneStrategy类:
1.package strategy;
2.
3.public class AirplaneStrategy implements TravelStrategy {
12. @Override
13. public void travel() {
14. System.out.println("坐飞机去旅行");
15. }
8.}
TrainStrategy类:
1.package strategy;
2.
3.public class TrainStrategy implements TravelStrategy{
16. @Override
17. public void travel() {
18. System.out.println("坐火车去旅行");
19. }
8.}
BicycleStartegy类:
1.package strategy;
2.
3.public class BicycleStartegy implements TravelStrategy{
20. @Override
21. public void travel() {
22. System.out.println("骑车去旅行");
23. }
8.}
WalkStrategy类:
1.package strategy;
2.
3.public class WalkStrategy implements TravelStrategy {
24. @Override
25. public void travel() {
26. System.out.println("徒步去旅行");
27. }
8.}
Client类:
1.package strategy;
2.
3.
4.public class Client {
28. public static void main(String[] args) {
29. Person person=new Person();
30. TravelStrategy strategy;
31. strategy = (TravelStrategy) XMLUtil.getBean();
32. person.setStrategy(strategy);
33.
34. person.travel();
35. }
36. }
19.3 模式效果与应用
优点:
- 支持开闭原则
- 管理相关算法族的办法
- 可以替换继承关系的方法
- 避免多重条件选择语句
- 算法复用机制
缺点
- 客户端必须知道所有的策略类
- 产生很多的具体策略类
- 无法同时在客户端使用多个策略类
适用情况:
- 动态选择算法中的一种
- 避免使用难以维护的多重条件选择语句
- 提高算法的保密性和安全性
19.4 模式扩展
策略模式与状态模式比较
类别 | 策略模式 | 状态模式 |
---|---|---|
环境类中角色状态个数 | 一个 | 多个 |
环境类和策略类/状态类之间交互情况 | 单向关联 | 双向关联 |
转换关系 | 状态之间相互转换 | 不转换 |
客户端是否需要知道具体策略 | 是 | 否 |
二十、综合设计
Windows Media Player 和 RealPlayer 是两种常用的媒体播放器,它们的 API 结构和调用方法存在差别,现在你的应用程序需要支持这两种播放器 API,并且将来可能还需要支持新的媒体播放器,根据给出的类图,回答该应用程序使用了哪些设计模式,并编程实现这个应用程序
1.1由上面类图分析得到:采用了适配器模式和抽象工厂模式来实现
1.2代码:
ClientClass类:
1.package player;
2.
3.
4.public class ClientClass {
5. public static void main(String args[])
6. {
7. try
8. {
9. PlayerFactory factory;
10. MainWindow mainWindow;
11. PlayerList playerList;
12. factory=(PlayerFactory) XMLUtil.getBean();
13. System.out.println("软件1914赵雅琴");
14. mainWindow=factory.createMainWindow();
15. mainWindow.show();
16. playerList=factory.createPlayerList();
17. playerList.player();
18. }
19. catch(Exception e)
20. {
21. System.out.println(e.getMessage());
22. }
23. }
24.}
MainWindow类
1.package player;
2.
3.public interface MainWindow {
4.public void show();
5.}
MediaPlayerFactory类
1.package player;
2.
3.public class MediaPlayerFactory extends PlayerFactory {
4. @Override
5. public MainWindow createMainWindow() {
6. return new MediaPlayerWindow();
7. }
8.
9. @Override
10. public PlayerList createPlayerList() {
11. return new MediaPlayerList();
12. }
13.}
MediaPlayerList 类
1.package player;
2.
3.public class MediaPlayerList implements PlayerList {
4. private MeadiaPlayerAPI meadiaPlayerAPI=new MeadiaPlayerAPI();
5. @Override
6. public void player() {
7. meadiaPlayerAPI.call();
8. System.out.println("MediaPlayerList列表正在播放");
9. }
10.}
MeadiaPlayerAPI类
1.package player;
2.
3.public class MeadiaPlayerAPI {
4. public void call(){
5. System.out.println("调用MeadiaPlayerAPI播放MediaPlayerList列表");
6. }
7.}
MediaPlayerWindow类
1.package player;
2.
3.public class MediaPlayerWindow implements MainWindow{
4.
5. @Override
6. public void show() {
7. System.out.println("现在打开的是MediaPlayerWindow");
8. }
9.}
PlayerFactory类
1.package player;
2.
3.public abstract class PlayerFactory {
4.public abstract MainWindow createMainWindow();
5.public abstract PlayerList createPlayerList();
6.}