1. 总述
☞ 23 种设计模式——行为型设计模式(11种)
软件设计模式使人们可以更加简单方便复用成功的设计和体系结构,它通常包含以下几个基本要素:模式名称、别名、动机、问题、解决方案、效果、模式角色、合作关系、实现方法、实用性、已知应用、例程、模式扩展和相关模式等。
设计模式有两种分类方法,一种根据模式的目的来分;另一种根据模式的作用来分。
1.1 根据模式的目的划分
根据模式是用来完成什么样的工作来划分,这种方法可分为创建型模式、结构型模式、行为型模式3种。
1.1.1 创建型模式
用于描述“怎么创建对象”。它的主要特点是“将对象的创建与使用分离”。如,单例、原型、工厂方法、抽象工厂、建造者等5种创建型模式。
1.1.2 结构型模式
用于描述“如何将类或对象按某种布局组成更大的结构”。如,代理、适配器、桥接、装饰、外观、享元、组合等7种结构型模式。
1.1.3 行为型模式
用于描述“类或对象之间怎样相互协作共同完成单个对象无法单独完成的任务,以及怎样分配职责”。如,模板方法、策略、命令、职责链、状态、观察者、中介者、迭代器、访问者、备忘录模式、解释器等11中行为模式。
1.2 根据模式的作用划分
根据模式的主要用于类上还是主要用户对象上来分,这种方式可分为类模式和对象模式两种。
1.2.1 类模式
用于处理类与子类之间的关系,这些关系通过继承来建立,是静态的,在编译时便确定下来了。如,工厂方法、(类)适配器、模板方法、解释器等4种类模式。
1.2.2 对象模式
用户处理对象之间关系的,这些关系可以通过组合或聚合来实现,在运行时刻是可以变化的,更具动态性。
范围/目的 |
创建型模式 |
结构型模式 |
行为型模式 |
类模式 |
工厂方法 |
(类)适配器 |
模板方法 解释器 |
对象模式 |
单例 原型 抽象工厂 建造者 |
代理 (对象)适配器 桥接 装饰 外观 享元 组合 |
策略 命令 职责链 状态 观察者 中介者 迭代器 访问者 备忘录 |
1.3 23种设计模式功能
前面说明了这23种设计模式的分类,下面是对各个模式的功能进行介绍。
1)单例(Singleton)模式
某个类只能生成一个实例,该类提供了一个全局访问点,供外部获取该实例,其拓展是有限多个实例。
2)原型(Prototype)模式
将一个对象作为原型,通过对其进行复制而克隆出多个和原型类似的新实例。
3)工厂方法(Factory Method)模式
定义一个用户创建产品的接口,有子类决定生产什么产品。
4)抽象工厂(Abstract Factory)模式
提供一个创建产品族的接口,其每个子类可以生产一些列相关的产品。
5)建造者(Builder)模式
将一个复杂对象分解成多个相对简单的部分,然后根据不同需要分别创建它们,最后构建成该复杂对象。
6)代理(Proxy)模式
为某个对象提供一种代理以控制对对象的访问。即客户端通过代理间接地访问该对象,从而限制、增强或修改该对象的一些特征。
7)适配器(Adapter)模式
将一个类的接口转换成客户希望的另一个接口,使得原本由于接口不兼容而不能一起工作的那些类能一起工作。
8)桥接(Bridge)模式
将抽象与实现分离,使它们可以独立变化。它是用组合关系代替继承关系来实现,从而降低 抽象和实现这两个可变维度的耦合度。
9)装饰(Decorator)模式
动态的给对象增加一些职责,即增加其额外的功能。
10)外观(Facade)模式
为多个复杂的子系统提供一个一致的接口,使这些子系统更加容易被访问。
11)享元(Flyweight)模式
运用共享技术来有效地支持大量细粒度对象的复用。
12)组合(Composite)模式
将对象组合成树状层次结构,使用户对单个对象和组合对象具有一致的访问性。
13)模板方法(Tempplate Method)模式
定义一个操作中的算法骨架,而将算法的一些步骤延迟到子类中,使得子类可以不改变该算法结构的情况下重定义该算法的某些特点步骤。
14)策略(Strategy)模式
定义了一系列算法,并将每个算法封装起来,使它们可以相互替换,且算法的改变不会影响算法的客户。
15)命令(Command)模式
将一个请求封装为一个对象,使发出请求的责任和执行请求的责任分割开。
16)职责链(Chain of Responsibility)模式
把请求从链中的一个对象传到下一个对象,直到请求被响应为止。通过这个方式去除对象之间的耦合。
17)状态(State)模式
允许一个对象在其内部状态发生改变时改变其行为能力。
18)观察者(Observer)模式
多个对象间存在一对多的关系,当一个对象发生改变时,把这种改变通知给其他多个对象,从而影响其它对象的行为。
19)中介者(Mediator)模式
定义一个中介对象来简化原有对象之间的交互关系,降低系统中对象的耦合度,使原有对象之间不必户互了解。
20)迭代器(Iterator)模式
提供一种方法来顺序访问聚合对象中的一系列数据,而不暴露聚合对象的内部表示。
21)访问者(Visitor)模式
在不改变集合元素的前提下,为一个集合中的每个元素提供多种访问方式,即每个元素有多个访问者对象。
22)备忘录(Memento)模式
在不破坏封装性的前提下,获取并保存一个对象的内部状态,以便以后回复它。
23)解释器(Interpreter)
提供如何定义语言的文法,以及对语言句子的解释方法,即解释器。
必须指出,这 23 种并设计模式不是孤立存在的,很多模式之间存在一定的关联关系,在大的系统开发中常常同时使用多种设计模式。
2. 创建型设计模式
创建型设计模式的主要关注点是“怎么创建对象”,它的主要特点是“将对象的创建与使用分离”。这样可以降低系统的耦合度,使用者不需要关注对象的创建细节,对象的创建有相关的工厂来完成。就像我们去商城购买商品时,不需要知道商品是怎么深处出来的一样,因为它们由专业的厂商生产。
创建型模式分为以下几种:
- 单例(Singleton)模式:某个类只能生成一个实例,该实例提供一个全局访问店供外部获取该对象,其扩展时有限多例模式。
- 原型(Prototype)模式:将一个对象作为原型,通过对其进行复制而克隆出多个和原型类型的新实例。
- 工厂方法(Factory Method)模式:定义一个用于创建产品的接口,有子类决定生产什么产品。
- 抽象工厂(Abstract Factory)模式:提供一个创建产品族的接口,其每个子类可以生产一些列相关的产品。
- 建造者(Builder)模式:将一个复杂对象分解成多个相对简单的部分,然后根据不同需要分别创建它们,最后构建从复杂对象。
以上 5 种创建型模式,处理工厂方法模式属于(类)创建型模式,其他的全部属于(对象)创建模式。
2.1 单例模式
在有些系统中,为了节省内存资源、保证数据内容的一致性,对某些类要求只能创建一个实例,这就是所谓的单例模式。
单例(Singleton)模式的定义:指一个类只有一个实例,其该类能自行创建这个实例的一种模式。例如,Windows 中只能打开一个任务管理器,这样可以避免因打开多个任务管理器窗口而造成内存资源的浪费,或出现各个窗口显示的内容不一致等错误。
在计算机系统中,还有 Windows 的回收站、操作系统中的文件系统、多线程中的线程池、显卡的驱动程序对象、打印机的后台处理服务、数据库的连接池、网站的计数器、Web 应用的配置对象、应用程序中的对话框、系统中的缓存等常常被设计成单例。
单例模式有以下3个特点:
1)单例类只有一个对象;
2)该单例对象必须由单例类自行创建;
3)单例类对外提供一个访问该单例的全局访问店。
2.1.1 单例模式的结构与实现
单例模式是设计模式中最简单的模式之一。通常,普通类的 结构函数是公有的,外部类可以通过“new 构造函数()”来生成多个实例。但是,如果将类的构造函数设为私有的,外部类就无法通过调用该类的构造函数,也就无法生成多个实例。这是该类自身必须定义一个静态私有实例,并向外提供一个讲台的公有函数用于创建或获取静态私有实例。
下面分析单例模式的实现。
单例模式有懒汉式和饿汉式两种实现形式。
第一种:懒汉式
该模式的特点是类加载时没有生成单例,只有当第一次调用 getInstance 方法才去创建单例。代码如下:
1 public class LazySingleton { 2 private static volatile LazySingleton instance = null; //保证 instance 在所有线程中同步 3 private LazySingleton() {} //private 避免类在外部被实例化 4 public static synchronized LazySingleton getInstance() { 5 // getInstance 方法前加同步 6 if(instance == null) { 7 instance = new LazySingleton(); 8 } 9 return instance; 10 } 11 }
注意:如果编写的是多线程程序,则不要删除上例代码中的关键字 volatile 和 synchronized,邹泽将存在线程非安全的问题。如果不删除这两个关键字就保证线程安全,但是每次访问时都要同步,会影响性能,且小号更多的资源,这是懒汉式单例的缺点。
第二种:饿汉式单例
该模式的特点是类一旦加载就创建一个单例,保证在调用 getInstance 方法之前单例已经存在了。
1 public class HungrySingleton { 2 private static final HungrySingleton instance = new HungrySingleton(); 3 private HungrySingleton(){} 4 public static HungrySingleton getInstance() { 5 return instance; 6 } 7 }
饿汉式单例在类 创建的同时就已经创建好一个静态的对象供系统使用,以后不再改变,以后线程安全的,可以直接用于多线程而不会出现问题。
2.1.2 单例模式的应用场景
- 在应用场景中,某类只要求生成一个对象的时候,如一个班中的板子、每个人的身份证号等。
- 当对象需要被共享的场合。由于单例模式志云与创建一个对象,共享该对象可以节省内存,并加快对象访问速度。如 Web 中的配置对象、数据库的连接池等。
- 当某类需要频繁实例化,而创建的对象有频繁被销毁的时候,如多线程的线程池、网络连接池等。
2.1.3 单例模式的扩展
单例模式可扩展为有效的多例(Multiton)模式,这种模式可以生成有限个实例并保存在 ArmyList 中,客户需要时可随机获取,其结构如图2-1所示。
图2-1 有限的多例模式的结构图
2.2 原型(Prototype)模式
在有些系统中,存在大量相同或相似对象的创建问题,如果用传统的构造函方法创建对象,会比较复杂且耗时耗资源,用原型模式生成对象就很高效,就像孙悟空拔下猴毛轻轻一吹就变出很多孙悟空一样简单。
2.2.1 原型模式的定义与特点
原型模式的定义如下:用一个已经创建的实例作为原型,通过复制该原型对象来创建一个和原型相同或相似的新对象。在这里,原型实例指定了要创建的对象的种类。这种方式创建对象非常高效,根本无需指定对象创建的细节。例如,Windows 操作系统的安装通常比较耗时,如果复制就快了很多。
2.2.2 原型模式的结构和实现
由于 Java 提供了对象的 clone() 方法,所以用 Java 实现原型模式很简单。
(1)模式的结构
原型模式包含以下主要角色:
① 抽象原型类:规定了具体原型对象必须实现的接口。
② 具体实现类:实现抽象原型类的 clone() 方法,它使可被复制的对象。
③ 访问类:使用具体原型类中的 clone() 方法来复制新对象。
(2)模式的实现
原型模式的克隆分为浅克隆和深克隆,Java 中的 Object 类提供了浅克隆的 clone() 方法,具体原型类只要实现 Cloneable 接口就可实现对象的浅克隆,这里的 Cloneable 接口就是抽象原型类。
其代码如下:
1 // 具体原型类 2 class Realizetype implements Cloneable { 3 Realizetype() { 4 System.out.println("具体原型创建成功!"); 5 } 6 public Object clone() throws CloneNotSupportedException { 7 System.out.println("具体原型复制成功!"); 8 return (Realizetype)super.clone(); 9 } 10 }
1 // 原型模式的测试类 2 public class PrototypeTest { 3 public static void main(String[] args)throws CloneNotSupportedException { 4 Realizetype obj1=new Realizetype(); 5 Realizetype obj2=(Realizetype)obj1.clone(); 6 System.out.println("obj1==obj2?"+(obj1==obj2)); 7 } 8 }
程序运行结果如下:
具体原型创建成功!
具体原型复制成功!
obj1==obj2?false
2.2.3 原型模式的应用实例
分析:孙悟空拔下猴毛轻轻一吹就变出很多孙悟空,这里实际上是用到了原型模式。这里的孙悟空类 SunWukong 是具体原型类,而 Java 中的 Cloneable 接口是抽象原型类。
我们需要重写 Cloneable 接口的 clone() 方法,拥有复制新的孙悟空。访问类可以通过调用孙悟空的 clone() 方法复制多个孙悟空,并在框架窗体 JFrame 中显示。
程序代码如下:
1 class SunWukong extends JPanel implements Cloneable { 2 3 public SunWukong() { 4 JLabel l1 = new JLabel(new ImageIcon("src/Wukong.jpg")); 5 this.add(l1); 6 } 7 public Object clone() { 8 SunWukong w = null; 9 try { 10 w = (SunWukong)super.clone(); 11 } catch(CloneNotSupportedException e) { 12 System.out.println("拷贝悟空失败!"); 13 } 14 return w; 15 } 16 }
用原型模式除了可以生成相同的对象,还可以生成相似的对象,如以下实例:
分析:同一个学校的“三好学生”阿精装出获奖人姓名不同,其他的都想吐,属于相似对象的复制,同样可以用原型模式创建,然后再做简单的修改就可以了。
程序代码如下:
1 // 奖状类 2 class citation implements Cloneable { 3 String name; 4 String info; 5 String college; 6 citation(String name,String info,String college) { 7 this.name = name; 8 this.info = info; 9 this.college = college; 10 System.out.println("奖状创建成功!"); 11 } 12 void setName(String name) { 13 this.name = name; 14 } 15 String getName() { 16 return(this.name); 17 } 18 void display() { 19 System.out.println(name+info+college); 20 } 21 public Object clone() throws CloneNotSupportedException { 22 System.out.println("奖状拷贝成功!"); 23 return (citation)super.clone(); 24 } 25 }
1 public class ProtoTypeCitation { 2 public static void main(String[] args) throws CloneNotSupportedException { 3 citation obj1 = new citation("张三","同学:在2016学年第一学期中表现优秀,被评为三好学生。","韶关学院"); 4 obj1.display(); 5 citation obj2 = (citation) obj1.clone(); 6 obj2.setName("李四"); 7 obj2.display(); 8 } 9 }
程序运行结果如下:
奖状创建成功!
张三同学:在2016学年第一学期中表现优秀,被评为三好学生。韶关学院
奖状拷贝成功!
李四同学:在2016学年第一学期中表现优秀,被评为三好学生。韶关学院
2.2.4 原型模式的应用场景
原型模式通常适用于以下场景。
- 对象之间相同或相似,即只是个别的几个属性不同的时候,
- 对象的创建过程比较麻烦,但复制比较简单的时候。
2.2.5 原型模式的扩展
原型模式可扩展为带原型管理器的原型模式,它在原型模式的基础上增加了一个原型管理器 PrototypeManager 类。该类用 HashMap 保存多个复制的原型,Client 类可以通过管理器的 get(String id) 方法从中获取复制的原型。结构图如图2-2所示:
图2-2 带原型管理器的原型模式的结构图
用带原型管理器的原型模式来生成包含“圆”和“正方形”等圆形的原型,并计算其面积。分析:本实例中由于存在不同的图形类,例如,“圆”和“正方形”,它们计算面积的方法不一样,所以需要用一个原型管理器来管理它们,图2-3所示的是其结构图。
图2-3 图形生成器的结构图
程序代码如下:
1 interface Shape extends Cloneable { 2 public Object clone(); //拷贝 3 public void countArea(); //计算面积 4 }
1 class Circle implements Shape { 2 public Object clone() { 3 Circle w = null; 4 try { 5 w = (Circle)super.clone(); 6 } catch(CloneNotSupportedException e) { 7 System.out.println("拷贝圆失败!"); 8 } 9 return w; 10 } 11 public void countArea() { 12 int r = 0; 13 System.out.print("这是一个圆,请输入圆的半径:"); 14 Scanner input = new Scanner(System.in); // 阻塞,等待用户在命令行输入数据回车确认 15 r = input.nextInt(); 16 System.out.println("该圆的面积=" + 3.1415 * r * r + " "); 17 } 18 }
1 class Square implements Shape { 2 public Object clone() { 3 Square b = null; 4 try { 5 b = (Square)super.clone(); 6 } catch(CloneNotSupportedException e) { 7 System.out.println("拷贝正方形失败!"); 8 } 9 return b; 10 } 11 public void countArea() { 12 int a=0; 13 System.out.print("这是一个正方形,请输入它的边长:"); 14 Scanner input = new Scanner(System.in); 15 a = input.nextInt(); 16 System.out.println("该正方形的面积=" + a * a + " "); 17 } 18 }
1 class ProtoTypeManager { 2 private HashMap<String, Shape>ht=new HashMap<String,Shape>(); 3 public ProtoTypeManager() { 4 ht.put("Circle", new Circle()); 5 ht.put("Square", new Square()); 6 } 7 public void addshape(String key,Shape obj) { 8 ht.put(key,obj); 9 } 10 public Shape getShape(String key) { 11 Shape temp = ht.get(key); 12 return (Shape) temp.clone(); 13 } 14 }
1 public class ProtoTypeShape { 2 public static void main(String[] args) { 3 ProtoTypeManager pm = new ProtoTypeManager(); 4 Shape obj1 = (Circle)pm.getShape("Circle"); 5 obj1.countArea(); 6 Shape obj2 = (Shape)pm.getShape("Square"); 7 obj2.countArea(); 8 } 9 }
运行结果如下:
这是一个圆,请输入圆的半径:3 该圆的面积=28.2735 这是一个正方形,请输入它的边长:3 该正方形的面积=9
2.3 工厂方法(Factory Method)模式
在显示生活中社会分工越来越细,越来越专业。各种产品有专门的工厂生成蟒蛇地告别自给自足的小农经济时代,这大大缩短了产品的生产周期,提高了生产效率。同样,在软件开发中能否做到软件对象的生产和使用相分离呢?能否在满足“开闭原则”的前提现下,客户随意增删或改变对软件相关对象的使用呢?
2.3.1 工厂方法模式的定义与特点
工厂方法模式的定义:定义一个创建产品对象的工厂接口,将产品对象的实际创建工作推迟都具体工厂类中。当满足创建型模式中所要求的“创建与使用相分离”的特点。
我们把被创建的对象成为“产品”,把创建产品的对象成为“工厂”。如果要创建的产品不多,只要一个工厂类就可以完成,这种模式键“简单工厂模式”,它不属于23种经典设计模式,它的缺点是增加新产品时会违背“开闭原则”。
本节介绍的“工厂方法模式”是对简单工厂模式的进一步抽象画,其好处是可以是系统自不修改原来代码的情况下引进新的产品,即满足开闭原则。
工厂方法模式的主要优点有:
- 用户只需要指导具体工厂的名称就可以得到所需要的产品,无需知道产品的具体创建过程;
- 在系统增加新的产品时只需要添加具体产品类和对应的具体工厂类,无需对原工厂进行任何修改,满足开闭原则。
其缺点是:每增加一个产品就要增加一个具体产品类和一个对应的具体工厂类,这增加了系统的复杂度。
2.3.2 工厂方法模式的结构与实现
工厂方法模式由抽象工厂、具体工厂、抽象产品和具体产品等 4 个要素构成。
(1)模式的结构
工厂方法模式的主要角色如下:
1)抽象工厂(Abstract Factory):提供了创建产品的接口,调用者通过它访问具体工厂的工厂方法 newProduct() 来创建产品;
2)具体工厂(Concrete Factory):主要是实现抽象工厂中的抽象方法,具体产品的创建;
3)抽象产品(Product):定义了产品的规范,描述了产品的主要特征和功能;
4)具体产品(Concrete Product):实现了抽象产品角色所定义的接口,有具体工厂来创建,它同具体工厂之间一一对应。
其结构图如图2-4所示:
图2-4 工厂方法模式的结构图
(2)模式的实现
根据图2-4 写出该模式下的代码:
1 // 抽象产品:提供了产品的接口 2 interface Product { 3 public void show(); 4 }
1 //具体产品1:实现抽象产品中的抽象方法 2 class ConcreteProduct1 implements Product { 3 public void show() { 4 System.out.println("具体产品1显示..."); 5 } 6 }
1 // 具体产品2:实现抽象产品中的抽象方法 2 class ConcreteProduct2 implements Product { 3 public void show() { 4 System.out.println("具体产品2显示..."); 5 } 6 }
1 // 抽象工厂:提供了厂品的生成方法 2 interface AbstractFactory { 3 public Product newProduct(); 4 }
1 //具体产品2:实现抽象产品中的抽象方法 2 class ConcreteProduct2 implements Product { 3 public void show() { 4 System.out.println("具体产品2显示..."); 5 } 6 }
1 // 抽象工厂:提供了厂品的生成方法 2 interface AbstractFactory { 3 public Product newProduct(); 4 }
1 //具体工厂1:实现了厂品的生成方法 2 class ConcreteFactory1 implements AbstractFactory { 3 public Product newProduct() { 4 System.out.println("具体工厂1生成-->具体产品1..."); 5 return new ConcreteProduct1(); 6 } 7 }
1 //具体工厂2:实现了产品的生成方法 2 class ConcreteFactory2 implements AbstractFactory { 3 public Product newProduct() { 4 System.out.println("具体工厂2生成-->具体产品2..."); 5 return new ConcreteProduct2(); 6 } 7 }
2.3.3 模式的应用实例
【例】用工厂方法模式设计畜牧场。
分析:有很多种类的畜牧场,如养马场用于养马,养牛场用于养牛,所以该实例用工厂方法模式比较适合。
对养马场和养牛场等具体工厂类,只要定义一个生产动物的方法 newAnimal() 即可。其结构图如图2-5所示。
图2-5 畜牧场结构图
程序代码如下
1 // 抽象产品:动物类 2 interface IAnimal { 3 public void show(); 4 }
1 // 具体产品:马类 2 public class Horse implements IAnimal { 3 @Override 4 public void show() { 5 System.out.println("这是一匹马!"); 6 } 7 }
// 具体产品:牛类 public class Cattle implements IAnimal { @Override public void show() { System.out.println("这是一头牛!"); } }
1 //抽象工厂:畜牧场 2 interface IAnimalFarm { 3 public IAnimal newAnimal(); 4 }
1 //具体工厂:养马场 2 class HorseFarm implements IAnimalFarm { 3 public IAnimal newAnimal() { 4 System.out.println("新马出生!"); 5 return new Horse(); 6 } 7 }
1 //具体工厂:养牛场 2 class CattleFarm implements IAnimalFarm { 3 public IAnimal newAnimal() { 4 System.out.println("新牛出生!"); 5 return new Cattle(); 6 } 7 }
1 public class AnimalFarmTest { 2 3 public static void main(String[] args) { 4 try { 5 IAnimal a; 6 IAnimalFarm af = (IAnimalFarm) ReadXML.getObject(HorseFarm.class); 7 if (af != null) { 8 a = af.newAnimal(); 9 a.show(); 10 } 11 } catch (Exception e) { 12 System.out.println(e.getMessage()); 13 } 14 } 15 } 16 17 public class ReadXML { 18 public static Object getObject(Class<?> mClass) { 19 try { 20 return mClass.newInstance(); 21 } catch (InstantiationException e) { 22 e.printStackTrace(); 23 return null; 24 } catch (IllegalAccessException e) { 25 e.printStackTrace(); 26 return null; 27 } 28 } 29 }
程序的运行结果:
新马出生!
这是一匹马!
2.3.4 模式的应用场景
工厂方法模式通常适用于以下场景。
- 客户只知道创建产品的工厂名,而不知道具体的产品名。如 TCL 电视工厂、海信电视工厂等。
- 创建对象的任务由多个具体工厂中的某一个完成,而抽象工厂只提供创建产品的接口。
- 客户不关心创建产品的细节,只关心产品的品牌。
2.3.5 模式的扩展
当需要生成的产品不多且不会增加,一个具体工厂类就可以完成任务时,可删除抽象工厂类。这时工厂方法模式将退化到简单工厂模式,其结构图如图2-5所示:
图2-5 简单工厂模式的结构图
2.4 抽象工厂(AbstractFactory)模式
上节介绍的工厂方法模式中考虑的是一类产品的生产,如畜牧场只养动物、电视机厂只生产电视机、计算机软件学院只培养计算机软件专业的学生等。
同种类称为同等级,也就是说:工厂方法模式只考虑深处同等级的产品,但是在显示生活中许多工厂是综合型的工厂,能生产多等级(种类)的产品,如农场里既养动物又种植物,电器厂既生产电视机又生产洗衣机或空调,大学既有软件专业又有生物专业等。
本节要介绍的抽象工厂模式将考虑多等级产品的生产,将同一个具体工厂所生产的位于不同等级的一组产品成为一个产品族,图2-6所示的是海尔工厂和TCL工厂生产的电视机与空调对应的关系图。
图2-6 电器工厂的产品等级与产品族
2.4.1 模式的定义与特点
抽象工厂模式的定义:是一种为访问类提供一个创建一组相关或相互依赖对象的接口,其访问类无须指定所要产品的具体类就能得到同族的不同等级的产品的模式结构。
抽象工厂模式是工厂方法模式的升级版本,工厂方法模式只生产一个等级的产品,而抽象工厂模式可生产多个等级的产品。
使用抽象工厂模式一般要满足以下条件:
- 系统中有多个产品族,每个具体工厂创建同一族但属于不同等级结构的产品。
- 系统一次只可能消费其中某一族产品,即同族的产品一起使用。
抽象工厂模式除了具有工厂方法模式的优点外,其他主要优点如下:
- 可以在类的内部对产品族中相关联的多等级产品共同管理,而不必专门引入多个新的类来进行管理。
- 当增加一个新的产品族时不需要修改原代码,满足开闭原则。
其缺点是:当产品族中需要增加一个新的产品时,所有的工厂类都需要进行修改。
2.4.2 模式的结构与实现
抽象工厂模式同工厂方法模式一样,也是由抽象工厂、具体工厂、抽象产品和具体产品等 4 个要素构成,但抽象工厂中方法个数不同,抽象产品的个数也不同。现在我们来分析其基本结构和实现方法。
(1)模式的结构
抽象工厂模式的主要角色如下:
① 抽象工厂:提供了创建产品的接口,它包含多个创建产品的方法 newProduct(),可以创建多个不同等级的产品。
② 具体工厂:主要是实现抽象工厂中的多个抽象方法,完成具体产品的创建。
③ 抽象产品:定义产品的规范,描述了产品的主要特征和功能,抽象工厂模式有多个抽象产品。
④ 具体产品:实现了抽象产品角色所定义的接口,由具体工厂来创建,它同具体工厂之间是多对一的关系。
抽象工厂模式的结构图如图2-7 所示:
图2-7 抽象工厂模式的结构图
(2)模式的实现
从图2-7 可以看出抽象工厂模式的结构同工厂方法模式的结构相似,不同的是其产品的种类不止一个,所以创建产品的方法也不止一个。下面给出抽象工厂和具体工厂的代码。
① 抽象工厂:提供了产品的生成方法。
1 interface AbstractFactory { 2 public Product1 newProduct1(); 3 public Product2 newProduct2(); 4 }
② 具体工厂:实现了产品的生成方法。
1 class ConcreteFactory1 implements AbstractFactory { 2 public Product1 newProduct1() { 3 System.out.println("具体工厂 1 生成-->具体产品 11..."); 4 return new ConcreteProduct11(); 5 } 6 public Product2 newProduct2() { 7 System.out.println("具体工厂 1 生成-->具体产品 21..."); 8 return new ConcreteProduct21(); 9 } 10 }
2.4.3 模式的应用实例
例:用抽象工厂模式设计农场类。
分析:农场中除了畜牧场一样可以养动物,还可以培养植物,如养马、养牛、种菜、种水果等,所以本实例比签名介绍的畜牧场类复杂,必须用抽象工厂模式来实现。
本例用抽象工厂模式来设计两个农场,一个是韶关农场用于养牛和种菜,一个是上饶农场用于养马和种水果,可以在以上两个农场中定义一个生产动物的方法 newAnimal() 和一个培养的方法 newPlant()。其结构如图2-8所示。
图2-8 农场类的结构图
程序代码如下:
1 // 抽象产品:动物类 2 interface Animal { 3 public void show(); 4 }
1 // 具体产品:马类 2 class Horse implements Animal { 3 public void show() { 4 System.out.println("这是一匹马"); 5 } 6 }
// 具体产品:牛类 class Cattle implements Animal { public void show() { System.out.println("这是一头牛"); } }
1 // 抽象产品:植物类 2 interface Plant { 3 public void show(); 4 }
1 // 具体产品:水果类 2 class Fruitage implements Plant { 3 public void show() { 4 System.out.println("这是一水果"); 5 } 6 }
1 // 具体产品:蔬菜类 2 class Vegetables implements Plant { 3 public void show() { 4 System.out.println("这是一蔬菜"); 5 } 6 }
1 // 抽象工厂:农场类 2 interface Farm { 3 public Animal newAnimal(); 4 public Plant newPlant(); 5 }
1 // 具体工厂:韶关农场类 2 class SGfarm implements Farm { 3 public Animal newAnimal() { 4 System.out.println("新牛出生!"); 5 return new Cattle(); 6 } 7 public Plant newPlant() { 8 System.out.println("蔬菜长成!"); 9 return new Vegetables(); 10 } 11 }
1 // 具体工厂:上饶农场类 2 class SRfarm implements Farm { 3 public Animal newAnimal() { 4 System.out.println("新马出生!"); 5 return new Horse(); 6 } 7 public Plant newPlant() { 8 System.out.println("水果长成!"); 9 return new Fruitage(); 10 } 11 }
2.4.4 模式的应用场景
抽象工厂模式最早的应用是用于创建处于不同操作系统的视窗构件。如 Java 的 AWT 中的 Button 和 Text 等构件在 Windows 和 UNIX 中的本地实现是不同的。
抽象工厂模式通常适用于以下场景:
- 当需要创建的对象是一些列相互关联或相互依赖的产品族时,如电器工厂中的电视机、洗衣机、空调等。
- 系统中有多个产品族,但每次只使用其中的某一族产品。如有人只喜欢穿某一个品牌的衣服和鞋。
- 系统中提供了产品的类库,其所有产品的接口相同,客户端不依赖产品实例的创建细节和内部结构。
2.4.5 模式的扩展
抽象工厂模式的扩展有一定的“开闭原则”倾斜性:
- 当增加一个新的产品族时只需要一个新的具体工厂,不需要修改原代码,满足开闭原则。
- 当产品族中需要增加一个新种类的产品时,则所以的工厂类都需要进行修改,不满足开闭原则。
另一方面,当系统中只存在一个等级结构的产品时,抽象工厂模式将退化到工厂方法模式。
2.5 建造者(Builder)模式
在软件开发过程中有时需要创建一个复杂对象,这个复杂对象通常由多个子部件按一定的步骤组成而已。例如,计算机有 CPU、主板、内存、硬盘、显卡、机箱、显示器、键盘、鼠标等部件组装而成的,采购员不可能自己去组装计算机,而是将计算机的配置要求告诉计算机销售公司,计算机销售公司安排技术人员去组装计算机,然后再交给要买计算机的采购员。
生活中这样的例子很多,如游戏中的不同角色,其性别、个性、能力、脸型、体型、服装、发型等特征都有所差异;还有汽车中的方向盘、发动机、车架、轮胎等部件也多种多样;每封电子邮件的发件人、收件人、主题、内容、附件等内容各不相同。
以上所有这些产品都是由多个部件构成的,各个部件可以灵活选择,但其创建步骤都大同小异。这类产品的创建无法用前面介绍的工厂模式描述,只有创建者模式可以很好地描述该类产品的创建。
2.5.1 模式的定义与特点
建造者模式定义:指将一个复杂对象的构造与它的表示分离,使同样的构建过程可以创建不同的表示,这样的设计模式被称为建造者。它使将一个复杂的对象分解为多个简单的对象,然后一步步构建而成。它将变与不变相分离,即产品的组成部分是不变得,但每一部分是可以灵活选择的。
该模式的主要优点如下:
- 各个具体建造者相互独立,有利于系统的扩展。
- 客户端不必知道产品内部组成的细节,便于口直细节风险。
其缺点如下:
- 产品的组成部分必须相同,这限制了其使用的范围。
- 如果产品的内部变化负责,该模式会增加很多的建造者类。
建造者模式和工厂模式的关注点不同:建造者模式注重零部件的组装过程,而工厂方法模式更注重零 部件的创建过程,但两者可以结合使用。
2.5.2 模式的结构与实现
建造者模式由产品、抽象建造者、具体建造者、指挥者等4个要素构成,现在我们来分析其基本结构和实现方法。
(1)模式的结构
建造者模式的主要角色如下。
- 产品角色:它是包含多个组成部件的复杂对象,有具体建造者来创建其各个部件。
- 抽象建造者:它是一个包含创建产品各个子部件的抽象方法的接口,通常还包含一个返回复杂产品的方法 getResult()。
- 具体建造者:实现 Builder 接口,完成复杂产品的各个部件的具体创建方法。
- 指挥者:它调用建造者对象中的部件构造与装配方法完成复杂对象的创建,在指挥者中不涉及具体产品的信息。
其结构图如图2-9所示:
图2-9 建造者模式的结构图
(2)模式的实现
图2-9 给出了建造者模式的主要结构,其相关类的代码如下。
① 产品角色:包含多个组成部件的复杂对象。
1 class Product { 2 private String partA; 3 private String partB; 4 private String partC; 5 public void setPartA(String partA) { 6 this.partA=partA; 7 } 8 public void setPartB(String partB) { 9 this.partB=partB; 10 } 11 public void setPartC(String partC) { 12 this.partC=partC; 13 } 14 public void show() { 15 //显示产品的特性 16 } 17 }
② 抽象建造者:包含创建产品各个子部件的抽象方法。
1 abstract class Builder { 2 //创建产品对象 3 protected Product product=new Product(); 4 public abstract void buildPartA(); 5 public abstract void buildPartB(); 6 public abstract void buildPartC(); 7 //返回产品对象 8 public Product getResult() { 9 return product; 10 } 11 }
③ 具体建造者:实现了抽象建造者接口。
1 public class ConcreteBuilder extends Builder { 2 public void buildPartA() { 3 product.setPartA("建造 PartA"); 4 } 5 public void buildPartB() { 6 product.setPartA("建造 PartB"); 7 } 8 public void buildPartC() { 9 product.setPartA("建造 PartC"); 10 } 11 }
④ 指挥者:调用建造者的方法完成复杂对象的创建。
1 class Director { 2 private Builder builder; 3 public Director(Builder builder) { 4 this.builder=builder; 5 } 6 //产品构建与组装方法 7 public Product construct() { 8 builder.buildPartA(); 9 builder.buildPartB(); 10 builder.buildPartC(); 11 return builder.getResult(); 12 } 13 }
⑤ 客户类。
1 public class Client { 2 public static void main(String[] args) { 3 Builder builder = new ConcreteBuilder(); 4 Director director = new Director(builder); 5 Product product = director.construct(); 6 product.show(); 7 } 8 }
2.5.3 模式的应用实例
例:用建造者模式描述客厅装修。
分析:客厅装修是一个复杂的过程,它包含墙体的装修、电视机的选择、沙发的购买与布局等。客户把装修要求告诉项目经理,项目经理指挥装修工人一步步装修,最后完成整个客厅的装修与布局,所以本实例用建造者模式实现比较适合。
这里客厅是产品,包含墙、电视和沙发等组成部分。具体装修工人是具体建造者,它们负责装修与墙、电视和沙发的布局。项目经理指挥者,它负责指挥装修工人进行装修。其类图如图2-10所示。
图2-10 客厅装修的结构图
程序代码如下:
1 // 产品:客厅 2 class Parlour { 3 private String wall; //墙 4 private String TV; //电视 5 private String sofa; //沙发 6 public void setWall(String wall) { 7 this.wall = wall; 8 } 9 public void setTV(String TV) { 10 this.TV = TV; 11 } 12 public void setSofa(String sofa) { 13 this.sofa = sofa; 14 } 15 public void show() { 16 System.out.println("开始装修客厅"); 17 } 18 }
1 // 抽象建造者:装修工人 2 abstract class Decorator { 3 // 创建产品对象 4 protected Parlour product = new Parlour(); 5 publicabstract void buildWall(); 6 publicabstract void buildTV(); 7 publicabstract void buildSofa(); 8 //返回产品对象 9 public Parlour getResult() { 10 return product; 11 } 12 }
1 // 具体建造者:具体装修工人1 2 class ConcreteDecorator1 extends Decorator { 3 public void buildWall() { 4 product.setWall("w1"); 5 } 6 public void buildTV() { 7 product.setTV("TV1"); 8 } 9 public void buildSofa() { 10 product.setSofa("sf1"); 11 } 12 }
1 //具体建造者:具体装修工人2 2 class ConcreteDecorator2 extends Decorator { 3 public void buildWall() { 4 product.setWall("w2"); 5 } 6 public void buildTV() { 7 product.setTV("TV2"); 8 } 9 public void buildSofa() { 10 product.setSofa("sf2"); 11 } 12 }
1 //指挥者:项目经理 2 class ProjectManager { 3 private Decorator builder; 4 public ProjectManager(Decorator builder) { 5 this.builder = builder; 6 } 7 //产品构建与组装方法 8 public Parlour decorate() { 9 builder.buildWall(); 10 builder.buildTV(); 11 builder.buildSofa(); 12 return builder.getResult(); 13 } 14 }
2.5.4 模式的应用场景
建造者模式创建的是负责对象,其产品的各个部分经常面临着剧烈的变化,但将它们组合在一起的算法却相对稳定,所以它通常在以下场合使用。
- 创建的对象较复杂,由多个部件构成,各部件面临着复杂的变化,但构件间的建造顺序是稳定的。
- 创建复杂对象的算法独立于该对象的组成部分以及它们的装配方式,即产品的构建过程和最终的表示独立的。
2.5.5 模式的扩展
建造者模式在应用过程中可以根据需要改变,如果创建的产品种类只有一种,只需要一个具体建造者,这时可以省略掉抽象建造者,甚至可以省略指挥者角色。