导图社区 23种设计模式知识导图
本导图归纳了23种设计模式的来源、应用场景、结构、UML类图与实现,此外还包含了J2EE设计模式内容,希望梳理的内容对你有所帮助!
编辑于2021-09-05 08:37:03介绍归纳 C Sharp 语音的基础重点知识。包括语言基础、字面常量、程序集、不安全代码、基础类、枚举、数组、泛型、字符串、正则表达式、委托与事件、文件、异常、多线程、异步、反射、网络、绘图、WinForm、Windows、跨平台调用等内容。思维导图示例中,有示例代码,方便学习与练习。
这份思维导图归纳了一些HTML基本的元素标签、布局、表单,以及 HTML5 API 如 WebSockets、Fetch API 等内容。CSS 主要是归纳了选择器。JavaScript 主要是包含了函数与箭头函数、this 关键字、Promise 异步对象。此外还有AJAX、jQuery 与 jQuery AJAX、JSONP 等内容。导图中的注释有很多相关的详细说明与示例代码,其中后端的测试代码是用的 PHP。希望能帮到大家!
WPF开发相关的笔记。WPF基本概念、XAML基本语法、控件与布局、Binding、依赖属性与附加属性、路由事件与附加事件、命令、资源、模板与样式、2D绘图与动画、3D绘图等内容。导图中的注释还有很多相关的详细说明与示例代码,希望能帮到大家!
社区模板帮助中心,点此进入>>
介绍归纳 C Sharp 语音的基础重点知识。包括语言基础、字面常量、程序集、不安全代码、基础类、枚举、数组、泛型、字符串、正则表达式、委托与事件、文件、异常、多线程、异步、反射、网络、绘图、WinForm、Windows、跨平台调用等内容。思维导图示例中,有示例代码,方便学习与练习。
这份思维导图归纳了一些HTML基本的元素标签、布局、表单,以及 HTML5 API 如 WebSockets、Fetch API 等内容。CSS 主要是归纳了选择器。JavaScript 主要是包含了函数与箭头函数、this 关键字、Promise 异步对象。此外还有AJAX、jQuery 与 jQuery AJAX、JSONP 等内容。导图中的注释有很多相关的详细说明与示例代码,其中后端的测试代码是用的 PHP。希望能帮到大家!
WPF开发相关的笔记。WPF基本概念、XAML基本语法、控件与布局、Binding、依赖属性与附加属性、路由事件与附加事件、命令、资源、模板与样式、2D绘图与动画、3D绘图等内容。导图中的注释还有很多相关的详细说明与示例代码,希望能帮到大家!
设计模式
概念
简介
设计模式(Design Pattern)代表了最佳的实践
设计模式是软件开发人员在软件开发过程中面临的一般问题的解决方案
GoF (Gang of Four)
指 Designs - Elements of Reusable Object-Oriented Software 书籍四位作者
主要原则
对接口编程而不是对实现编程
优先使用对象组合而不是继承
软件设计七大原则
目的
降低对象之间的耦合,增加程序的可复用性、可扩展性和可维护性。
分类
开闭原则
概念
对扩展开放,对修改关闭
目的
降低维护带来的新风险
依赖倒置原则
概念
高层不应该依赖低层,要面向接口编程
目的
更利于代码结构的升级扩展
单一职责原则
概念
一个类只干一件事,类的职责要单一
目的
便于理解,提高代码的可读性
接口隔离原则
概念
一个接口只干一件事,使类对接口的依赖精简单一
目的
功能解耦,高聚合、低耦合
迪米特原则
概念
不该知道的不要知道,一个类应该保持对其它对象最少的了解,降低耦合度
目的
只和朋友交流,不和陌生人说话,减少代码臃肿
里氏替换原则
概念
不要破坏继承体系,子类重写方法功能发生改变,不应该影响父类方法的含义
目的
防止继承泛滥
合成复用原则
概念
尽量使用组合或者聚合关系实现代码复用,少使用继承
目的
降低代码耦合
分类
按目的
根据模式是用来完成什么工作。
创建型模式
用于描述“怎样创建对象”,它的主要特点是“将对象的创建与使用分离”
结构型模式
用于描述如何将类或对象按某种布局组成更大的结构
行为型模式
用于描述类或对象之间怎样相互协作共同完成单个对象都无法单独完成的任务,以及怎样分配职责
按作用范围
根据模式是主要用于类上还是主要用于对象上。
类模式
用于处理类与子类之间的关系,这些关系通过继承来建立,是静态的,在编译时刻便确定下来了
对象模式
用于处理对象之间的关系,这些关系可以通过组合或聚合来实现,在运行时刻是可以变化的,更具动态性
表格
关系图
概要
23种设计模式
创建型模式(Creationals)
说明
关注创建对象的方法,特殊的方法对应特殊的创建逻辑。
分类
单例模式(Singleton)
来源
在有些系统中,为了节省内存资源、保证数据内容的一致性,对某些类要求只能创建一个实例,这就是所谓的单例模式。
定义
指一个类只有一个实例,且该类能自行创建这个实例。
特点
单例类只有一个实例对象;
该单例对象必须由单例类自行创建;
单例类对外提供一个访问该单例的全局访问点。
优点
单例模式可以保证内存里只有一个实例,减少了内存的开销。
可以避免对资源的多重占用。
单例模式设置全局访问点,可以优化和共享资源的访问。
缺点
单例模式一般没有接口,扩展困难。如果要扩展,则除了修改原来的代码,没有第二种途径,违背开闭原则。
在并发测试中,单例模式不利于代码调试。在调试过程中,如果单例中的代码没有执行完,也不能模拟生成一个新的对象。
单例模式的功能代码通常写在一个类中,如果功能设计不合理,则很容易违背单一职责原则。
应用场景
需要频繁创建的一些类,使用单例可以降低系统的内存压力,减少 GC。
某类只要求生成一个对象的时候,如一个班中的班长、每个人的身份证号等。
某些类创建实例时占用资源较多,或实例化耗时较长,且经常使用。
某类需要频繁实例化,而创建的对象又频繁被销毁的时候,如多线程的线程池、网络连接池等。
频繁访问数据库或文件的对象。
对于一些控制硬件级别的操作,或者从系统上来讲应当是单一控制逻辑的操作,如果有多个实例,则系统会完全乱套。
当对象需要被共享的场合。由于单例模式只允许创建一个对象,共享该对象可以节省内存,并加快对象访问速度。如 Web 中的配置对象、数据库的连接池等。
结构
主要角色
单例类
包含一个实例且能自行创建这个实例的类。
访问类
使用单例的类。
图示

实现
懒汉式单例
特点
类加载时没有生成单例,只有当第一次调用 getlnstance 方法时才去创建这个单例。
需采取额外措施保证线程安全
例:
方式一:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace DesignePattern { public class President //懒汉式单例类 { private static volatile President instance = null; //使用volatile防止指令重新排序 private static readonly object padLock = new object(); private President() { } public static President GetInstance() { if (null == instance) //双重检查锁。第1次检查,减少二次访问性能消耗 { lock (padLock) //加锁 { if (null == instance) //第2次检查 { instance = new President(); } } } return instance; } public void GetName() { Console.WriteLine("我是总统:某某某。"); } } class Client { static void Main(string[] args) { President zt1 = President.GetInstance(); zt1.GetName(); //输出总统的名字 President zt2 = President.GetInstance(); zt2.GetName(); //输出总统的名字 if (zt1 == zt2) { Console.WriteLine("他们是同一人!"); } else { Console.WriteLine("他们不是同一人!"); } Console.Read(); } } }
方式二:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace DesignePattern { public class President //懒汉式单例类 { private static readonly Lazy<President> instanceLazy = new Lazy<President>(() => new President()); //使用Lazy<T> private President() { } public static President GetInstance() { return instanceLazy.Value; } public void GetName() { Console.WriteLine("独一无二?"); } } class Client { static void Main(string[] args) { President zt1 = President.GetInstance(); zt1.GetName(); //输出单例的名字 President zt2 = President.GetInstance(); zt2.GetName(); //输出单例的名字 if (zt1 == zt2) { Console.WriteLine("是同一个!"); } else { Console.WriteLine("不是同一个!"); } Console.Read(); } } }
饿汉式单例
特点
类一旦加载就创建一个单例,保证在调用 getInstance 方法之前单例已经存在了。
默认线程安全
例:
方式一:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace DesignePattern { public class President //饿汉式单例类 { private static readonly President instance = new President(); private President() { } //可以显式定义静态构造方法让编译器取消“beforefieldinit”标记,从而使字段的实例化延迟(不过后续访问字段时还会检测静态构造方法是否已被执行的标志位,效率稍低)。 static President() { } public static President GetInstance() { return instance; } public void GetName() { Console.WriteLine("独一无二?"); } } class Client { static void Main(string[] args) { President zt1 = President.GetInstance(); zt1.GetName(); //输出单例的名字 President zt2 = President.GetInstance(); zt2.GetName(); //输出单例的名字 if (zt1 == zt2) { Console.WriteLine("是同一个!"); } else { Console.WriteLine("不是同一个!"); } Console.Read(); } } }
方式二:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace DesignePattern { public class President //饿汉式单例类 { public static President Instance { get; } = new President(); //C# 6.0自动属性初始化语法糖 private President() { } public void GetName() { Console.WriteLine("独一无二?"); } } class Client { static void Main(string[] args) { President zt1 = President.Instance; zt1.GetName(); //输出单例的名字 President zt2 = President.Instance; zt2.GetName(); //输出单例的名字 if (zt1 == zt2) { Console.WriteLine("是同一个!"); } else { Console.WriteLine("不是同一个!"); } Console.Read(); } } }
使用枚举类型实现单例
Java
JDK 1.5之后,可使用单元素的枚举类型实现单例。
特点
不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象。
例:
class President { public void GetName() { System.out.println("独一无二?"); } } enum PresidentSingleton { INSTANCE; //单元素单示例 private President instance; private PresidentSingleton() { instance = new President(); } public President GetInstance() { return instance; } } public class Client { public static void main(String[] args) { President zt1 = PresidentSingleton.INSTANCE.GetInstance(); zt1.GetName(); //输出单例的名字 President zt2 = PresidentSingleton.INSTANCE.GetInstance(); zt2.GetName(); //输出单例的名字 if (zt1 == zt2) { System.out.println("是同一个!"); } else { System.out.println("不是同一个!"); } } }
扩展
有限的多例(Multitcm)模式
特点
这种模式可生成有限个实例并保存在 ArrayList 中,客户需要时可随机获取
图示

例:
Java
class President { public void GetName() { System.out.println("独一无二?"); } } enum PresidentSingleton { INSTANCE, INSTANCE2, INSTANCE3; //多元素有限多例 private President instance; private PresidentSingleton() { instance = new President(); } public President GetInstance() { return instance; } } public class Client { public static void main(String[] args) { President zt1 = PresidentSingleton.INSTANCE.GetInstance(); zt1.GetName(); //输出单例的名字 President zt2 = PresidentSingleton.INSTANCE.GetInstance(); zt2.GetName(); //输出单例的名字 if (zt1 == zt2) { System.out.println("是同一个!"); } else { System.out.println("不是同一个!"); } President zt3 = PresidentSingleton.INSTANCE2.GetInstance(); zt3.GetName(); //输出单例的名字 President zt4 = PresidentSingleton.INSTANCE2.GetInstance(); zt4.GetName(); //输出单例的名字 if (zt3 == zt4) { System.out.println("是同一个!"); } else { System.out.println("不是同一个!"); } President zt5 = PresidentSingleton.INSTANCE3.GetInstance(); zt5.GetName(); //输出单例的名字 President zt6 = PresidentSingleton.INSTANCE3.GetInstance(); zt6.GetName(); //输出单例的名字 if (zt5 == zt6) { System.out.println("是同一个!"); } else { System.out.println("不是同一个!"); } if (zt1 == zt3) { System.out.println("是同一个!"); } else { System.out.println("不是同一个!"); } if (zt3 == zt6) { System.out.println("是同一个!"); } else { System.out.println("不是同一个!"); } } }
原型模式(Prototype)
来源
在有些系统中,存在大量相同或相似对象的创建问题,如果用传统的构造函数来创建对象,会比较复杂且耗时耗资源,用原型模式生成对象就很高效,就像孙悟空拔下猴毛轻轻一吹就变出很多孙悟空一样简单。
定义
用一个已经创建的实例作为原型,通过复制该原型对象来创建一个和原型相同或相似的新对象。在这里,原型实例指定了要创建的对象的种类。
特点
用这种方式创建对象非常高效,根本无须知道对象创建的细节。
优点
Java 自带的原型模式基于内存二进制流的复制,在性能上比直接 new 一个对象更加优良。
可以使用深克隆方式保存对象的状态,使用原型模式将对象复制一份,并将其状态保存起来,简化了创建对象的过程,以便在需要的时候使用(例如恢复到历史某一状态),可辅助实现撤销操作。
缺点
需要为每一个类都配置一个 clone 方法
clone 方法位于类的内部,当对已有类进行改造的时候,需要修改代码,违背了开闭原则。
当实现深克隆时,需要编写较为复杂的代码,而且当对象之间存在多重嵌套引用时,为了实现深克隆,每一层对象对应的类都必须支持深克隆,实现起来会比较麻烦。因此,深克隆、浅克隆需要运用得当。
应用场景
对象之间相同或相似,即只是个别的几个属性不同的时候。
创建对象成本较大,例如初始化时间长,占用CPU太多,或者占用网络资源太多等,需要优化资源。
创建一个对象需要繁琐的数据准备或访问权限等,需要提高性能或者提高安全性。
系统中大量使用该类对象,且各个调用者都需要给它的属性重新赋值。
结构
主要角色
说明
原型模式的克隆分为浅克隆和深克隆。
抽象原型类
规定了具体原型对象必须实现的接口。
具体原型类
实现抽象原型类的 clone() 方法,它是可被复制的对象。
访问类
使用具体原型类中的 clone() 方法来复制新的对象。
图示

实现
抽象原型类
public interface ICloneable { object Clone(); }
具体原型类
public class Realizetype : ICloneable { public Realizetype() { Console.WriteLine("具体原型创建成功!"); } public object Clone() { Console.WriteLine("具体原型复制成功!"); return (Realizetype)this.MemberwiseClone(); } }
访问类
class Client { static void Main(string[] args) { Realizetype obj1 = new Realizetype(); Realizetype obj2 = (Realizetype)obj1.Clone(); Console.WriteLine("obj1==obj2? " + (obj1 == obj2)); Console.Read(); } }
实例
扩展
带原型管理器的原型模式
特点
它在原型模式的基础上增加了一个原型管理器 PrototypeManager 类。该类用 HashMap 保存多个复制的原型,Client 类可以通过管理器的 get(String id) 方法从中获取复制的原型。
图示

例:
抽象原型类
public interface ICloneable { object Clone(); } public interface IShape : ICloneable { void CountArea(); }
具体原型类
class Circle : IShape { public object Clone() { return (Circle)this.MemberwiseClone(); } public void CountArea() { Console.Write("这是一个圆,请输入圆的半径:"); int r = int.Parse(Console.ReadLine()); Console.WriteLine("该圆的面积=" + 3.1415 * r * r); } } class Square : IShape { public object Clone() { return (Square)this.MemberwiseClone(); } public void CountArea() { Console.Write("这是一个正方形,请输入它的边长:"); int a = int.Parse(Console.ReadLine()); Console.WriteLine("该正方形的面积=" + a * a); } }
原型管理器类
class ProtoTypeManager { private Dictionary<string, IShape> dt = new Dictionary<string, IShape>(); public ProtoTypeManager() { dt.Add("Circle", new Circle()); dt.Add("Square", new Square()); } public void AddShape(string key, IShape obj) { dt.Add(key, obj); } public IShape GetShape(string key) { IShape temp = dt[key]; return (IShape)temp.Clone(); } }
访问类
class Client { static void Main(string[] args) { ProtoTypeManager pm = new ProtoTypeManager(); IShape obj1 = (Circle)pm.GetShape("Circle"); obj1.CountArea(); IShape obj2 = (Square)pm.GetShape("Square"); obj2.CountArea(); Console.Read(); } }
工厂模式(Factory)
概念
来源
现实生活中,原始社会自给自足(没有工厂),农耕社会小作坊(简单工厂,民间酒坊),工业革命流水线(工厂方法,自产自销),现代产业链代工厂(抽象工厂,富士康)。我们的项目代码同样是由简到繁一步一步迭代而来的,但对于调用者来说,却越来越简单。 在日常开发中,凡是需要生成复杂对象的地方,都可以尝试考虑使用工厂模式来代替。
上述复杂对象指的是类的构造函数参数过多等对类的构造有影响的情况,因为类的构造过于复杂,如果直接在其他业务类内使用,则两者的耦合过重,后续业务更改,就需要在任何引用该类的源代码内进行更改,光是查找所有依赖就很消耗时间了,更别说要一个一个修改了。
定义
定义一个创建产品对象的工厂接口,将产品对象的实际创建工作推迟到具体子工厂类当中。
特点
满足创建型模式中所要求的“创建与使用相分离”的特点。
我们把被创建的对象称为“产品”,把创建产品的对象称为“工厂”。
划分
按实际业务场景划分,工厂模式有 3 种不同的实现方式
简单工厂模式
工厂方法模式
抽象工厂模式
优点
缺点
应用场景
结构
主要角色
图示
实现
实例
扩展
分类
简单工厂模式(Simple Factory)
来源
定义
如果要创建的产品不多,只要一个工厂类就可以完成,这种模式叫“简单工厂模式”。
在简单工厂模式中创建实例的方法通常为静态(static)方法,因此简单工厂模式(Simple Factory Pattern)又叫作静态工厂方法模式(Static Factory Method Pattern)。
特点
简单来说,简单工厂模式有一个具体的工厂类,可以生成多个不同的产品,属于创建型设计模式。
简单工厂模式不在 GoF 23 种设计模式之列。
优点
工厂类包含必要的逻辑判断,可以决定在什么时候创建哪一个产品的实例。客户端可以免除直接创建产品对象的职责,很方便的创建出相应的产品。工厂和产品的职责区分明确。
客户端无需知道所创建具体产品的类名,只需知道参数即可。
也可以引入配置文件,在不修改客户端代码的情况下更换和添加新的具体产品类。
缺点
简单工厂模式的工厂类单一,负责所有产品的创建,职责过重,一旦异常,整个系统将受影响。且工厂类代码会非常臃肿,违背高聚合原则。
使用简单工厂模式会增加系统中类的个数(引入新的工厂类),增加系统的复杂度和理解难度。
系统扩展困难,一旦增加新产品不得不修改工厂逻辑,在产品类型较多时,可能造成逻辑过于复杂。所以说从工厂的角度来说简单工厂模式是不符合“开-闭”原则的。
简单工厂模式使用了 static 工厂方法,造成工厂角色无法形成基于继承的等级结构。
应用场景
对于产品种类相对较少的情况,考虑使用简单工厂模式。使用简单工厂模式的客户端只需要传入工厂类的参数,不需要关心如何创建对象的逻辑,可以很方便地创建所需产品。
结构
主要角色
抽象产品(Product)
是简单工厂模式的核心,负责实现创建所有实例的内部逻辑。工厂类的创建产品类的方法可以被外界直接调用,创建所需的产品对象。
具体产品(ConcreteProduct)
是简单工厂创建的所有对象的父类,负责描述所有实例共有的公共接口。
简单工厂(SimpleFactory)
是简单工厂模式的创建目标。
图示

实现
抽象产品
public interface IProduct { void Show(); }
具体产品
public class ConcreteProduct1 : IProduct { public void Show() { Console.WriteLine("具体产品1显示..."); } } public class ConcreteProduct2 : IProduct { public void Show() { Console.WriteLine("具体产品2显示..."); } }
简单工厂
public static class SimpleFactory { public static IProduct MakeProduct(string kind) { switch (kind) { case "具体产品1": return new ConcreteProduct1(); case "具体产品2": return new ConcreteProduct2(); } return null; } }
客户端
class Client { static void Main(string[] args) { IProduct product1 = SimpleFactory.MakeProduct("具体产品1"); product1.Show(); IProduct product2 = SimpleFactory.MakeProduct("具体产品2"); product2.Show(); Console.Read(); } }
实例
扩展
工厂方法模式(Factory Method)(类)
来源
定义
“工厂方法模式”是对简单工厂模式的进一步抽象化,其好处是可以使系统在不修改原来代码的情况下引进新的产品,即满足开闭原则。
特点
优点
用户只需要知道具体工厂的名称就可得到所要的产品,无须知道产品的具体创建过程。
灵活性增强,对于新产品的创建,只需多写一个相应的工厂类。
典型的解耦框架。高层模块只需要知道产品的抽象类,无须关心其他实现类,满足迪米特法则、依赖倒置原则和里氏替换原则。
缺点
类的个数容易过多,增加复杂度
增加了系统的抽象性和理解难度
抽象产品只能生产一种产品,此弊端可使用抽象工厂模式解决。
应用场景
客户只知道创建产品的工厂名,而不知道具体的产品名。如 TCL 电视工厂、海信电视工厂等。
创建对象的任务由多个具体子工厂中的某一个完成,而抽象工厂只提供创建产品的接口。
客户不关心创建产品的细节,只关心产品的品牌
结构
主要角色
抽象产品(Product)
定义了产品的规范,描述了产品的主要特性和功能。
具体产品(ConcreteProduct)
实现了抽象产品角色所定义的接口,由具体工厂来创建,它同具体工厂之间一一对应。
抽象工厂(Abstract Factory)
提供了创建产品的接口,调用者通过它访问具体工厂的工厂方法 newProduct() 来创建产品。
具体工厂(ConcreteFactory)
主要是实现抽象工厂中的抽象方法,完成具体产品的创建。
图示

实现
抽象产品
public interface IProduct { void Show(); }
具体产品
public class ConcreteProduct1 : IProduct { public void Show() { Console.WriteLine("具体产品1显示..."); } } public class ConcreteProduct2 : IProduct { public void Show() { Console.WriteLine("具体产品2显示..."); } }
抽象工厂
public interface AbstractFactory { IProduct NewProduct(); }
具体工厂
public class ConcreteFactory1 : AbstractFactory { public IProduct NewProduct() { Console.WriteLine("具体工厂1生成-->具体产品1..."); return new ConcreteProduct1(); } } public class ConcreteFactory2 : AbstractFactory { public IProduct NewProduct() { Console.WriteLine("具体工厂2生成-->具体产品2..."); return new ConcreteProduct2(); } }
客户端
class Client { static void Main(string[] args) { AbstractFactory f1 = new ConcreteFactory1(); IProduct p1 = f1.NewProduct(); p1.Show(); AbstractFactory f2 = new ConcreteFactory2(); IProduct p2 = f2.NewProduct(); p2.Show(); Console.Read(); } }
实例
扩展
抽象工厂模式(Abstract Factory)
来源
同种类称为同等级,也就是说:工厂方法模式只考虑生产同等级的产品,但是在现实生活中许多工厂是综合型的工厂,能生产多等级(种类) 的产品,如农场里既养动物又种植物,电器厂既生产电视机又生产洗衣机或空调,大学既有软件专业又有生物专业等。 本节要介绍的抽象工厂模式将考虑多等级产品的生产,将同一个具体工厂所生产的位于不同等级的一组产品称为一个产品族,图 1 所示的是海尔工厂和 TCL 工厂所生产的电视机与空调对应的关系图。
定义
是一种为访问类提供一个创建一组相关或相互依赖对象的接口,且访问类无须指定所要产品的具体类就能得到同族的不同等级的产品的模式结构。
特点
抽象工厂模式是工厂方法模式的升级版本,工厂方法模式只生产一个等级的产品,而抽象工厂模式可生产多个等级的产品。
要求条件
系统中有多个产品族,每个具体工厂创建同一族但属于不同等级结构的产品。
系统一次只可能消费其中某一族产品,即同族的产品一起使用。
优点
可以在类的内部对产品族中相关联的多等级产品共同管理,而不必专门引入多个新的类来进行管理。
当需要产品族时,抽象工厂可以保证客户端始终只使用同一个产品的产品族。
抽象工厂增强了程序的可扩展性,当增加一个新的产品族时,不需要修改原代码,满足开闭原则。
缺点
当产品族中需要增加一个新的产品时,所有的工厂类都需要进行修改。增加了系统的抽象性和理解难度。
应用场景
抽象工厂模式最早的应用是用于创建属于不同操作系统的视窗构件。如 Java 的 AWT 中的 Button 和 Text 等构件在 Windows 和 UNIX 中的本地实现是不同的。
当需要创建的对象是一系列相互关联或相互依赖的产品族时,如电器工厂中的电视机、洗衣机、空调等。
系统中有多个产品族,但每次只使用其中的某一族产品。如有人只喜欢穿某一个品牌的衣服和鞋。
系统中提供了产品的类库,且所有产品的接口相同,客户端不依赖产品实例的创建细节和内部结构。
结构
主要角色
抽象产品(Product)
定义了产品的规范,描述了产品的主要特性和功能,抽象工厂模式有多个抽象产品。
具体产品(ConcreteProduct)
实现了抽象产品角色所定义的接口,由具体工厂来创建,它同具体工厂之间是多对一的关系。
抽象工厂(Abstract Factory)
提供了创建产品的接口,它包含多个创建产品的方法 newProduct(),可以创建多个不同等级的产品。
具体工厂(Concrete Factory)
主要是实现抽象工厂中的多个抽象方法,完成具体产品的创建。
图示

实现
抽象产品
public interface IAnimal { void Show(); } public interface IPlant { void Show(); }
具体产品
public class Fruitage : IPlant { public void Show() { Console.WriteLine("水果显示..."); } } public class Vegetables : IPlant { public void Show() { Console.WriteLine("蔬菜显示..."); } } public class Horse : IAnimal { public void Show() { Console.WriteLine("马显示..."); } } public class Cattle : IAnimal { public void Show() { Console.WriteLine("牛显示..."); } }
抽象工厂
public interface IFarm { IAnimal NewAnimal(); IPlant NewPlant(); }
具体工厂
public class SGfarm : IFarm { public IAnimal NewAnimal() { Console.WriteLine("新马出生!"); return new Horse(); } public IPlant NewPlant() { Console.WriteLine("水果长成!"); return new Fruitage(); } } public class SRfarm : IFarm { public IAnimal NewAnimal() { Console.WriteLine("新牛出生!"); return new Cattle(); } public IPlant NewPlant() { Console.WriteLine("蔬菜长成!"); return new Vegetables(); } }
客户端
class Client { static void Main(string[] args) { IFarm sgf = new SGfarm(); IAnimal sga = sgf.NewAnimal(); IPlant sgp = sgf.NewPlant(); sga.Show(); sgp.Show(); IFarm srf = new SRfarm(); IAnimal sra = srf.NewAnimal(); IPlant srp = srf.NewPlant(); sra.Show(); srp.Show(); Console.Read(); } }
实例
扩展
扩展有一定的“开闭原则”倾斜性:
当增加一个新的产品族时只需增加一个新的具体工厂,不需要修改原代码,满足开闭原则。
当产品族中需要增加一个新种类的产品(即产品等级)时,则所有的工厂类都需要进行修改,不满足开闭原则。
另一方面,当系统中只存在一个等级结构的产品时,抽象工厂模式将退化到工厂方法模式。
建造者模式(Builder)
来源
在软件开发过程中有时需要创建一个复杂的对象,这个复杂对象通常由多个子部件按一定的步骤组合而成。例如,计算机是由 CPU、主板、内存、硬盘、显卡、机箱、显示器、键盘、鼠标等部件组装而成的,采购员不可能自己去组装计算机,而是将计算机的配置要求告诉计算机销售公司,计算机销售公司安排技术人员去组装计算机,然后再交给要买计算机的采购员。 以上所有这些产品都是由多个部件构成的,各个部件可以灵活选择,但其创建步骤都大同小异。这类产品的创建无法用前面介绍的工厂模式描述,只有建造者模式可以很好地描述该类产品的创建。
定义
建造者模式也叫做生成器模式,指将一个复杂对象的构造与它的表示分离,使同样的构建过程可以创建不同的表示。
特点
它是将一个复杂的对象分解为多个简单的对象,然后一步一步构建而成。它将变与不变相分离,即产品的组成部分是不变的,但每一部分是可以灵活选择的。
优点
封装性好,构建和表示分离。
扩展性好,各个具体的建造者相互独立,有利于系统的解耦。
客户端不必知道产品内部组成的细节,建造者可以对创建过程逐步细化,而不对其它模块产生任何影响,便于控制细节风险。
缺点
产品的组成部分必须相同,这限制了其使用范围。
如果产品的内部变化复杂,如果产品内部发生变化,则建造者也要同步修改,后期维护成本较大。
应用场景
当需要创建的产品具备复杂创建过程时,可以抽取出共性创建过程,然后交由具体实现类自定义创建流程,使得同样的创建行为可以生产出不同的产品,分离了创建与表示,使创建产品的灵活性大大增加。
主要场景
相同的方法,不同的执行顺序,产生不同的结果。
多个部件或零件,都可以装配到一个对象中,但是产生的结果又不相同。
产品类非常复杂,或者产品类中不同的调用顺序产生不同的作用。
初始化一个对象特别复杂,参数多,而且很多参数都具有默认值。
与工厂模式区别
建造者模式是针对复杂对象的创建。也就是说,如果创建简单对象,通常都是使用工厂模式进行创建,而如果创建复杂对象,就可以考虑使用建造者模式。
建造者模式注重零部件的组装过程,而工厂方法模式更注重零部件的创建过程,但两者可以结合使用。
建造者模式更加注重方法的调用顺序,工厂模式注重创建对象。
创建对象的力度不同,建造者模式创建复杂的对象,由各种复杂的部件组成,工厂模式创建出来的对象都一样
关注重点不一样,工厂模式只需要把对象创建出来就可以了,而建造者模式不仅要创建出对象,还要知道对象由哪些部件组成。
建造者模式根据建造过程中的顺序不一样,最终对象部件组成也不一样。
结构
主要角色
产品(Product)
它是包含多个组成部件的复杂对象,由具体建造者来创建其各个零部件。
抽象建造者(Builder)
它是一个包含创建产品各个子部件的抽象方法的接口,通常还包含一个返回复杂产品的方法 getResult()。
具体建造者(Concrete Builder)
实现 Builder 接口,完成复杂产品的各个部件的具体创建方法。
指挥者(Director)
它调用建造者对象中的部件构造与装配方法完成复杂对象的创建,在指挥者中不涉及具体产品的信息。
图示

实现
产品
public class Product { private string partA; private string partB; private string partC; public void SetPartA(string partA) { this.partA = partA; } public void SetPartB(string partB) { this.partB = partB; } public void SetPartC(string partC) { this.partC = partC; } public void Show() { Console.WriteLine($"{partA} + {partB} + {partC}"); } }
抽象建造者
public abstract class Builder { protected Product product = new Product(); //创建产品对象 public abstract void BuildPartA(); public abstract void BuildPartB(); public abstract void BuildPartC(); public Product GetResult() //返回产品对象 { return product; } }
具体建造者
public class ConcreteBuilder : Builder { public override void BuildPartA() { product.SetPartA("零件A"); } public override void BuildPartB() { product.SetPartB("零件B"); } public override void BuildPartC() { product.SetPartC("零件C"); } }
指挥者
public class Director { private Builder builder; public Director(Builder builder) { this.builder = builder; } public Product Construct() //产品构建与组装方法 { builder.BuildPartA(); builder.BuildPartB(); builder.BuildPartC(); return builder.GetResult(); } }
客户端
class Client { static void Main(string[] args) { Builder builder = new ConcreteBuilder(); Director director = new Director(builder); Product product = director.Construct(); product.Show(); Console.Read(); } }
实例
扩展
建造者(Builder)模式在应用过程中可以根据需要改变,如果创建的产品种类只有一种,只需要一个具体建造者,这时可以省略掉抽象建造者,甚至可以省略掉指挥者角色。
结构型模式(Structurals)
说明
关注对象间的结构,即不同对象的组合。
分类
代理模式(Proxy)
来源
在有些情况下,一个客户不能或者不想直接访问另一个对象,这时需要找一个中介帮忙完成某项任务,这个中介就是代理对象。例如,购买火车票不一定要去火车站买,可以通过 12306 网站或者去火车票代售点买。又如找女朋友、找保姆、找工作等都可以通过找中介完成。 在软件设计中,使用代理模式的例子也很多,例如,要访问的远程对象比较大(如视频或大图像等),其下载要花很多时间。还有因为安全原因需要屏蔽客户端直接访问真实对象,如某单位的内部数据库等。
定义
由于某些原因需要给某对象提供一个代理以控制对该对象的访问。这时,访问对象不适合或者不能直接引用目标对象,代理对象作为访问对象和目标对象之间的中介。
特点
当无法或不想直接引用某个对象或访问某个对象存在困难时,可以通过代理对象来间接访问。
使用代理模式主要有两个目的:一是保护目标对象,二是增强目标对象。
优点
代理模式在客户端与目标对象之间起到一个中介作用和保护目标对象的作用;
代理对象可以扩展目标对象的功能;
代理模式能将客户端与目标对象分离,在一定程度上降低了系统的耦合度,增加了程序的可扩展性;
缺点
代理模式会造成系统设计中类的数量增加;
在客户端和目标对象之间增加一个代理对象,会造成请求处理速度变慢;
增加了系统的复杂度;
应用场景
远程代理,这种方式通常是为了隐藏目标对象存在于不同地址空间的事实,方便客户端访问。例如,用户申请某些网盘空间时,会在用户的文件系统中建立一个虚拟的硬盘,用户访问虚拟硬盘时实际访问的是网盘空间。
虚拟代理,这种方式通常用于要创建的目标对象开销很大时。例如,下载一幅很大的图像需要很长时间,因某种计算比较复杂而短时间无法完成,这时可以先用小比例的虚拟代理替换真实的对象,消除用户对服务器慢的感觉。
安全代理,这种方式通常用于控制不同种类客户对真实对象的访问权限。
智能指引,主要用于调用目标对象时,代理附加一些额外的处理功能。例如,增加计算真实对象的引用次数的功能,这样当该对象没有被引用时,就可以自动释放它。
延迟加载,指为了提高系统的性能,延迟对目标的加载。例如,Hibernate 中就存在属性的延迟加载和关联表的延时加载。
结构
主要角色
抽象主题(Subject)类
通过接口或抽象类声明真实主题和代理对象实现的业务方法。
真实主题(Real Subject)类
实现了抽象主题中的具体业务,是代理对象所代表的真实对象,是最终要引用的对象。
代理(Proxy)类
提供了与真实主题相同的接口,其内部含有对真实主题的引用,它可以访问、控制或扩展真实主题的功能。
图示

划分
根据代理的创建时期,代理模式分为
静态代理
由程序员创建代理类或特定工具自动生成源代码再对其编译,在程序运行前代理类的 .class 文件就已经存在了。
动态代理
在程序运行时,运用反射等机制动态创建代理类。
静态代理和动态代理的区别
静态代理只能通过手动完成代理操作,如果被代理类增加了新的方法,则代理类需要同步增加,违背开闭原则。
动态代理采用在运行时动态生成代码的方式,取消了对被代理类的扩展限制,遵循开闭原则。
若动态代理要对目标类的增强逻辑进行扩展,结合策略模式,只需要新增策略类便可完成,无需修改代理类的代码。
实现
静态代理
抽象主题类
public interface ISubject { void Request(); }
真实主题类
public class RealSubject : ISubject { public void Request() { Console.WriteLine("访问真实主题方法..."); } }
代理类
外部传入真实主题对象
public class Proxy : ISubject { private RealSubject realSubject; public Proxy(RealSubject realSubject) //外部传入真实主题对象 { this.realSubject = realSubject; } public void PreRequest() { Console.WriteLine("访问真实主题之前的预处理。"); } public void Request() { PreRequest(); realSubject.Request(); PostRequest(); } public void PostRequest() { Console.WriteLine("访问真实主题之后的后续处理。"); } }
内部创建真实主题对象
public class Proxy : ISubject { private RealSubject realSubject; public Proxy() { this.realSubject = new RealSubject(); //内部创建真实主题对象 } public void PreRequest() { Console.WriteLine("访问真实主题之前的预处理。"); } public void Request() { PreRequest(); realSubject.Request(); PostRequest(); } public void PostRequest() { Console.WriteLine("访问真实主题之后的后续处理。"); } }
客户端
外部传入真实主题对象
class Client { static void Main(string[] args) { RealSubject realSubject = new RealSubject(); Proxy proxy = new Proxy(realSubject); proxy.Request(); Console.Read(); } }
内部创建真实主题对象
class Client { static void Main(string[] args) { Proxy proxy = new Proxy(); proxy.Request(); Console.Read(); } }
实例
扩展
动态代理
静态代理类中包含了对真实主题的引用,这种方式存在两个缺点:
真实主题与代理主题一一对应,增加真实主题也要增加代理。
设计代理以前真实主题必须事先存在,不太灵活。
采用动态代理模式可以解决以上问题,如 SpringAOP
AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。比较常见的场景是:日志记录,错误捕获、性能监控等。
图示

例:
环境准备
通过NuGet安装Castle.Core
使用命名空间
using Castle.DynamicProxy;
抽象主题类
public interface ISubject { string Request(int a, int b); }
真实主题类
public class RealSubject : ISubject { public virtual string Request(int a, int b) //生成动态代理对象会用到多态,这里要使用 virtual 修饰 { Console.WriteLine("访问真实主题方法..."); return $"{a + b}"; } }
拦截器
public class SubjectInterceptor : IInterceptor { public void Intercept(IInvocation invocation) { PreProceed(invocation); invocation.Proceed(); //执行下一层的拦截器,当该拦截器是拦截器链的最后一个的时候则会直接调用被代理对象的方法 PostProceed(invocation); } public void PreProceed(IInvocation invocation) { Console.Write($"访问真实主题之前的预处理。拦截调用方法:{invocation.Method.Name};"); for (int i = 0; i < invocation.Arguments.Length; i++) { Console.Write($"{(0 == i ? "" : ",")}参数{i}={invocation.Arguments[i]}"); } Console.WriteLine(); } public void PostProceed(IInvocation invocation) { Console.WriteLine($"访问真实主题之后的后续处理。返回值={invocation.ReturnValue ?? "无"}。"); } }
客户端
class Client { static void Main(string[] args) { ProxyGenerator prxGen = new ProxyGenerator(); //ProxyGenerator对象用于生成代理对象 SubjectInterceptor subInter = new SubjectInterceptor(); RealSubject realSubject = prxGen.CreateClassProxy<RealSubject>(subInter); //通过CreateClassProxy方法创建代理对象,传入的RealSubject为代理的类 realSubject.Request(5, 6); Console.Read(); } }
适配器(类、对象)模式(Adapter)
来源
在现实生活中,经常出现两个对象因接口不兼容而不能在一起工作的实例,这时需要第三者进行适配。例如,讲中文的人同讲英文的人对话时需要一个翻译,用直流电的笔记本电脑接交流电源时需要一个电源适配器,用计算机访问照相机的 SD 内存卡时需要一个读卡器等。 在软件设计中也可能出现:需要开发的具有某种业务功能的组件在现有的组件库中已经存在,但它们与当前系统的接口规范不兼容,如果重新开发这些组件成本又很高,这时用适配器模式能很好地解决这些问题。
定义
将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类能一起工作。
划分
类结构型模式
该模式类之间的耦合度比对象结构型模式高,且要求程序员了解现有组件库中的相关组件的内部结构,所以应用相对较少些。
对象结构型模式(更常用)
特点
优点
客户端通过适配器可以透明地调用目标接口。
复用了现存的类,程序员不需要修改原有代码而重用现有的适配者类。
将目标类和适配者类解耦,解决了目标类和适配者类接口不一致的问题。
在很多业务场景中符合开闭原则。
缺点
适配器编写过程需要结合业务场景全面考虑,可能会增加系统的复杂性。
增加代码阅读难度,降低代码可读性,过多使用适配器会使系统代码变得凌乱。
应用场景
以前开发的系统存在满足新系统功能需求的类,但其接口同新系统的接口不一致。
使用第三方提供的组件,但组件接口定义和自己要求的接口定义不同。
结构
主要角色
说明
类适配器模式
可采用多重继承方式实现,如 C++ 可定义一个适配器类来同时继承当前系统的业务接口和现有组件库中已经存在的组件接口;Java 不支持多继承,但可以定义一个适配器类来实现当前系统的业务接口,同时又继承现有组件库中已经存在的组件。
对象适配器模式
可釆用将现有组件库中已经实现的组件引入适配器类中,该类同时实现当前系统的业务接口。现在来介绍它们的基本结构。
目标(Target)接口
当前系统业务所期待的接口,它可以是抽象类或接口。
适配者(Adaptee)类
它是被访问和适配的现存组件库中的组件接口。
适配器(Adapter)类
它是一个转换器,通过继承或引用适配者的对象,把适配者接口转换成目标接口,让客户按目标接口的格式访问适配者。
图示
类适配器模式

对象适配器模式

实现
类适配器模式
目标接口
public interface ITarget { void Request(); }
适配者类
public class Adaptee { public void SpecificRequest() { Console.WriteLine("适配者中的业务代码被调用!"); } }
类适配器类
public class ClassAdapter : Adaptee, ITarget { public void Request() { SpecificRequest(); } }
客户端
class Client { static void Main(string[] args) { ITarget target = new ClassAdapter(); target.Request(); Console.Read(); } }
对象适配器模式
目标接口
public interface ITarget { void Request(); }
适配者类
public class Adaptee { public void SpecificRequest() { Console.WriteLine("适配者中的业务代码被调用!"); } }
对象适配器类
class ObjectAdapter : ITarget { private Adaptee adaptee; public ObjectAdapter(Adaptee adaptee) { this.adaptee = adaptee; } public void Request() { adaptee.SpecificRequest(); } }
客户端
class Client { static void Main(string[] args) { Adaptee adaptee = new Adaptee(); ITarget target = new ObjectAdapter(adaptee); target.Request(); Console.Read(); } }
实例
扩展
双向适配器模式
双向适配器类既可以把适配者接口转换成目标接口,也可以把目标接口转换成适配者接口。
图示

例:
目标接口
public interface ITarget { void Request(); }
适配者接口
public interface IAdaptee { void SpecificRequest(); }
目标实现
public class TargetRealize : ITarget { public void Request() { Console.WriteLine("目标代码被调用!"); } }
适配者实现
public class AdapteeRealize : IAdaptee { public void SpecificRequest() { Console.WriteLine("适配者代码被调用!"); } }
双向适配器
public class TwoWayAdapter : ITarget, IAdaptee { private ITarget target; private IAdaptee adaptee; public TwoWayAdapter(ITarget target) { this.target = target; } public TwoWayAdapter(IAdaptee adaptee) { this.adaptee = adaptee; } public void Request() { adaptee.SpecificRequest(); } public void SpecificRequest() { target.Request(); } }
客户端
class Client { static void Main(string[] args) { Console.WriteLine("目标通过双向适配器访问适配者:"); IAdaptee adaptee = new AdapteeRealize(); ITarget target = new TwoWayAdapter(adaptee); target.Request(); Console.WriteLine(); Console.WriteLine("适配者通过双向适配器访问目标:"); target = new TargetRealize(); adaptee = new TwoWayAdapter(target); adaptee.SpecificRequest(); Console.Read(); } }
桥接模式(Bridge)
来源
在现实生活中,某些类具有两个或多个维度的变化,如图形既可按形状分,又可按颜色分。如何设计类似于 Photoshop 这样的软件,能画不同形状和不同颜色的图形呢?如果用继承方式,m 种形状和 n 种颜色的图形就有 m×n 种,不但对应的子类很多,而且扩展困难。 当然,这样的例子还有很多,如不同颜色和字体的文字、不同品牌和功率的汽车、不同性别和职业的男女、支持不同平台和不同文件格式的媒体播放器等。如果用桥接模式就能很好地解决这些问题。
定义
将抽象与实现分离,使它们可以独立变化。它是用组合关系代替继承关系来实现,从而降低了抽象和实现这两个可变维度的耦合度。
特点
遵循了里氏替换原则和依赖倒置原则,最终实现了开闭原则,对修改关闭,对扩展开放。
优点
抽象与实现分离,扩展能力强
符合开闭原则
符合合成复用原则
其实现细节对客户透明
缺点
由于聚合关系建立在抽象层,要求开发者针对抽象化进行设计与编程,能正确地识别出系统中两个独立变化的维度,这增加了系统的理解与设计难度。
应用场景
当一个类内部具备两种或多种变化维度时,使用桥接模式可以解耦这些变化的维度,使高层代码架构稳定。
通常适用于以下场景
当一个类存在两个独立变化的维度,且这两个维度都需要进行扩展时。
当一个系统不希望使用继承或因为多层次继承导致系统类的个数急剧增加时。
当一个系统需要在构件的抽象化角色和具体化角色之间增加更多的灵活性时。
桥接模式的一个常见使用场景就是替换(掉)继承。我们知道,继承拥有很多优点,比如,抽象、封装、多态等,父类封装共性,子类实现特性。继承可以很好的实现代码复用(封装)的功能,但这也是继承的一大缺点。因为父类拥有的方法,子类也会继承得到,无论子类需不需要,这说明继承具备强侵入性(父类代码侵入子类),同时会导致子类臃肿。因此,在设计模式中,有一个原则为优先使用组合/聚合,而不是继承。
结构
主要角色
说明
可以将抽象化部分与实现化部分分开,取消二者的继承关系,改用组合关系。
实现化(Implementor)角色
定义实现化角色的接口,供扩展抽象化角色调用。
具体实现化(Concrete Implementor)角色
给出实现化角色接口的具体实现。
抽象化(Abstraction)角色
定义抽象类,并包含一个对实现化对象的引用
这种引用成为抽象化角色和实现化角色之间的桥梁
扩展抽象化(Refined Abstraction)角色
是抽象化角色的子类,实现父类中的业务方法,并通过组合关系调用实现化角色中的业务方法。
图示

实现
实现化角色
public interface IImplementor { void OperationImpl(); }
具体实现化角色
public class ConcreteImplementorA : IImplementor { public void OperationImpl() { Console.WriteLine("具体实现化(ConcreteImplementorA)角色被访问"); } }
抽象化角色
public abstract class Abstraction { protected IImplementor imple; protected Abstraction(IImplementor imple) { this.imple = imple; } public abstract void Operation(); }
扩展抽象化角色
public class RefinedAbstraction : Abstraction { public RefinedAbstraction(IImplementor imple) : base(imple) { } public override void Operation() { Console.WriteLine("扩展抽象化(RefinedAbstraction)角色被访问"); imple.OperationImpl(); } }
客户端
class Client { static void Main(string[] args) { IImplementor imple = new ConcreteImplementorA(); Abstraction abs = new RefinedAbstraction(imple); abs.Operation(); Console.Read(); } }
实例
扩展
与适配器模式联合使用
在软件开发中,有时桥接(Bridge)模式可与适配器模式联合使用。当桥接(Bridge)模式的实现化角色的接口与现有类的接口不一致时,可以在二者中间定义一个适配器将二者连接起来。
图示

装饰器模式(Decorator)
来源
上班族大多都有睡懒觉的习惯,每天早上上班时间都很紧张,于是很多人为了多睡一会,就会用方便的方式解决早餐问题。有些人早餐可能会吃煎饼,煎饼中可以加鸡蛋,也可以加香肠,但是不管怎么“加码”,都还是一个煎饼。在现实生活中,常常需要对现有产品增加新的功能或美化其外观,如房子装修、相片加相框等,都是装饰器模式。 在软件开发过程中,有时想用一些现存的组件。这些组件可能只是完成了一些核心功能。但在不改变其结构的情况下,可以动态地扩展其功能。所有这些都可以釆用装饰器模式来实现。
定义
指在不改变现有对象结构的情况下,动态地给该对象增加一些职责(即增加其额外功能)的模式。
特点
优点
装饰器是继承的有力补充,比继承灵活,在不改变原有对象的情况下,动态的给一个对象扩展功能,即插即用
通过使用不用装饰类及这些装饰类的排列组合,可以实现不同效果
装饰器模式完全遵守开闭原则
缺点
装饰器模式会增加许多子类,过度使用会增加程序得复杂性。
应用场景
当需要给一个现有类添加附加职责,而又不能采用生成子类的方法进行扩充时。例如,该类被隐藏或者该类是终极类或者采用继承方式会产生大量的子类。
当需要通过对现有的一组基本功能进行排列组合而产生非常多的功能时,采用继承关系很难实现,而采用装饰器模式却很好实现。
当对象的功能要求可以动态地添加,也可以再动态地撤销时。
结构
主要角色
说明
通常情况下,扩展一个类的功能会使用继承方式来实现。但继承具有静态特征,耦合度高,并且随着扩展功能的增多,子类会很膨胀。 如果使用组合关系来创建一个包装对象(即装饰对象)来包裹真实对象,并在保持真实对象的类结构不变的前提下,为其提供额外的功能,这就是装饰器模式的目标。
抽象构件(Component)角色
定义一个抽象接口以规范准备接收附加责任的对象。
具体构件(ConcreteComponent)角色
实现抽象构件,通过装饰角色为其添加一些职责。
抽象装饰(Decorator)角色
继承抽象构件,并包含具体构件的实例,可以通过其子类扩展具体构件的功能。
具体装饰(ConcreteDecorator)角色
实现抽象装饰的相关方法,并给具体构件对象添加附加的责任。
图示

实现
抽象构件角色
public abstract class Component { public abstract void Operation(); }
具体构件角色
public class ConcreteComponent : Component { public ConcreteComponent() { Console.WriteLine("创建具体构件角色"); } public override void Operation() { Console.WriteLine("调用具体构件角色的方法Operation()"); } }
抽象装饰角色
public class Decorator : Component { private Component component; public Decorator(Component component) { this.component = component; } public override void Operation() { component.Operation(); } }
具体装饰角色
public class ConcreteDecorator1 : Decorator { public ConcreteDecorator1(Component component) : base(component) { } public override void Operation() { base.Operation(); AddedFunction(); } public void AddedFunction() { Console.WriteLine("为具体构件角色增加额外的功能AddedFunction() 1"); } } public class ConcreteDecorator2 : Decorator { public ConcreteDecorator2(Component component) : base(component) { } public override void Operation() { base.Operation(); AddedFunction(); } public void AddedFunction() { Console.WriteLine("为具体构件角色增加额外的功能AddedFunction() 2"); } } public class ConcreteDecorator3 : Decorator { public ConcreteDecorator3(Component component) : base(component) { } public override void Operation() { base.Operation(); AddedFunction(); } public void AddedFunction() { Console.WriteLine("为具体构件角色增加额外的功能AddedFunction() 3"); } }
客户端
class Client { static void Main(string[] args) { Component p = new ConcreteComponent(); p.Operation(); Console.WriteLine(); Component d = new ConcreteDecorator1(p); d = new ConcreteDecorator2(d); //将装饰了一次之后的对象继续注入到另一个装饰类中,实现二次装饰 d = new ConcreteDecorator3(d); d.Operation(); Console.Read(); } }
实例
扩展
饰器模式所包含的 4 个角色不是任何时候都要存在的,在有些应用环境下模式是可以简化的
如果只有一个具体构件而没有抽象构件时,可以让抽象装饰继承具体构件
图示

如果只有一个具体装饰时,可以将抽象装饰和具体装饰合并
图示

外观模式(Facade)
来源
在现实生活中,常常存在办事较复杂的例子,如办房产证或注册一家公司,有时要同多个部门联系,这时要是有一个综合部门能解决一切手续问题就好了。 软件设计也是这样,当一个系统的功能越来越强,子系统会越来越多,客户对系统的访问也变得越来越复杂。这时如果系统内部发生改变,客户端也要跟着改变,这违背了“开闭原则”,也违背了“迪米特法则”,所以有必要为多个子系统提供一个统一的接口,从而降低系统的耦合度,这就是外观模式的目标。
定义
外观模式又叫作门面模式,是一种通过为多个复杂的子系统提供一个一致的接口,而使这些子系统更加容易被访问的模式。该模式对外有一个统一接口,外部应用程序不用关心内部子系统的具体细节,这样会大大降低应用程序的复杂度,提高了程序的可维护性。
特点
优点
说明
外观(Facade)模式是“迪米特法则”的典型应用
降低了子系统与客户端之间的耦合度,使得子系统的变化不会影响调用它的客户类。
对客户屏蔽了子系统组件,减少了客户处理的对象数目,并使得子系统使用起来更加容易。
降低了大型软件系统中的编译依赖性,简化了系统在不同平台之间的移植过程,因为编译一个子系统不会影响其他的子系统,也不会影响外观对象。
缺点
不能很好地限制客户使用子系统类,很容易带来未知风险。
增加新的子系统可能需要修改外观类或客户端的源代码,违背了“开闭原则”。
应用场景
对分层结构系统构建时,使用外观模式定义子系统中每层的入口点可以简化子系统之间的依赖关系。
当一个复杂系统的子系统很多时,外观模式可以为系统设计一个简单的接口供外界访问。
当客户端与多个子系统之间存在很大的联系时,引入外观模式可将它们分离,从而提高子系统的独立性和可移植性。
结构
主要角色
子系统(Sub System)角色
实现系统的部分功能,客户可以通过外观角色访问它。
外观(Facade)角色
为多个子系统对外提供一个共同的接口。
客户(Client)角色
通过一个外观角色访问各个子系统的功能。
图示

实现
子系统角色
public class SubSystem01 { public void Method1() { Console.WriteLine("子系统01的method1()被调用!"); } } public class SubSystem02 { public void Method2() { Console.WriteLine("子系统02的method2()被调用!"); } } public class SubSystem03 { public void Method3() { Console.WriteLine("子系统03的method3()被调用!"); } }
外观角色
public class Facade { private SubSystem01 obj1 = new SubSystem01(); private SubSystem02 obj2 = new SubSystem02(); private SubSystem03 obj3 = new SubSystem03(); public void Method() { obj1.Method1(); obj2.Method2(); obj3.Method3(); } }
客户端
class Client { static void Main(string[] args) { Facade f = new Facade(); f.Method(); Console.Read(); } }
实例
扩展
引入抽象外观类
在外观模式中,当增加或移除子系统时需要修改外观类,这违背了“开闭原则”。如果引入抽象外观类,则在一定程度上解决了该问题
图示

享元模式(Flyweight)
来源
在面向对象程序设计过程中,有时会面临要创建大量相同或相似对象实例的问题。创建那么多的对象将会耗费很多的系统资源,它是系统性能提高的一个瓶颈。 例如,围棋和五子棋中的黑白棋子,图像中的坐标点或颜色,局域网中的路由器、交换机和集线器,教室里的桌子和凳子等。这些对象有很多相似的地方,如果能把它们相同的部分提取出来共享,则能节省大量的系统资源,这就是享元模式的产生背景。
定义
运用共享技术来有效地支持大量细粒度对象的复用。它通过共享已经存在的对象来大幅度减少需要创建的对象数量、避免大量相似类的开销,从而提高系统资源的利用率。
特点
享元模式的本质是缓存共享对象,降低内存消耗。
优点
相同对象只要保存一份,这降低了系统中对象的数量,从而降低了系统中细粒度对象给内存带来的压力。
缺点
为了使对象可以共享,需要将一些不能共享的状态外部化,这将增加程序的复杂性。
读取享元模式的外部状态会使得运行时间稍微变长。
应用场景
说明
当系统中多处需要同一组信息时,可以把这些信息封装到一个对象中,然后对该对象进行缓存,这样,一个对象就可以提供给多出需要使用的地方,避免大量同一对象的多次创建,降低大量内存空间的消耗。
享元模式其实是工厂方法模式的一个改进机制,享元模式同样要求创建一个或一组对象,并且就是通过工厂方法模式生成对象的,只不过享元模式为工厂方法模式增加了缓存这一功能。
系统中存在大量相同或相似的对象,这些对象耗费大量的内存资源。
大部分的对象可以按照内部状态进行分组,且可将不同部分外部化,这样每一个组只需保存一个内部状态。
由于享元模式需要额外维护一个保存享元的数据结构,所以应当在有足够多的享元实例时才值得使用享元模式。
结构
主要角色
说明
享元模式的定义提出了两个要求,细粒度和共享对象。因为要求细粒度,所以不可避免地会使对象数量多且性质相近,此时我们就将这些对象的信息分为两个部分:
比如,连接池中的连接对象,保存在连接对象中的用户名、密码、连接URL等信息,在创建对象的时候就设置好了,不会随环境的改变而改变,这些为内部状态。而当每个连接要被回收利用时,我们需要将它标记为可用状态,这些为外部状态。
内部状态
指对象共享出来的信息,存储在享元信息内部,并且不回随环境的改变而改变
外部状态
指对象得以依赖的一个标记,随环境的改变而改变,不可共享
非享元(Unsharable Flyweight)角色
是不可以共享的外部状态,它以参数的形式注入具体享元的相关方法中。
抽象享元(Flyweight)角色
是所有的具体享元类的基类,为具体享元规范需要实现的公共接口,非享元的外部状态以参数的形式通过方法传入。
具体享元(Concrete Flyweight)角色
实现抽象享元角色中所规定的接口。
享元工厂(Flyweight Factory)角色
负责创建和管理享元角色。当客户对象请求一个享元对象时,享元工厂检査系统中是否存在符合要求的享元对象,如果存在则提供给客户;如果不存在的话,则创建一个新的享元对象。
图示
说明
UnsharedConcreteFlyweight 是非享元角色,里面包含了非共享的外部状态信息 info;
Flyweight 是抽象享元角色,里面包含了享元方法 operation(UnsharedConcreteFlyweight state),非享元的外部状态以参数的形式通过该方法传入;
ConcreteFlyweight 是具体享元角色,包含了关键字 key,它实现了抽象享元接口;
FlyweightFactory 是享元工厂角色,它用关键字 key 来管理具体享元;
客户角色通过享元工厂获取具体享元,并访问具体享元的相关方法。

实现
非享元角色
public class UnsharedConcreteFlyweight { private string info; public UnsharedConcreteFlyweight(string info) { this.info = info; } public void SetInfo(string info) { this.info = info; } public string GetInfo() { return info; } }
抽象享元角色
public interface IFlyweight { void Operation(UnsharedConcreteFlyweight state); }
具体享元角色
public class ConcreteFlyweight : IFlyweight { private string key; public ConcreteFlyweight(string key) { this.key = key; Console.WriteLine($"具体享元{key}被创建!"); } public void Operation(UnsharedConcreteFlyweight outState) { Console.Write($"具体享元{key}被调用,"); Console.WriteLine($"非享元信息是:{outState.GetInfo()}"); } }
享元工厂角色
public class FlyweightFactory { private Dictionary<string, IFlyweight> flyweights = new Dictionary<string, IFlyweight>(); public IFlyweight GetFlyweight(string key) { IFlyweight flyweight; bool has = flyweights.TryGetValue(key, out flyweight); if (has) { Console.WriteLine($"具体享元{key}已经存在,被成功获取!"); } else { flyweight = new ConcreteFlyweight(key); flyweights.Add(key, flyweight); } return flyweight; } }
客户端
class Client { static void Main(string[] args) { FlyweightFactory factory = new FlyweightFactory(); IFlyweight f01 = factory.GetFlyweight("a"); IFlyweight f02 = factory.GetFlyweight("a"); IFlyweight f03 = factory.GetFlyweight("a"); IFlyweight f11 = factory.GetFlyweight("b"); IFlyweight f12 = factory.GetFlyweight("b"); f01.Operation(new UnsharedConcreteFlyweight("第1次调用a。")); f02.Operation(new UnsharedConcreteFlyweight("第2次调用a。")); f03.Operation(new UnsharedConcreteFlyweight("第3次调用a。")); f11.Operation(new UnsharedConcreteFlyweight("第1次调用b。")); f12.Operation(new UnsharedConcreteFlyweight("第2次调用b。")); Console.Read(); } }
实例
扩展
在前面介绍的享元模式中,其结构图通常包含可以共享的部分和不可以共享的部分。在实际使用过程中,有时候会稍加改变,即存在两种特殊的享元模式:
单纯享元模式
这种享元模式中的所有的具体享元类都是可以共享的,不存在非共享的具体享元类
图示

复合享元模式
这种享元模式中的有些享元对象是由一些单纯享元对象组合而成的,它们就是复合享元对象。虽然复合享元对象本身不能共享,但它们可以分解成单纯享元对象再被共享
图示

组合模式(Composite)
来源
在现实生活中,存在很多“部分-整体”的关系,例如,大学中的部门与学院、总公司中的部门与分公司、学习用品中的书与书包、生活用品中的衣服与衣柜、以及厨房中的锅碗瓢盆等。在软件开发中也是这样,例如,文件系统中的文件与文件夹、窗体程序中的简单控件与容器控件等。对这些简单对象与复合对象的处理,如果用组合模式来实现会很方便。
定义
有时又叫作整体-部分(Part-Whole)模式,它是一种将对象组合成树状的层次结构的模式,用来表示“整体-部分”的关系,使用户对单个对象和组合对象具有一致的访问性。
说明
组合模式一般用来描述整体与部分的关系,它将对象组织到树形结构中,顶层的节点被称为根节点,根节点下面可以包含树枝节点和叶子节点,树枝节点下面又可以包含树枝节点和叶子节点。
根节点和树枝节点本质上属于同一种数据类型,可以作为容器使用;而叶子节点与树枝节点在语义上不属于用一种类型。但是在组合模式中,会把树枝节点和叶子节点看作属于同一种数据类型(用统一接口定义),让它们具备一致行为。 这样,在组合模式中,整个树形结构中的对象都属于同一种类型,带来的好处就是用户不需要辨别是树枝节点还是叶子节点,可以直接进行操作,给用户的使用带来极大的便利。
图示

特点
优点
组合模式使得客户端代码可以一致地处理单个对象和组合对象,无须关心自己处理的是单个对象,还是组合对象,这简化了客户端代码;
更容易在组合体内加入新的对象,客户端不会因为加入了新的对象而更改源代码,满足“开闭原则”;
缺点
1. 设计较复杂,客户端需要花更多时间理清类之间的层次关系;
2. 不容易限制容器中的构件;
3. 不容易用继承的方法来增加构件的新功能;
应用场景
在需要表示一个对象整体与部分的层次结构的场合。
要求对用户隐藏组合对象与单个对象的不同,用户可以用统一的接口使用组合结构中的所有对象的场合。
结构
主要角色
抽象构件(Component)角色
它的主要作用是为树叶构件和树枝构件声明公共接口,并实现它们的默认行为。 在透明式的组合模式中抽象构件还声明访问和管理子类的接口; 在安全式的组合模式中不声明访问和管理子类的接口,管理工作由树枝构件完成。(总的抽象类或接口,定义一些通用的方法,比如新增、删除)
树叶构件(Leaf)角色
是组合中的叶节点对象,它没有子节点,用于继承或实现抽象构件。
树枝构件(Composite)角色 / 中间构件
是组合中的分支节点对象,它有子节点,用于继承和实现抽象构件。它的主要作用是存储和管理子部件,通常包含 Add()、Remove()、GetChild() 等方法。
划分
透明式组合模式
说明
在该方式中,由于抽象构件声明了所有子类中的全部方法,所以客户端无须区别树叶对象和树枝对象,对客户端来说是透明的。但其缺点是:树叶构件本来没有 Add()、Remove() 及 GetChild() 方法,却要实现它们(空实现或抛异常),这样会带来一些安全性问题。
图示

安全式组合模式
说明
在该方式中,将管理子构件的方法移到树枝构件中,抽象构件和树叶构件没有对子对象的管理方法,这样就避免了上一种方式的安全性问题,但由于叶子和分支有不同的接口,客户端在调用时要知道树叶对象和树枝对象的存在,所以失去了透明性。
图示

实现
场景
假如要访问集合 c0={leaf1,{leaf2,leaf3}} 中的元素,图示

透明式组合模式
抽象构件
public interface IComponent { void Add(IComponent c); void Remove(IComponent c); IComponent GetChild(int i); void Operation(); }
树叶构件
public class Leaf : IComponent { private string name; public Leaf(string name) { this.name = name; } public void Add(IComponent c) { } public void Remove(IComponent c) { } public IComponent GetChild(int i) { return null; } public void Operation() { Console.WriteLine($"树叶{name}:被访问!"); } }
树枝构件
public class Composite : IComponent { private List<IComponent> children = new List<IComponent>(); public void Add(IComponent c) { children.Add(c); } public void Remove(IComponent c) { children.Remove(c); } public IComponent GetChild(int i) { return children[i]; } public void Operation() { foreach (var obj in children) { obj.Operation(); } } }
客户端
class Client { static void Main(string[] args) { IComponent c0 = new Composite(); IComponent c1 = new Composite(); IComponent leaf1 = new Leaf("1"); IComponent leaf2 = new Leaf("2"); IComponent leaf3 = new Leaf("3"); c0.Add(leaf1); c0.Add(c1); c1.Add(leaf2); c1.Add(leaf3); c0.Operation(); Console.Read(); } }
安全式组合模式
抽象构件
public interface IComponent { void Operation(); }
树叶构件
public class Leaf : IComponent { private string name; public Leaf(string name) { this.name = name; } public void Operation() { Console.WriteLine($"树叶{name}:被访问!"); } }
树枝构件
public class Composite : IComponent { private List<IComponent> children = new List<IComponent>(); public void Add(IComponent c) { children.Add(c); } public void Remove(IComponent c) { children.Remove(c); } public IComponent GetChild(int i) { return children[i]; } public void Operation() { foreach (var obj in children) { obj.Operation(); } } }
客户端
class Client { static void Main(string[] args) { Composite c0 = new Composite(); Composite c1 = new Composite(); IComponent leaf1 = new Leaf("1"); IComponent leaf2 = new Leaf("2"); IComponent leaf3 = new Leaf("3"); c0.Add(leaf1); c0.Add(c1); c1.Add(leaf2); c1.Add(leaf3); c0.Operation(); Console.Read(); } }
实例
扩展
复杂的组合模式
如果对前面介绍的组合模式中的树叶节点和树枝节点进行抽象,也就是说树叶节点和树枝节点还有子节点,这时组合模式就扩展成复杂的组合模式了,如 Java AWT/Swing 中的简单组件 JTextComponent 有子类 JTextField、JTextArea,容器组件 Container 也有子类 Window、Panel。
图示

过滤器模式(Filter、Criteria)
来源
定义
过滤器模式(Filter Pattern)又称为标准模式(Criteria Pattern),该模式使用不同的标准来过滤一组对象,通过逻辑运算以解耦的方式把它们连接起来。 该模式可结合多个标准来获得单一标准。
特点
优点
缺点
应用场景
结构
主要角色
图示
实现
实例
场景
我们将创建一个 Person 对象、Criteria 接口和实现了该接口的实体类,来过滤 Person 对象的列表。CriteriaPatternDemo 类使用 Criteria 对象,基于各种标准和它们的结合来过滤 Person 对象的列表。 
代码
被标准过滤的角色
public class Person { private string name; private string gender; private string maritalStatus; public Person(string name, string gender, string maritalStatus) { this.name = name; this.gender = gender; this.maritalStatus = maritalStatus; } public string GetName() { return name; } public string GetGender() { return gender; } public string GetMaritalStatus() { return maritalStatus; } }
抽象标准过滤角色
public interface ICriteria { List<Person> MeetCriteria(List<Person> persons); }
具体标准过滤角色
//单一标准过滤标准类 public class CriteriaMale : ICriteria { public List<Person> MeetCriteria(List<Person> persons) { List<Person> malePersons = new List<Person>(); foreach (Person person in persons) { if (person.GetGender().Equals("MALE", StringComparison.OrdinalIgnoreCase)) { malePersons.Add(person); } } return malePersons; } } public class CriteriaFemale : ICriteria { public List<Person> MeetCriteria(List<Person> persons) { List<Person> femalePersons = new List<Person>(); foreach (Person person in persons) { if (person.GetGender().Equals("FEMALE", StringComparison.OrdinalIgnoreCase)) { femalePersons.Add(person); } } return femalePersons; } } public class CriteriaSingle : ICriteria { public List<Person> MeetCriteria(List<Person> persons) { List<Person> singlePersons = new List<Person>(); foreach (Person person in persons) { if (person.GetMaritalStatus().Equals("SINGLE", StringComparison.OrdinalIgnoreCase)) { singlePersons.Add(person); } } return singlePersons; } } //结合标准过滤标准类 public class AndCriteria : ICriteria { private ICriteria criteria; private ICriteria otherCriteria; public AndCriteria(ICriteria criteria, ICriteria otherCriteria) { this.criteria = criteria; this.otherCriteria = otherCriteria; } public List<Person> MeetCriteria(List<Person> persons) { //实现与逻辑过滤 List<Person> firstCriteriaPersons = criteria.MeetCriteria(persons); return otherCriteria.MeetCriteria(firstCriteriaPersons); } } public class OrCriteria : ICriteria { private ICriteria criteria; private ICriteria otherCriteria; public OrCriteria(ICriteria criteria, ICriteria otherCriteria) { this.criteria = criteria; this.otherCriteria = otherCriteria; } public List<Person> MeetCriteria(List<Person> persons) { //实现或逻辑过滤 List<Person> firstCriteriaItems = criteria.MeetCriteria(persons); List<Person> otherCriteriaItems = otherCriteria.MeetCriteria(persons); foreach (Person person in otherCriteriaItems) { if (!firstCriteriaItems.Contains(person)) { firstCriteriaItems.Add(person); } } return firstCriteriaItems; } }
客户端
class Client { static void Main(string[] args) { List<Person> persons = new List<Person>(); persons.Add(new Person("Robert", "Male", "Single")); persons.Add(new Person("John", "Male", "Married")); persons.Add(new Person("Laura", "Female", "Married")); persons.Add(new Person("Diana", "Female", "Single")); persons.Add(new Person("Mike", "Male", "Single")); persons.Add(new Person("Bobby", "Male", "Single")); ICriteria male = new CriteriaMale(); ICriteria female = new CriteriaFemale(); ICriteria single = new CriteriaSingle(); ICriteria singleMale = new AndCriteria(single, male); ICriteria singleOrFemale = new OrCriteria(single, female); Console.WriteLine("Males: "); PrintPersons(male.MeetCriteria(persons)); Console.WriteLine("\nFemales: "); PrintPersons(female.MeetCriteria(persons)); Console.WriteLine("\nSingle Males: "); PrintPersons(singleMale.MeetCriteria(persons)); Console.WriteLine("\nSingle Or Females: "); PrintPersons(singleOrFemale.MeetCriteria(persons)); Console.Read(); } public static void PrintPersons(List<Person> persons) { foreach (Person person in persons) { Console.WriteLine( $"Person : [ Name : {person.GetName()}, Gender : {person.GetGender()}, Marital Status : {person.GetMaritalStatus()} ]"); } } }
扩展
行为型模式(Behaviorals)
说明
关注对象间的行为,即不同对象间的通信。
分类
模板方法(类)模式(Template Method)
来源
在面向对象程序设计过程中,程序员常常会遇到这种情况:设计一个系统时知道了算法所需的关键步骤,而且确定了这些步骤的执行顺序,但某些步骤的具体实现还未知,或者说某些步骤的实现与具体的环境相关。 例如,去银行办理业务一般要经过以下4个流程:取号、排队、办理具体业务、对银行工作人员进行评分等,其中取号、排队和对银行工作人员进行评分的业务对每个客户是一样的,可以在父类中实现,但是办理具体业务却因人而异,它可能是存款、取款或者转账等,可以延迟到子类中实现。 这样的例子在生活中还有很多,例如,一个人每天会起床、吃饭、做事、睡觉等,其中“做事”的内容每天可能不同。我们把这些规定了流程或格式的实例定义成模板,允许使用者根据自己的需求去更新它,例如,简历模板、论文模板、Word 中模板文件等。
定义
定义一个操作中的算法骨架,而将算法的一些步骤延迟到子类中,使得子类可以在不改变该算法结构的情况下重定义该算法的某些特定步骤。
特点
优点
它封装了不变部分,扩展可变部分。它把认为是不变部分的算法封装到父类中实现,而把可变部分算法由子类继承实现,便于子类继续扩展。
它在父类中提取了公共的部分代码,便于代码复用。
部分方法是由子类实现的,因此子类可以通过扩展方式增加相应的功能,符合开闭原则。
缺点
对每个不同的实现都需要定义一个子类,这会导致类的个数增加,系统更加庞大,设计也更加抽象,间接地增加了系统实现的复杂度。
父类中的抽象方法由子类实现,子类执行的结果会影响父类的结果,这导致一种反向的控制结构,它提高了代码阅读的难度。
由于继承关系自身的缺点,如果父类添加新的抽象方法,则所有子类都要改一遍。
应用场景
算法的整体步骤很固定,但其中个别部分易变时,这时候可以使用模板方法模式,将容易变的部分抽象出来,供子类实现。
当多个子类存在公共的行为时,可以将其提取出来并集中到一个公共父类中以避免代码重复。首先,要识别现有代码中的不同之处,并且将不同之处分离为新的操作。最后,用一个调用这些新的操作的模板方法来替换这些不同的代码。
当需要控制子类的扩展时,模板方法只在特定点调用钩子操作,这样就只允许在这些点进行扩展。
结构
主要角色
说明
模板方法模式需要注意抽象类与具体子类之间的协作。它用到了虚函数的多态性技术以及“不用调用我,让我来调用你”的反向控制技术。
“不要给我们打电话,我们会给你打电话(don‘t call us, we‘ll call you)”这是著名的好莱坞原则。在好莱坞,把简历递交给演艺公司后就只有回家等待。由演艺公司对整个娱乐项的完全控制,演员只能被动式的接受公司的差使,在需要的环节中,完成自己的演出。 关于控制反转(Inversion of Control),在具体实现上也有许多其它的叫法,如依赖倒置(Dependency Inversion Principles, DIP)、依赖注入(Dependency Injection)等等,现在自己就本人的理解,来说一下这里的反转及倒置的讲究。 就总的原则来说,控制反转(依赖倒置)是: 高层模块不应该依赖于低层模块,二者都要依赖于抽象 抽象不应该依赖于具体,具体应该依赖于抽象。 从现实社会生活中,我们知道:官职越大的人,负责的工作越抽象,官职越低的人,负责的工作越具体;所以说,上面动动嘴皮子,下面跑断腿。也就是说,正常状态下,应该是高层调用(控制)低层办理具体的事务,我们估且说这是正置吧;如果低层调用(控制)高层进行工作,这种情况可以说是倒置了。 软件设计中,确实存在有正置和倒置。正置是结构化设计时的情况,自上而下,由高层调用低层完成软件设计,低层的具体工作向高层提供服务,而且IT中许多设计思想也是正置的,比如计算机网络的七层(或五层)架构,物理层向上层数据链路层提供服务(下层向上层提供服务,上层调用或控制或依赖下层),数据链路层向上层网络层提供服务,等等。 但在面向对象的设计中,为了降低模块间的耦合,实际低耦合,高内聚的总原则,控制反转就成为面向对象设计的主要原则。 看下例: public class EmailService { public void SendMessage() {....} } public class NotificationSystem { private EmailServie svc; public NotificationSystem() { svc = new EmailService(); } public void InterestingEventHappened() { svc.SendMessage(); } } 这是典型的正置,类EmailService属于具体类(低层模块),发出邮件;而NotificationSystem类则是调用类(高层模块),它调用(控制)具体类EmailService中的具体方法完成任务,这在面向对象设计中,是一种紧耦合。 这种紧耦合可能会造成如下问题: 1、当两个模块其中之一产生修改时,另外一个模块就会受到影响。如果多个模块紧耦合,这个影响就有可能造成软件系统修改量巨大,如果是封装出售的商业模块,对购买者今后的平滑升级与维护将造成极大困难。 2、如果这时NotificationSystem要发出的消息不是电邮,而是短信或者存到数据库中以后再看,这个类就需要再进行修改。 为解决这个问题,就需要把紧耦合变为松耦合,办法就是:加入一个抽象层,大家都依赖于抽象层,因为抽象层是最不容易变化的,从某种程序上来讲,设计完成之后,抽象层应该永远不变,如果需要,可以再添加其它抽象层。 这个抽象层可以是接口(Interface)或者抽象类(Abstract Class),但一般建议使用接口完成。 public interface IMessagingService { void SendMessage(); } public class EmailService : IMessagingService { public void SendMessage() {....} } public class NotificationSystem { private IMessagingService svc; public NotificationSystem() { svc = new EmailService(); } public void InterestingEventHappened() { svc.SendMessage(); } } 看上面的代码,添加了一个接口IMessagingService。然后高层模块NotificationSytem依赖于抽象的接口,低层模块EmailService实现这个抽象接口内的方法定义(或称虚方法、虚函数);事实是两者都依赖于这个抽象接口。低层模块EmailService通过实现接口方法定义完成对它的依赖(即EmailService : IMessagingService),而高层模块NotificationSystem则通过声明接口IMessagingService(抽象层)实现了对它的调用(依赖),而这个调用实际是调用的更高层,因为抽象层是最高层,这种调用,就是控制反转或者依赖倒置。实际调用的是它的具体实现,而这个具体实现由EmailService中的SendMessage方法完成。 这里只是谈了控制反转或者依赖倒置的总原则。对于依赖倒置的具体办法,其它文章再谈。 ---------- 因为还有一种情况没有解决,如果现在要加入发短信的功能,应该有类似下面的代码 public class SmsService : IMessagingService { public void SendMessage() {....} } 但只加入上面的代码,还没有完成发短信的功能,需要修改NotificationSystem类中构造函数的实现了,它写的是静态的实例化EmailService对象。因此解决办法应该是:在构造函数中(或者实现方法InterestingEventHappened()中)加入传递参数,这个传递参数就是具体的低层模块类(或具体实现模块类)实例化的对象,这样就不需要修改主程序代码,实现了对修改封闭,对扩展开放的OO设计原则。
抽象类/抽象模板(Abstract Class)
抽象模板类,负责给出一个算法的轮廓和骨架。它由一个模板方法和若干个基本方法构成。
方法
模板方法
定义了算法的骨架,按某种顺序调用其包含的基本方法。
基本方法
是整个算法中的一个步骤。
类型
抽象方法
在抽象类中声明,由具体子类实现。
具体方法
在抽象类中已经实现,在具体子类中可以继承或重写它。
钩子方法
在抽象类中已经实现,包括用于判断的逻辑方法和需要子类重写的空方法两种。
理解钩子方法 对于钩子方法,是对于抽象方法或者接口中定义的方法的一个空实现。 钩子方法是啥 钩子顾名思义就是用来挂东西的。那么要挂东西必须有个被挂的东西,要不就是铁环、要不就是墙的边沿。所以要能挂住东西必须要有个被勾住的铁环,要一个钩子。那么在java中也是同样的原理,你首先需要一个被挂在的东西,一个挂载的东西。 钩子的实现方法 在实际中的应用,比如说有一个接口,这个接口里有7个方法,而你只想用其中一个方法,那么这时,你可以写一个抽象类实现这个接口,在这个抽象类里将你要用的那个方法设置为abstract,其它方法进行空实现,然后你再继承这个抽象类,就不需要实现其它不用的方法,这就是钩子方法的作用。 1.钩子方法(hook)放置钩子是隔离变化的一种常见手段,在父类中容易变化的地方放置钩子,钩子可以有一个默认的实现,究竟要不要'挂钩',这由子类自行决定。 2.钩子方法的返回结果决定了模板方法后面部分的执行步骤,也就是程序接下来的走向,这样一来,程序就拥有了变化的可能。 //抽象父类 public abstract class AbstractClass { public abstract boolean isOpen(); public final void operating() { if(isOpen()) { System.out.println("钩子方法开启"); }else { System.out.println("钩子方法关闭"); } } } //实现类 public class AchieveClass extends AbstractClass { //钩子方法能挂在到operating干预operating业务逻辑 @Override public boolean isOpen() { return true; } public static void main(String[] args) { AchieveClass ac = new AchieveClass(); ac.operating(); } } 只要重写isOpen就能干预父类方法的业务流程。相当于将isOpen挂载在了父类的operating()中。
具体子类/具体实现(Concrete Class)
具体实现类,实现抽象类中所定义的抽象方法和钩子方法,它们是一个顶级逻辑的一个组成步骤。
图示

实现
抽象类
public abstract class AbstractClass { public void TemplateMethod() //模板方法 { SpecificMethod(); AbstractMethod1(); AbstractMethod2(); } public abstract void AbstractMethod1(); //抽象方法1 public abstract void AbstractMethod2(); //抽象方法2 public void SpecificMethod() //具体方法 { Console.WriteLine("抽象类中的具体方法被调用..."); } }
具体子类
public class ConcreteClass : AbstractClass { public override void AbstractMethod1() { Console.WriteLine("抽象方法1的实现被调用..."); } public override void AbstractMethod2() { Console.WriteLine("抽象方法2的实现被调用..."); } }
客户端
class Client { static void Main(string[] args) { AbstractClass tm = new ConcreteClass(); tm.TemplateMethod(); Console.Read(); } }
实例
扩展
钩子方法的使用
在模板方法模式中,基本方法包含:抽象方法、具体方法和钩子方法,正确使用“钩子方法”可以使得子类控制父类的行为。
例:
通过在具体子类中重写钩子方法 HookMethod1() 和 HookMethod2() 来改变抽象父类中的运行结果
图示

代码
含钩子方法的抽象类
public abstract class HookAbstractClass { public void TemplateMethod() //模板方法 { AbstractMethod1(); HookMethod1(); if (HookMethod2()) { SpecificMethod(); } AbstractMethod2(); } public abstract void AbstractMethod1(); //抽象方法1 public abstract void AbstractMethod2(); //抽象方法2 public void SpecificMethod() //具体方法 { Console.WriteLine("抽象类中的具体方法被调用..."); } public virtual void HookMethod1() //钩子方法1 { } public virtual bool HookMethod2() //钩子方法2 { return true; } }
含钩子方法的具体子类
public class HookConcreteClass : HookAbstractClass { public override void AbstractMethod1() { Console.WriteLine("抽象方法1的实现被调用..."); } public override void AbstractMethod2() { Console.WriteLine("抽象方法2的实现被调用..."); } public override void HookMethod1() { Console.WriteLine("钩子方法1被重写..."); } public override bool HookMethod2() { return false; } }
客户端
class Client { static void Main(string[] args) { HookAbstractClass tm = new HookConcreteClass(); tm.TemplateMethod(); Console.Read(); } }
策略模式(Strategy)
来源
在现实生活中常常遇到实现某种目标存在多种策略可供选择的情况,例如,出行旅游可以乘坐飞机、乘坐火车、骑自行车或自己开私家车等,超市促销可以釆用打折、送商品、送积分等方法。 在软件开发中也常常遇到类似的情况,当实现某一个功能存在多种算法或者策略,我们可以根据环境或者条件的不同选择不同的算法或者策略来完成该功能,如数据排序策略有冒泡排序、选择排序、插入排序、二叉树排序等。 如果使用多重条件转移语句实现(即硬编码),不但使条件语句变得很复杂,而且增加、删除或更换算法要修改原代码,不易维护,违背开闭原则。如果采用策略模式就能很好解决该问题。
定义
该模式定义了一系列算法,并将每个算法封装起来,使它们可以相互替换,且算法的变化不会影响使用算法的客户。
特点
通过对算法进行封装,把使用算法的责任和算法的实现分割开来,并委派给不同的对象对这些算法进行管理。
优点
多重条件语句不易维护,而使用策略模式可以避免使用多重条件语句,如 if...else 语句、switch...case 语句。
策略模式提供了一系列的可供重用的算法族,恰当使用继承可以把算法族的公共代码转移到父类里面,从而避免重复的代码。
策略模式可以提供相同行为的不同实现,客户可以根据不同时间或空间要求选择不同的。
策略模式提供了对开闭原则的完美支持,可以在不修改原代码的情况下,灵活增加新算法。
策略模式把算法的使用放到环境类中,而算法的实现移到具体策略类中,实现了二者的分离。
缺点
客户端必须理解所有策略算法的区别,以便适时选择恰当的算法类。
策略模式造成很多的策略类,增加维护难度。
应用场景
一个系统需要动态地在几种算法中选择一种时,可将每个算法封装到策略类中。
一个类定义了多种行为,并且这些行为在这个类的操作中以多个条件语句的形式出现,可将每个条件分支移入它们各自的策略类中以代替这些条件语句。
系统中各算法彼此完全独立,且要求对客户隐藏具体算法的实现细节时。
系统要求使用算法的客户不应该知道其操作的数据时,可使用策略模式来隐藏与算法相关的数据结构。
多个类只区别在表现行为不同,可以使用策略模式,在运行时动态选择具体要执行的行为。
结构
主要角色
说明
策略模式是准备一组算法,并将这组算法封装到一系列的策略类里面,作为一个抽象策略类的子类。策略模式的重心不是如何实现算法,而是如何组织这些算法,从而让程序结构更加灵活,具有更好的维护性和扩展性。
抽象策略(Strategy)类
定义了一个公共接口,各种不同的算法以不同的方式实现这个接口,环境角色使用这个接口调用不同的算法,一般使用接口或抽象类实现。
具体策略(Concrete Strategy)类
实现了抽象策略定义的接口,提供具体的算法实现。
环境(Context)类
持有一个策略类的引用,最终给客户端调用。
图示

实现
抽象策略类
public interface IStrategy { void StrategyMethod(); //策略方法 }
具体策略类A
public class ConcreteStrategyA : IStrategy { public void StrategyMethod() { Console.WriteLine("具体策略A的策略方法被访问!"); } }
具体策略类B
public class ConcreteStrategyB : IStrategy { public void StrategyMethod() { Console.WriteLine("具体策略B的策略方法被访问!"); } }
环境类
public class Context { private IStrategy strategy; public IStrategy GetStrategy() { return strategy; } public void SetStrategy(IStrategy strategy) { this.strategy = strategy; } public void StrategyMethod() { strategy.StrategyMethod(); } }
客户端
class Client { static void Main(string[] args) { Context c = new Context(); IStrategy s = new ConcreteStrategyA(); c.SetStrategy(s); c.StrategyMethod(); Console.WriteLine(); s = new ConcreteStrategyB(); c.SetStrategy(s); c.StrategyMethod(); Console.Read(); } }
实例
扩展
在环境类中使用策略工厂模式
在一个使用策略模式的系统中,当存在的策略很多时,客户端管理所有策略算法将变得很复杂,如果在环境类中使用策略工厂模式来管理这些策略类将大大减少客户端的工作复杂度。
图示

命令模式(Command)
来源
在软件开发系统中,“方法的请求者”与“方法的实现者”之间经常存在紧密的耦合关系,这不利于软件功能的扩展与维护。例如,想对方法进行“撤销、重做、记录”等处理都很不方便,因此“如何将方法的请求者与实现者解耦?”变得很重要,命令模式就能很好地解决这个问题。 在现实生活中,命令模式的例子也很多。比如看电视时,我们只需要轻轻一按遥控器就能完成频道的切换,这就是命令模式,将换台请求和换台处理完全解耦了。电视机遥控器(命令发送者)通过按钮(具体命令)来遥控电视机(命令接收者)。 再比如,我们去餐厅吃饭,菜单不是等到客人来了之后才定制的,而是已经预先配置好的。这样,客人来了就只需要点菜,而不是任由客人临时定制。餐厅提供的菜单就相当于把请求和处理进行了解耦,这就是命令模式的体现。
定义
将一个请求封装为一个对象,使发出请求的责任和执行请求的责任分割开。这样两者之间通过命令对象进行沟通,这样方便将命令对象进行储存、传递、调用、增加与管理。 即将行为封装成一个命令,然后将命令添加到调用者内,再由调用者发送给真正的接收者。
特点
优点
通过引入中间件(抽象接口)降低系统的耦合度。
扩展性良好,增加或删除命令非常方便。采用命令模式增加与删除命令不会影响其他类,且满足“开闭原则”。
可以实现宏命令。命令模式可以与组合模式结合,将多个命令装配成一个组合命令,即宏命令。
方便实现 Undo 和 Redo 操作。命令模式可以与后面介绍的备忘录模式结合,实现命令的撤销与恢复。
可以在现有命令的基础上,增加额外功能。比如日志记录,结合装饰器模式会更加灵活。
缺点
可能产生大量具体的命令类。因为每一个具体操作都需要设计一个具体命令类,这会增加系统的复杂性。
命令模式的结果其实就是接收方的执行结果,但是为了以命令的形式进行架构、解耦请求与实现,引入了额外类型结构(引入了请求方与抽象命令接口),增加了理解上的困难。不过这也是设计模式的通病,抽象必然会额外增加类的数量,代码抽离肯定比代码聚合更加难理解。
应用场景
说明
当系统的某项操作具备命令语义,且命令实现不稳定(变化)时,可以通过命令模式解耦请求与实现。使用抽象命令接口使请求方的代码架构稳定,封装接收方具体命令的实现细节。接收方与抽象命令呈现弱耦合(内部方法无需一致),具备良好的扩展性。
请求调用者需要与请求接收者解耦时,命令模式可以使调用者和接收者不直接交互。
系统随机请求命令或经常增加、删除命令时,命令模式可以方便地实现这些功能。
当系统需要执行一组操作时,命令模式可以定义宏命令来实现该功能。
当系统需要支持命令的撤销(Undo)操作和恢复(Redo)操作时,可以将命令对象存储起来,采用备忘录模式来实现。
结构
主要角色
说明
将系统中的相关操作抽象成命令,使调用者与实现者相关分离。
实现者/接收者(Receiver)角色
执行命令功能的相关操作,是具体命令对象业务的真正实现者。
抽象命令类(Command)角色
声明执行命令的接口,拥有执行命令的抽象方法 execute()。
具体命令类(Concrete Command)角色
是抽象命令类的具体实现类,它拥有接收者对象,并通过调用接收者的功能来完成命令要执行的操作。
调用者/请求者(Invoker)角色
是请求的发送者,它通常拥有很多的命令对象,并通过访问命令对象来执行相关请求,它不直接访问接收者。
图示

实现
接收者
public class Receiver { public void Action() { Console.WriteLine("接收者的Action()方法被调用..."); } }
抽象命令
public interface ICommand { void Execute(); }
具体命令
public class ConcreteCommand : ICommand { private Receiver receiver; public ConcreteCommand() { receiver = new Receiver(); } public void Execute() { receiver.Action(); //命令执行时调用接收者处理 } }
调用者
public class Invoker { private ICommand command; public Invoker(ICommand command) { this.command = command; } public void SetCommand(ICommand command) { this.command = command; } public void Call() { Console.WriteLine("调用者执行命令command..."); command.Execute(); } }
客户端
class Client { static void Main(string[] args) { ICommand cmd = new ConcreteCommand(); Invoker ir = new Invoker(cmd); Console.WriteLine("客户访问调用者的Call()方法..."); ir.Call(); Console.Read(); } }
实例
扩展
宏命令模式
在软件开发中,有时将命令模式与组合模式联合使用,这就构成了宏命令模式,也叫组合命令模式。宏命令包含了一组命令,它充当了具体命令与调用者的双重角色,执行它时将递归调用它所包含的所有命令。
图示

例:
接收者
public class CompositeReceiver { public void Action1() { Console.WriteLine("接收者的action1()方法被调用..."); } public void Action2() { Console.WriteLine("接收者的action2()方法被调用..."); } }
抽象命令
public interface IAbstractCommand { void Execute(); }
树叶构件: 具体命令
//树叶构件: 具体命令1 public class ConcreteCommand1 : IAbstractCommand { private CompositeReceiver receiver; public ConcreteCommand1() { receiver = new CompositeReceiver(); } public void Execute() { receiver.Action1(); } } //树叶构件: 具体命令2 public class ConcreteCommand2 : IAbstractCommand { private CompositeReceiver receiver; public ConcreteCommand2() { receiver = new CompositeReceiver(); } public void Execute() { receiver.Action2(); } }
树枝构件: 调用者
public class CompositeInvoker : IAbstractCommand { private List<IAbstractCommand> children = new List<IAbstractCommand>(); public void Add(IAbstractCommand c) { children.Add(c); } public void Remove(IAbstractCommand c) { children.Remove(c); } public IAbstractCommand GetChild(int i) { return children[i]; } public void Execute() { foreach (var obj in children) { obj.Execute(); } } }
客户端
class Client { static void Main(string[] args) { IAbstractCommand cmd1 = new ConcreteCommand1(); IAbstractCommand cmd2 = new ConcreteCommand2(); CompositeInvoker ir = new CompositeInvoker(); ir.Add(cmd1); ir.Add(cmd2); Console.WriteLine("客户访问调用者的execute()方法..."); ir.Execute(); Console.Read(); } }
可撤销、重做的命令模式
通过反操作实现
代码
接收者
public class Receiver { public int X { get; private set; } = 0; public int Y { get; private set; } = 0; public void XAdd(int value) { X += value; } public void YAdd(int value) { Y += value; } public void Inspect() { Console.WriteLine($"接收者 X:{X},Y:{Y}。"); } }
抽象命令
public interface ICommand { void Execute(); //执行 void Undo(); //(反操作实现)撤销 }
具体命令
//X加命令 public class XAddCommand : ICommand { private Receiver receiver; public int AddValue { get; set; } public XAddCommand(Receiver receiver) { this.receiver = receiver; } public void Execute() { receiver.XAdd(AddValue); } public void Undo() { receiver.XAdd(-AddValue); } } //Y加命令 public class YAddCommand : ICommand { private Receiver receiver; public int AddValue { get; set; } public YAddCommand(Receiver receiver) { this.receiver = receiver; } public void Execute() { receiver.YAdd(AddValue); } public void Undo() { receiver.YAdd(-AddValue); } }
调用者
public class Invoker { private List<ICommand> undoList = new List<ICommand>(); private List<ICommand> redoList = new List<ICommand>(); private int undoMax = 5; public void ExecuteCommand(ICommand command) //执行新命令 { command.Execute(); undoList.Add(command); //保留最近 undoMax 次操作,删除最早操作 if (undoList.Count > undoMax) { undoList.RemoveAt(0); } // 执行新操作后清空 redoList ,因为这些操作不能恢复了 redoList.Clear(); } public void Undo() //撤销命令 { if (undoList.Count <= 0) { return; } ICommand oldCmd = undoList[undoList.Count - 1]; oldCmd.Undo(); undoList.Remove(oldCmd); redoList.Add(oldCmd); } public void Redo() //重做命令 { if (redoList.Count <= 0) { return; } ICommand newOldCmd = redoList[redoList.Count - 1]; newOldCmd.Execute(); redoList.Remove(newOldCmd); undoList.Add(newOldCmd); } }
客户端
class Client { static void Main(string[] args) { Receiver recv = new Receiver(); Invoker invk = new Invoker(); invk.ExecuteCommand(new XAddCommand(recv) { AddValue = 1 }); invk.ExecuteCommand(new XAddCommand(recv) { AddValue = 2 }); recv.Inspect(); Console.WriteLine(); invk.Undo(); recv.Inspect(); invk.Redo(); recv.Inspect(); invk.Undo(); recv.Inspect(); Console.WriteLine(); invk.ExecuteCommand(new YAddCommand(recv) { AddValue = 1 }); invk.ExecuteCommand(new YAddCommand(recv) { AddValue = 2 }); recv.Inspect(); Console.WriteLine(); invk.Undo(); recv.Inspect(); invk.Redo(); recv.Inspect(); invk.Undo(); recv.Inspect(); Console.WriteLine(); Console.Read(); } }
结合备忘录模式实现
说明
与通过反操作来做撤销重做相比,引入备忘录模式最大的好处是真正保证了数据不会因为反操作带来精度的丢失。并且当计算十分复杂的时候,直接取出之前的数据比重新计算要快。
代码
接收者发起人
public class Receiver { public int X { get; private set; } = 0; public int Y { get; private set; } = 0; public void XAdd(int value) { X += value; } public void YAdd(int value) { Y += value; } public void Inspect() { Console.WriteLine($"接收者 X:{X},Y:{Y}。"); } public Memento CreateMemento() { return new Memento(X, Y); } public void RestoreMemento(Memento m) { X = m.X; Y = m.Y; } }
备忘录
public class Memento { public int X { get; private set; } = 0; public int Y { get; private set; } = 0; public Memento(int x, int y) { X = x; Y = y; } }
备忘录管理者
public class Caretaker { private Memento memento; public void SetMemento(Memento m) { memento = m; } public Memento GetMemento() { return memento; } }
抽象命令
public interface ICommand { void Execute(); //执行 void Undo(); //(反操作实现)撤销 }
具体命令
//X加命令 public class XAddCommand : ICommand { private Receiver receiver; public int AddValue { get; set; } private Caretaker crtr = new Caretaker(); public XAddCommand(Receiver receiver) { this.receiver = receiver; } public void Execute() { crtr.SetMemento(receiver.CreateMemento()); receiver.XAdd(AddValue); } public void Undo() { receiver.RestoreMemento(crtr.GetMemento()); } } //Y加命令 public class YAddCommand : ICommand { private Receiver receiver; public int AddValue { get; set; } private Caretaker crtr = new Caretaker(); public YAddCommand(Receiver receiver) { this.receiver = receiver; } public void Execute() { crtr.SetMemento(receiver.CreateMemento()); receiver.YAdd(AddValue); } public void Undo() { receiver.RestoreMemento(crtr.GetMemento()); } }
调用者
public class Invoker { private List<ICommand> undoList = new List<ICommand>(); private List<ICommand> redoList = new List<ICommand>(); private int undoMax = 5; public void ExecuteCommand(ICommand command) //执行新命令 { command.Execute(); undoList.Add(command); //保留最近 undoMax 次操作,删除最早操作 if (undoList.Count > undoMax) { undoList.RemoveAt(0); } // 执行新操作后清空 redoList ,因为这些操作不能恢复了 redoList.Clear(); } public void Undo() //撤销命令 { if (undoList.Count <= 0) { return; } ICommand oldCmd = undoList[undoList.Count - 1]; oldCmd.Undo(); undoList.Remove(oldCmd); redoList.Add(oldCmd); } public void Redo() //重做命令 { if (redoList.Count <= 0) { return; } ICommand newOldCmd = redoList[redoList.Count - 1]; newOldCmd.Execute(); redoList.Remove(newOldCmd); undoList.Add(newOldCmd); } }
客户端
class Client { static void Main(string[] args) { Receiver recv = new Receiver(); Invoker invk = new Invoker(); invk.ExecuteCommand(new XAddCommand(recv) { AddValue = 1 }); invk.ExecuteCommand(new XAddCommand(recv) { AddValue = 2 }); recv.Inspect(); Console.WriteLine(); invk.Undo(); recv.Inspect(); invk.Redo(); recv.Inspect(); invk.Undo(); recv.Inspect(); Console.WriteLine(); invk.ExecuteCommand(new YAddCommand(recv) { AddValue = 1 }); invk.ExecuteCommand(new YAddCommand(recv) { AddValue = 2 }); recv.Inspect(); Console.WriteLine(); invk.Undo(); recv.Inspect(); invk.Redo(); recv.Inspect(); invk.Undo(); recv.Inspect(); Console.WriteLine(); Console.Read(); } }
责任链模式(Chain of Responsibility)
来源
在现实生活中,一个事件需要经过多个对象处理是很常见的场景。例如,采购审批流程、请假流程等。公司员工请假,可批假的领导有部门负责人、副总经理、总经理等,但每个领导能批准的天数不同,员工必须根据需要请假的天数去找不同的领导签名,也就是说员工必须记住每个领导的姓名、电话和地址等信息,这无疑增加了难度。 在计算机软硬件中也有相关例子,如总线网中数据报传送,每台计算机根据目标地址是否同自己的地址相同来决定是否接收;还有异常处理中,处理程序根据异常的类型决定自己是否处理该异常;还有 Struts2 的拦截器、JSP 和 Servlet 的 Filter 等,所有这些,都可以考虑使用责任链模式来实现。
定义
为了避免请求发送者与多个请求处理者耦合在一起,于是将所有请求的处理者通过前一对象记住其下一个对象的引用而连成一条链;当有请求发生时,可将请求沿着这条链传递,直到有对象处理它为止。 责任链模式也叫职责链模式。
特点
在责任链模式中,客户只需要将请求发送到责任链上即可,无须关心请求的处理细节和请求的传递过程,请求会自动进行传递。所以责任链将请求的发送者和请求的处理者解耦了。
责任链模式的本质是解耦请求与处理,让请求在处理链中能进行传递与被处理;理解责任链模式应当理解其模式,而不是其具体实现。责任链模式的独到之处是将其节点处理者组合成了链式结构,并允许节点自身决定是否进行请求处理或转发,相当于让请求流动起来。
优点
降低了对象之间的耦合度。该模式使得一个对象无须知道到底是哪一个对象处理其请求以及链的结构,发送者和接收者也无须拥有对方的明确信息。
增强了系统的可扩展性。可以根据需要增加新的请求处理类,满足开闭原则。
增强了给对象指派职责的灵活性。当工作流程发生变化,可以动态地改变链内的成员或者调动它们的次序,也可动态地新增或者删除责任。
责任链简化了对象之间的连接。每个对象只需保持一个指向其后继者的引用,不需保持其他所有处理者的引用,这避免了使用众多的 if 或者 if···else 语句。
责任分担。每个类只需要处理自己该处理的工作,不该处理的传递给下一个对象完成,明确各类的责任范围,符合类的单一职责原则。
缺点
不能保证每个请求一定被处理。由于一个请求没有明确的接收者,所以不能保证它一定会被处理,该请求可能一直传到链的末端都得不到处理。
对比较长的职责链,请求的处理可能涉及多个处理对象,系统性能将受到一定影响。
职责链建立的合理性要靠客户端来保证,增加了客户端的复杂性,可能会由于职责链的错误设置而导致系统出错,如可能会造成循环调用。
应用场景
多个对象可以处理一个请求,但具体由哪个对象处理该请求在运行时自动确定。
可动态指定一组对象处理请求,或添加新的处理者。
需要在不明确指定请求处理者的情况下,向多个处理者中的一个提交请求。
结构
主要角色
说明
通常情况下,可以通过数据链表来实现职责链模式的数据结构。
抽象处理者(Handler)角色
定义一个处理请求的接口,包含抽象处理方法和一个后继连接。
具体处理者(Concrete Handler)角色
实现抽象处理者的处理方法,判断能否处理本次请求,如果可以处理请求则处理,否则将该请求转给它的后继者。
客户类(Client)角色
创建处理链,并向链头的具体处理者对象提交请求,它不关心处理细节和请求的传递过程。
图示
模式结构图

责任链

实现
抽象处理者角色
public abstract class Handler { private Handler next; public void SetNext(Handler next) { this.next = next; } public Handler GetNext() { return next; } public abstract void HandleRequest(string request); //处理请求的方法 }
具体处理者角色
public class ConcreteHandler1 : Handler { public override void HandleRequest(string request) { if (request.Equals("one")) { Console.WriteLine("具体处理者1负责处理该请求!"); } else { if (GetNext() != null) { GetNext().HandleRequest(request); } else { Console.WriteLine("没有人处理该请求!"); } } } } public class ConcreteHandler2 : Handler { public override void HandleRequest(string request) { if (request.Equals("two")) { Console.WriteLine("具体处理者2负责处理该请求!"); } else { if (GetNext() != null) { GetNext().HandleRequest(request); } else { Console.WriteLine("没有人处理该请求!"); } } } }
客户端
class Client { static void Main(string[] args) { //组装责任链 Handler handler1 = new ConcreteHandler1(); Handler handler2 = new ConcreteHandler2(); handler1.SetNext(handler2); //提交请求 handler1.HandleRequest("two"); Console.Read(); } } 在上面代码中,我们把消息硬编码为 String 类型,而在真实业务中,消息是具备多样性的,可以是 int、String 或者自定义类型。因此,在上面代码的基础上,可以对消息类型进行抽象 Request,增强了消息的兼容性。
实例
扩展
纯的职责链模式
一个请求必须被某一个处理者对象所接收,且一个具体处理者对某个请求的处理只能采用以下两种行为之一:自己处理(承担责任);把责任推给下家处理。
不纯的职责链模式
允许出现某一个具体处理者对象在承担了请求的一部分责任后又将剩余的责任传给下家的情况,且一个请求可以最终不被任何接收端对象所接收。
状态模式(State)
来源
在软件开发过程中,应用程序中的部分对象可能会根据不同的情况做出不同的行为,我们把这种对象称为有状态的对象,而把影响对象行为的一个或多个动态变化的属性称为状态。当有状态的对象与外部事件产生互动时,其内部状态就会发生改变,从而使其行为也发生改变。如人都有高兴和伤心的时候,不同的情绪有不同的行为,当然外界也会影响其情绪变化。 对这种有状态的对象编程,传统的解决方案是:将这些所有可能发生的情况全都考虑到,然后使用 if-else 或 switch-case 语句来做状态判断,再进行不同情况的处理。但是显然这种做法对复杂的状态判断存在天然弊端,条件判断语句会过于臃肿,可读性差,且不具备扩展性,维护难度也大。且增加新的状态时要添加新的 if-else 语句,这违背了“开闭原则”,不利于程序的扩展。 以上问题如果采用“状态模式”就能很好地得到解决。状态模式的解决思想是:当控制一个对象状态转换的条件表达式过于复杂时,把相关“判断逻辑”提取出来,用各个不同的类进行表示,系统处于哪种情况,直接使用相应的状态类对象进行处理,这样能把原来复杂的逻辑判断简单化,消除了 if-else、switch-case 等冗余语句,代码更有层次性,并且具备良好的扩展力。
定义
对有状态的对象,把复杂的“判断逻辑”提取到不同的状态对象中,允许状态对象在其内部状态发生改变时改变其行为。
特点
优点
结构清晰,状态模式将与特定状态相关的行为局部化到一个状态中,并且将不同状态的行为分割开来,满足“单一职责原则”。
将状态转换显示化,减少对象间的相互依赖。将不同的状态引入独立的对象中会使得状态转换变得更加明确,且减少对象间的相互依赖。
状态类职责明确,有利于程序的扩展。通过定义新的子类很容易地增加新的状态和转换。
缺点
状态模式的使用必然会增加系统的类与对象的个数。
状态模式的结构与实现都较为复杂,如果使用不当会导致程序结构和代码的混乱。
状态模式对开闭原则的支持并不太好,对于可以切换状态的状态模式,增加新的状态类需要修改那些负责状态转换的源码,否则无法切换到新增状态,而且修改某个状态类的行为也需要修改对应类的源码。
应用场景
当一个对象的行为取决于它的状态,并且它必须在运行时根据状态改变它的行为时,就可以考虑使用状态模式。
一个操作中含有庞大的分支结构,并且这些分支决定于对象的状态时。
结构
主要角色
抽象状态(State)角色
定义一个接口,用以封装环境对象中的特定状态所对应的行为,可以有一个或多个行为。
具体状态(Concrete State)角色
实现抽象状态所对应的行为,并且在需要的情况下进行状态切换。
环境类(Context)角色
也称为上下文,它定义了客户端需要的接口,内部维护一个当前状态,并负责具体状态的切换。
图示

实现
抽象状态类
public interface IState { void Handle(Context context); }
具体状态A类
public class ConcreteStateA : IState { public void Handle(Context context) { Console.WriteLine("当前状态是 A."); context.SetState(new ConcreteStateB()); //切换状态 } }
具体状态B类
public class ConcreteStateB : IState { public void Handle(Context context) { Console.WriteLine("当前状态是 B."); context.SetState(new ConcreteStateA()); //切换状态 } }
环境类
public class Context { private IState state; public Context() { this.state = new ConcreteStateA(); //定义环境类的初始状态 } public void SetState(IState state) //设置新状态 { this.state = state; } public IState GetState() //读取状态 { return state; } public void Handle() //对请求做处理 { state.Handle(this); } }
客户端
class Client { static void Main(string[] args) { Context context = new Context(); //创建环境 context.Handle(); //处理请求 context.Handle(); context.Handle(); context.Handle(); Console.Read(); } }
实例
多线程的状态转换程序
场景
先定义一个抽象状态类(TheadState),然后为 线程状态转换图 所示的每个状态设计一个具体状态类, 它们是新建状态(New)、就绪状态(Runnable )、运行状态(Running)、阻塞状态(Blocked)和死亡状态(Dead),每个状态中有触发它们转变状态的方法, 环境类(ThreadContext)中先生成一个初始状态(New),并提供相关触发方法。
线程状态转换图

线程状态转换程序的结构图

例:
抽象状态类:线程状态
public abstract class ThreadState { protected string stateName; //状态名 }
具体状态类:新建状态
public class New : ThreadState { public New() { stateName = "新建状态"; Console.WriteLine("当前线程处于:新建状态."); } public void Start(ThreadContext hj) { Console.Write("调用start()方法-->"); if (stateName.Equals("新建状态")) { hj.SetState(new Runnable()); } else { Console.WriteLine("当前线程不是新建状态,不能调用start()方法."); } } }
具体状态类:就绪状态
public class Runnable : ThreadState { public Runnable() { stateName = "就绪状态"; Console.WriteLine("当前线程处于:就绪状态."); } public void GetCPU(ThreadContext hj) { Console.Write("获得CPU时间-->"); if (stateName.Equals("就绪状态")) { hj.SetState(new Running()); } else { Console.WriteLine("当前线程不是就绪状态,不能获取CPU."); } } }
具体状态类:运行状态
public class Running : ThreadState { public Running() { stateName = "运行状态"; Console.WriteLine("当前线程处于:运行状态."); } public void Suspend(ThreadContext hj) { Console.Write("调用suspend()方法-->"); if (stateName.Equals("运行状态")) { hj.SetState(new Blocked()); } else { Console.WriteLine("当前线程不是运行状态,不能调用suspend()方法."); } } public void Stop(ThreadContext hj) { Console.Write("调用stop()方法-->"); if (stateName.Equals("运行状态")) { hj.SetState(new Dead()); } else { Console.WriteLine("当前线程不是运行状态,不能调用stop()方法."); } } }
具体状态类:阻塞状态
public class Blocked : ThreadState { public Blocked() { stateName = "阻塞状态"; Console.WriteLine("当前线程处于:阻塞状态."); } public void Resume(ThreadContext hj) { Console.Write("调用resume()方法-->"); if (stateName.Equals("阻塞状态")) { hj.SetState(new Runnable()); } else { Console.WriteLine("当前线程不是阻塞状态,不能调用resume()方法."); } } }
具体状态类:死亡状态
public class Dead : ThreadState { public Dead() { stateName = "死亡状态"; Console.WriteLine("当前线程处于:死亡状态."); } }
环境类
public class ThreadContext { private ThreadState state; public ThreadContext() { state = new New(); } public void SetState(ThreadState state) { this.state = state; } public ThreadState GetState() { return state; } public void Start() { ((New)state).Start(this); } public void GetCPU() { ((Runnable)state).GetCPU(this); } public void Suspend() { ((Running)state).Suspend(this); } public void Stop() { ((Running)state).Stop(this); } public void Resume() { ((Blocked)state).Resume(this); } }
客户端
class Client { static void Main(string[] args) { ThreadContext context = new ThreadContext(); context.Start(); context.GetCPU(); context.Suspend(); context.Resume(); context.GetCPU(); context.Stop(); Console.Read(); } }
扩展
共享状态模式
场景
在有些情况下,可能有多个环境对象需要共享一组状态,这时需要引入享元模式,将这些具体状态对象放在集合中供程序共享。
特点
共享状态模式的不同之处是在环境类中增加了一个 HashMap 来保存相关状态,当需要某种状态时可以从中获取。
图示

例:
抽象状态类
public abstract class ShareState { public abstract void Handle(ShareContext context); }
具体状态类
public class ConcreteState1 : ShareState { public override void Handle(ShareContext context) { Console.WriteLine("当前状态是: 状态1"); context.SetState(context.GetState("2")); } } public class ConcreteState2 : ShareState { public override void Handle(ShareContext context) { Console.WriteLine("当前状态是: 状态2"); context.SetState(context.GetState("1")); } }
环境类
public class ShareContext { private ShareState state; private Dictionary<string, ShareState> stateSet = new Dictionary<string, ShareState>(); public ShareContext() { state = new ConcreteState1(); stateSet.Add("1", state); state = new ConcreteState2(); stateSet.Add("2", state); state = GetState("1"); } public void SetState(ShareState state) //设置新状态 { this.state = state; } public ShareState GetState(string key) //读取状态 { stateSet.TryGetValue(key, out ShareState s); return s; } public void Handle() //对请求做处理 { state.Handle(this); } }
客户端
class Client { static void Main(string[] args) { ShareContext context = new ShareContext(); //创建环境 context.Handle(); //处理请求 context.Handle(); context.Handle(); context.Handle(); Console.Read(); } }
拓展
状态模式与责任链模式的区别
状态模式和责任链模式都能消除 if-else 分支过多的问题。但在某些情况下,状态模式中的状态可以理解为责任,那么在这种情况下,两种模式都可以使用。 从定义来看,状态模式强调的是一个对象内在状态的改变,而责任链模式强调的是外部节点对象间的改变。 从代码实现上来看,两者最大的区别就是状态模式的各个状态对象知道自己要进入的下一个状态对象,而责任链模式并不清楚其下一个节点处理对象,因为链式组装由客户端负责。
状态模式与策略模式的区别
状态模式和策略模式的 UML 类图架构几乎完全一样,但两者的应用场景是不一样的。策略模式的多种算法行为择其一都能满足,彼此之间是独立的,用户可自行更换策略算法,而状态模式的各个状态间存在相互关系,彼此之间在一定条件下存在自动切换状态的效果,并且用户无法指定状态,只能设置初始状态。
观察者模式(Observer)
来源
在现实世界中,许多对象并不是独立存在的,其中一个对象的行为发生改变可能会导致一个或者多个其他对象的行为也发生改变。例如,某种商品的物价上涨时会导致部分商家高兴,而消费者伤心;还有,当我们开车到交叉路口时,遇到红灯会停,遇到绿灯会行。这样的例子还有很多,例如,股票价格与股民、微信公众号与微信用户、气象局的天气预报与听众、小偷与警察等。 在软件世界也是这样,例如,Excel 中的数据与折线图、饼状图、柱状图之间的关系;MVC 模式中的模型与视图的关系;事件模型中的事件源与事件处理者。所有这些,如果用观察者模式来实现就非常方便。
定义
指多个对象间存在一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。 这种模式有时又称作发布-订阅模式、模型-视图模式。
特点
优点
降低了目标与观察者之间的耦合关系,两者之间是抽象耦合关系。符合依赖倒置原则。
目标与观察者之间建立了一套触发机制。
缺点
目标与观察者之间的依赖关系并没有完全解除,而且有可能出现循环引用。
当观察者对象很多时,通知的发布会花费很多时间,影响程序的效率。
应用场景
说明
在软件系统中,当系统一方行为依赖另一方行为的变动时,可使用观察者模式松耦合联动双方,使得一方的变动可以通知到感兴趣的另一方对象,从而让另一方对象对此做出响应。
对象间存在一对多关系,一个对象的状态发生改变会影响其他对象。
当一个抽象模型有两个方面,其中一个方面依赖于另一方面时,可将这二者封装在独立的对象中以使它们可以各自独立地改变和复用。
实现类似广播机制的功能,不需要知道具体收听者,只需分发广播,系统中感兴趣的对象会自动接收该广播。
多层级嵌套使用,形成一种链式触发机制,使得事件具备跨域(跨越两种观察者类型)通知。
结构
主要角色
说明
实现观察者模式时要注意具体目标对象和具体观察者对象之间不能直接调用,否则将使两者之间紧密耦合起来,这违反了面向对象的设计原则。
抽象主题(Subject)角色
也叫抽象目标类,它提供了一个用于保存观察者对象的聚集类和增加、删除观察者对象的方法,以及通知所有观察者的抽象方法。
具体主题(Concrete Subject)角色
也叫具体目标类,它实现抽象目标中的通知方法,当具体主题的内部状态发生改变时,通知所有注册过的观察者对象。
抽象观察者(Observer)角色
它是一个抽象类或接口,它包含了一个更新自己的抽象方法,当接到具体主题的更改通知时被调用。
具体观察者(Concrete Observer)角色
实现抽象观察者中定义的抽象方法,以便在得到目标的更改通知时更新自身的状态。
图示

实现
抽象目标
public abstract class Subject { protected List<IObserver> observers = new List<IObserver>(); public void Add(IObserver observer) //增加观察者 { observers.Add(observer); } public void Remove(IObserver observer) //删除观察者 { observers.Remove(observer); } public abstract void NotifyObserver(); //通知观察者 }
具体目标
public class ConcreteSubject : Subject { public override void NotifyObserver() { Console.WriteLine("具体目标发生改变..."); Console.WriteLine("--------------"); foreach (var obs in observers) { obs.Response(); } } }
抽象观察者
public interface IObserver { void Response(); //响应 }
具体观察者
public class ConcreteObserver1 : IObserver { public void Response() { Console.WriteLine("具体观察者1作出响应!"); } } public class ConcreteObserver2 : IObserver { public void Response() { Console.WriteLine("具体观察者2作出响应!"); } }
客户端
class Client { static void Main(string[] args) { Subject subject = new ConcreteSubject(); IObserver obs1 = new ConcreteObserver1(); IObserver obs2 = new ConcreteObserver2(); subject.Add(obs1); subject.Add(obs2); subject.NotifyObserver(); Console.Read(); } }
实例
扩展
中介者模式(Mediator)
来源
在现实生活中,常常会出现好多对象之间存在复杂的交互关系,这种交互关系常常是“网状结构”,它要求每个对象都必须知道它需要交互的对象。例如,每个人必须记住他(她)所有朋友的电话;而且,朋友中如果有人的电话修改了,他(她)必须让其他所有的朋友一起修改,这叫作“牵一发而动全身”,非常复杂。 如果把这种“网状结构”改为“星形结构”的话,将大大降低它们之间的“耦合性”,这时只要找一个“中介者”就可以了。如前面所说的“每个人必须记住所有朋友电话”的问题,只要在网上建立一个每个朋友都可以访问的“通信录”就解决了。这样的例子还有很多,例如,你刚刚参加工作想租房,可以找“房屋中介”;或者,自己刚刚到一个陌生城市找工作,可以找“人才交流中心”帮忙。 在软件的开发过程中,这样的例子也很多,例如,在 MVC 框架中,控制器(C)就是模型(M)和视图(V)的中介者;还有大家常用的 QQ 聊天程序的“中介者”是 QQ 服务器。所有这些,都可以采用“中介者模式”来实现,它将大大降低对象之间的耦合性,提高系统的灵活性。
定义
定义一个中介对象来封装一系列对象之间的交互,使原有对象之间的耦合松散,且可以独立地改变它们之间的交互。 中介者模式又叫调停模式,它是迪米特法则的典型应用。
特点
优点
类之间各司其职,符合迪米特法则。
降低了对象之间的耦合性,使得对象易于独立地被复用。
将对象间的一对多关联转变为一对一的关联,提高系统的灵活性,使得系统易于维护和扩展。
缺点
中介者模式将原本多个对象直接的相互依赖变成了中介者和多个同事类的依赖关系。当同事类越多时,中介者就会越臃肿,变得复杂且难以维护。
应用场景
当对象之间存在复杂的网状结构关系而导致依赖关系混乱且难以复用时。
当想创建一个运行于多个类之间的对象,又不想生成新的子类时。
结构
主要角色
抽象中介者(Mediator)角色
它是中介者的接口,提供了同事对象注册与转发同事对象信息的抽象方法。
具体中介者(Concrete Mediator)角色
实现中介者接口,定义一个 List 来管理同事对象,协调各个同事角色之间的交互关系,因此它依赖于同事角色。
抽象同事类(Colleague)角色
定义同事类的接口,保存中介者对象,提供同事对象交互的抽象方法,实现所有相互影响的同事类的公共功能。
具体同事类(Concrete Colleague)角色
是抽象同事类的实现者,当需要与其他同事对象交互时,由中介者对象负责后续的交互。
图示

实现
抽象中介者
public abstract class Mediator { public abstract void Register(Colleague colleague); public abstract void Relay(Colleague cl); //转发 }
具体中介者
public class ConcreteMediator : Mediator { private List<Colleague> colleagues = new List<Colleague>(); public override void Register(Colleague colleague) { if (!colleagues.Contains(colleague)) { colleagues.Add(colleague); colleague.SetMedium(this); } } public override void Relay(Colleague cl) { foreach (var ob in colleagues) { if (!ob.Equals(cl)) { ob.Receive(); } } } }
抽象同事类
public abstract class Colleague { protected Mediator mediator; public void SetMedium(Mediator mediator) { this.mediator = mediator; } public abstract void Receive(); public abstract void Send(); }
具体同事类
public class ConcreteColleague1 : Colleague { public override void Receive() { Console.WriteLine("具体同事类1收到请求。"); } public override void Send() { Console.WriteLine("具体同事类1发出请求。"); mediator.Relay(this); //请中介者转发 } } public class ConcreteColleague2 : Colleague { public override void Receive() { Console.WriteLine("具体同事类2收到请求。"); } public override void Send() { Console.WriteLine("具体同事类2发出请求。"); mediator.Relay(this); //请中介者转发 } }
客户端
class Client { static void Main(string[] args) { Mediator md = new ConcreteMediator(); Colleague c1 = new ConcreteColleague1(); Colleague c2 = new ConcreteColleague2(); md.Register(c1); md.Register(c2); c1.Send(); Console.WriteLine(); c2.Send(); Console.Read(); } }
实例
扩展
简化的中介者模式
在实际开发中,通常采用以下两种方法来简化中介者模式,使开发变得更简单。
不定义中介者接口,把具体中介者对象实现成为单例。
同事对象不持有中介者,而是在需要的时候直接获取中介者对象并调用。
图示

例:
单例中介者
public class SimpleMediator { private static SimpleMediator smd = new SimpleMediator(); private List<ISimpleColleague> colleagues = new List<ISimpleColleague>(); private SimpleMediator() { } public static SimpleMediator GetMedium() { return smd; } public void Register(ISimpleColleague colleague) { if (!colleagues.Contains(colleague)) { colleagues.Add(colleague); } } public void Relay(ISimpleColleague scl) { foreach (var ob in colleagues) { if (!ob.Equals(scl)) { ob.Receive(); } } } }
抽象同事类
public interface ISimpleColleague { void Receive(); void Send(); }
具体同事类
public class SimpleConcreteColleague1 : ISimpleColleague { public SimpleConcreteColleague1() { SimpleMediator smd = SimpleMediator.GetMedium(); smd.Register(this); } public void Receive() { Console.WriteLine("具体同事类1:收到请求。"); } public void Send() { SimpleMediator smd = SimpleMediator.GetMedium(); Console.WriteLine("具体同事类1:发出请求..."); smd.Relay(this); //请中介者转发 } } public class SimpleConcreteColleague2 : ISimpleColleague { public SimpleConcreteColleague2() { SimpleMediator smd = SimpleMediator.GetMedium(); smd.Register(this); } public void Receive() { Console.WriteLine("具体同事类2:收到请求。"); } public void Send() { SimpleMediator smd = SimpleMediator.GetMedium(); Console.WriteLine("具体同事类2:发出请求..."); smd.Relay(this); //请中介者转发 } }
客户端
class Client { static void Main(string[] args) { ISimpleColleague c1 = new SimpleConcreteColleague1(); ISimpleColleague c2 = new SimpleConcreteColleague2(); c1.Send(); Console.WriteLine(); c2.Send(); Console.Read(); } }
迭代器模式(Iterator)
来源
在现实生活以及程序设计中,经常要访问一个聚合对象中的各个元素,如“数据结构”中的链表遍历,通常的做法是将链表的创建和遍历都放在同一个类中,但这种方式不利于程序的扩展,如果要更换遍历方法就必须修改程序源代码,这违背了 “开闭原则”。 既然将遍历方法封装在聚合类中不可取,那么聚合类中不提供遍历方法,将遍历方法由用户自己实现是否可行呢?答案是同样不可取,因为这种方式会存在两个缺点: 1.暴露了聚合类的内部表示,使其数据不安全; 2.增加了客户的负担。 “迭代器模式”能较好地克服以上缺点,它在客户访问类与聚合类之间插入一个迭代器,这分离了聚合对象与其遍历行为,对客户也隐藏了其内部细节,且满足“单一职责原则”和“开闭原则”,如 Java 中的 Collection、List、Set、Map 等都包含了迭代器。 迭代器模式在生活中应用的比较广泛,比如:物流系统中的传送带,不管传送的是什么物品,都会被打包成一个个箱子,并且有一个统一的二维码。这样我们不需要关心箱子里是什么,在分发时只需要一个个检查发送的目的地即可。再比如,我们平时乘坐交通工具,都是统一刷卡或者刷脸进站,而不需要关心是男性还是女性、是残疾人还是正常人等信息。
定义
提供一个对象来顺序访问聚合对象中的一系列数据,而不暴露聚合对象的内部表示。
特点
优点
访问一个聚合对象的内容而无须暴露它的内部表示。
遍历任务交由迭代器完成,这简化了聚合类。
它支持以不同方式遍历一个聚合,甚至可以自定义迭代器的子类以支持新的遍历。
增加新的聚合类和迭代器类都很方便,无须修改原有代码。
封装性良好,为遍历不同的聚合结构提供一个统一的接口。
缺点
增加了类的个数,这在一定程度上增加了系统的复杂性。
应用场景
当需要为聚合对象提供多种遍历方式时。
当需要为遍历不同的聚合结构提供一个统一的接口时。
当访问一个聚合对象的内容而无须暴露其内部细节的表示时。
结构
主要角色
说明
迭代器模式是通过将聚合对象的遍历行为分离出来,抽象成迭代器类来实现的,其目的是在不暴露聚合对象的内部结构的情况下,让外部代码透明地访问聚合的内部数据。
抽象聚合(Aggregate)角色
定义存储、添加、删除聚合对象以及创建迭代器对象的接口。
具体聚合(ConcreteAggregate)角色
实现抽象聚合类,返回一个具体迭代器的实例。
抽象迭代器(Iterator)角色
定义访问和遍历聚合元素的接口,通常包含 hasNext()、first()、next() 等方法。
具体迭代器(Concretelterator)角色
实现抽象迭代器接口中所定义的方法,完成对聚合对象的遍历,记录遍历的当前位置。
图示

实现
抽象聚合
public interface IAggregate { void Add(object obj); void Remove(object obj); Iterator GetIterator(); }
具体聚合
public class ConcreteAggregate : IAggregate { private List<object> list = new List<object>(); public void Add(object obj) { list.Add(obj); } public void Remove(object obj) { list.Remove(obj); } public Iterator GetIterator() { return (new ConcreteIterator(list)); } }
抽象迭代器
public interface Iterator { object First(); object Next(); bool HasNext(); }
具体迭代器
public class ConcreteIterator : Iterator { private List<object> list = null; private int index = -1; public ConcreteIterator(List<object> list) { this.list = list; } public bool HasNext() { if (index < list.Count - 1) { return true; } else { return false; } } public object First() { index = 0; object obj = list[index]; return obj; } public object Next() { object obj = null; if (this.HasNext()) { obj = list[++index]; } return obj; } }
客户端
class Client { static void Main(string[] args) { IAggregate ag = new ConcreteAggregate(); ag.Add("中山大学"); ag.Add("华南理工"); ag.Add("韶关学院"); Console.Write("聚合的内容有:"); object obj; Iterator it = ag.GetIterator(); while (it.HasNext()) { obj = it.Next(); Console.Write(obj.ToString() + "\t"); } obj = it.First(); Console.WriteLine("\nFirst:" + obj.ToString()); Console.Read(); } }
实例
扩展
与组合模式结合
说明
迭代器模式常常与组合模式结合起来使用,在对组合模式中的容器构件进行访问时,经常将迭代器潜藏在组合模式的容器构成类中。当然,也可以构造一个外部迭代器来对容器构件进行访问。
图示

访问者模式(Visitor)
来源
在现实生活中,有些集合对象存在多种不同的元素,且每种元素也存在多种不同的访问者和处理方式。例如,公园中存在多个景点,也存在多个游客,不同的游客对同一个景点的评价可能不同;医院医生开的处方单中包含多种药元素,査看它的划价员和药房工作人员对它的处理方式也不同,划价员根据处方单上面的药品名和数量进行划价,药房工作人员根据处方单的内容进行抓药。 这样的例子还有很多,例如,电影或电视剧中的人物角色,不同的观众对他们的评价也不同;还有顾客在商场购物时放在“购物车”中的商品,顾客主要关心所选商品的性价比,而收银员关心的是商品的价格和数量。 这些被处理的数据元素相对稳定而访问方式多种多样的数据结构,如果用“访问者模式”来处理比较方便。访问者模式能把处理方法从数据结构中分离出来,并可以根据需要增加新的处理方法,且不用修改原来的程序代码与数据结构,这提高了程序的扩展性和灵活性。
定义
将作用于某种数据结构中的各元素的操作分离出来封装成独立的类,使其在不改变数据结构的前提下可以添加作用于这些元素的新的操作,为数据结构中的每个元素提供多种访问方式。它将对数据的操作与数据结构进行分离,是行为类模式中最复杂的一种模式。
特点
优点
扩展性好。能够在不修改对象结构中的元素的情况下,为对象结构中的元素添加新的功能。
复用性好。可以通过访问者来定义整个对象结构通用的功能,从而提高系统的复用程度。
灵活性好。访问者模式将数据结构与作用于结构上的操作解耦,使得操作集合可相对自由地演化而不影响系统的数据结构。
符合单一职责原则。访问者模式把相关的行为封装在一起,构成一个访问者,使每一个访问者的功能都比较单一。
缺点
增加新的元素类很困难。在访问者模式中,每增加一个新的元素类,都要在每一个具体访问者类中增加相应的具体操作,这违背了“开闭原则”。
破坏封装。访问者模式中具体元素对访问者公布细节,这破坏了对象的封装性。
违反了依赖倒置原则。访问者模式依赖了具体类,而没有依赖抽象类。
应用场景
说明
当系统中存在类型数量稳定(固定)的一类数据结构时,可以使用访问者模式方便地实现对该类型所有数据结构的不同操作,而又不会对数据产生任何副作用(脏数据)。 简而言之,就是当对集合中的不同类型数据(类型数量稳定)进行多种操作时,使用访问者模式。
对象结构相对稳定,但其操作算法经常变化的程序。
对象结构中的对象需要提供多种不同且不相关的操作,而且要避免让这些操作的变化影响对象的结构。
对象结构包含很多类型的对象,希望对这些对象实施一些依赖于其具体类型的操作。
结构
主要角色
说明
访问者(Visitor)模式实现的关键是如何将作用于元素的操作分离出来封装成独立的类
抽象访问者(Visitor)角色
定义一个访问具体元素的接口,为每个具体元素类对应一个访问操作 visit() ,该操作中的参数类型标识了被访问的具体元素。
具体访问者(ConcreteVisitor)角色
实现抽象访问者角色中声明的各个访问操作,确定访问者访问一个元素时该做什么。
抽象元素(Element)角色
声明一个包含接受操作 accept() 的接口,被接受的访问者对象作为 accept() 方法的参数。
具体元素(ConcreteElement)角色
实现抽象元素角色提供的 accept() 操作,其方法体通常都是 visitor.visit(this) ,另外具体元素中可能还包含本身业务逻辑的相关操作。
对象结构(Object Structure)角色
是一个包含元素角色的容器,提供让访问者对象遍历容器中的所有元素的方法,通常由 List、Set、Map 等聚合类实现。
图示

实现
抽象访问者
public interface IVisitor { void Visit(ConcreteElementA element); void Visit(ConcreteElementB element); }
具体访问者
public class ConcreteVisitorA : IVisitor { public void Visit(ConcreteElementA element) { Console.WriteLine("具体访问者A访问-->" + element.OperationA()); } public void Visit(ConcreteElementB element) { Console.WriteLine("具体访问者A访问-->" + element.OperationB()); } } public class ConcreteVisitorB : IVisitor { public void Visit(ConcreteElementA element) { Console.WriteLine("具体访问者B访问-->" + element.OperationA()); } public void Visit(ConcreteElementB element) { Console.WriteLine("具体访问者B访问-->" + element.OperationB()); } }
抽象元素
public interface IElement { void Accept(IVisitor visitor); }
具体元素
public class ConcreteElementA : IElement { public void Accept(IVisitor visitor) { visitor.Visit(this); } public string OperationA() { return "具体元素A的操作。"; } } public class ConcreteElementB : IElement { public void Accept(IVisitor visitor) { visitor.Visit(this); } public string OperationB() { return "具体元素B的操作。"; } }
对象结构角色
public class ObjectStructure { private List<IElement> list = new List<IElement>(); public void Accept(IVisitor visitor) { List<IElement>.Enumerator itr = list.GetEnumerator(); while (itr.MoveNext()) { itr.Current.Accept(visitor); } } public void Add(IElement element) { list.Add(element); } public void Remove(IElement element) { list.Remove(element); } }
客户端
class Client { static void Main(string[] args) { ObjectStructure os = new ObjectStructure(); os.Add(new ConcreteElementA()); os.Add(new ConcreteElementB()); IVisitor visitor = new ConcreteVisitorA(); os.Accept(visitor); Console.WriteLine(); visitor = new ConcreteVisitorB(); os.Accept(visitor); Console.Read(); } }
实例
扩展
与“迭代器模式”联用
因为访问者模式中的“对象结构”是一个包含元素角色的容器,当访问者遍历容器中的所有元素时,常常要用迭代器。 如果对象结构中的聚合类没有提供迭代器,也可以用迭代器模式自定义一个。
与“组合模式”联用
因为访问者(Visitor)模式中的“元素对象”可能是叶子对象或者是容器对象,如果元素对象包含容器对象,就必须用到组合模式。
图示

备忘录模式(Memento)
来源
每个人都有犯错误的时候,都希望有种“后悔药”能弥补自己的过失,让自己重新开始,但现实是残酷的。在计算机应用中,客户同样会常常犯错误,能否提供“后悔药”给他们呢?当然是可以的,而且是有必要的。这个功能由“备忘录模式”来实现。 其实很多应用软件都提供了这项功能,如 Word、记事本、Photoshop、Eclipse 等软件在编辑时按 Ctrl+Z 组合键时能撤销当前操作,使文档恢复到之前的状态;还有在 IE 中的后退键、数据库事务管理中的回滚操作、玩游戏时的中间结果存档功能、数据库与操作系统的备份操作、棋类游戏中的悔棋功能等都属于这类。 备忘录模式能记录一个对象的内部状态,当用户后悔时能撤销当前操作,使数据恢复到它原先的状态。
定义
在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,以便以后当需要时能将该对象恢复到原先保存的状态。 该模式又叫做快照模式(Snapshot Pattern)或Token模式。
特点
优点
提供了一种可以恢复状态的机制。当用户需要时能够比较方便地将数据恢复到某个历史的状态。
实现了内部状态的封装。除了创建它的发起人之外,其他对象都不能够访问这些状态信息。
简化了发起人类。发起人不需要管理和保存其内部状态的各个备份,所有状态信息都保存在备忘录中,并由管理者进行管理,这符合单一职责原则。
缺点
资源消耗大。如果要保存的内部状态信息过多或者特别频繁,将会占用比较大的内存资源。
应用场景
需要保存与恢复数据的场景,如玩游戏时的中间结果的存档功能。
需要提供一个可回滚操作的场景,如 Word、记事本、Photoshop,Eclipse 等软件在编辑时按 Ctrl+Z 组合键,还有数据库中事务操作。
结构
主要角色
说明
备忘录模式的核心是设计备忘录类以及用于管理备忘录的管理者类
发起人(Originator)角色
记录当前时刻的内部状态信息,提供创建备忘录和恢复备忘录数据的功能,实现其他业务功能,它可以访问备忘录里的所有信息。 一般将需要保存内部状态的类设计为发起人角色。
备忘录(Memento)角色
负责存储发起人的内部状态,在需要的时候提供这些内部状态给发起人。 它通常提供了与发起人角色相对应的属性(可以是全部,也可以是部分)用于存储发起人角色的状态。
管理者(Caretaker)角色
对备忘录进行管理,提供保存与获取备忘录的功能,但其不能对备忘录的内容进行访问与修改。
图示

实现
发起人
public class Originator { private string state; public void SetState(string state) { this.state = state; } public string GetState() { return state; } public Memento CreateMemento() { return new Memento(state); } public void RestoreMemento(Memento m) { this.SetState(m.GetState()); } }
备忘录
public class Memento { private string state; public Memento(string state) { this.state = state; } public void SetState(string state) { this.state = state; } public string GetState() { return state; } }
管理者
单状态备份
public class Caretaker { private Memento memento; public void SetMemento(Memento m) { memento = m; } public Memento GetMemento() { return memento; } }
多状态备份
public class Caretaker { private Stack<Memento> memento; public Caretaker() { memento = new Stack<Memento>(); } public void SetMemento(Memento m) { memento.Push(m); } public Memento GetMemento() { return memento.Pop(); } }
客户端
class Client { static void Main(string[] args) { Originator orgr = new Originator(); Caretaker crtr = new Caretaker(); orgr.SetState("S0"); //发起人设置状态 Console.WriteLine("初始状态:" + orgr.GetState()); crtr.SetMemento(orgr.CreateMemento()); //管理者保存发起人创建的备忘录 orgr.SetState("S1"); //发起人设置新状态 Console.WriteLine("新的状态:" + orgr.GetState()); orgr.RestoreMemento(crtr.GetMemento()); //发起人恢复出管理者保存的备忘录 Console.WriteLine("恢复状态:" + orgr.GetState()); Console.Read(); } }
实例
扩展
与原型模式联合使用
说明
在备忘录模式中,通过定义“备忘录”来备份“发起人”的信息,而原型模式的 clone() 方法具有自备份功能,所以,如果让发起人实现 Cloneable 接口就有备份自己的功能,这时可以删除备忘录类。
图示

例:
发起人原型
public class OriginatorPrototype : ICloneable { private string state; public void SetState(string state) { this.state = state; } public string GetState() { return state; } public OriginatorPrototype CreateMemento() { return this.Clone() as OriginatorPrototype; } public void RestoreMemento(OriginatorPrototype orpt) { this.SetState(orpt.GetState()); } public object Clone() { try { return (OriginatorPrototype)this.MemberwiseClone(); } catch (Exception e) { Console.WriteLine($"原型复制异常:{e.Message}{Environment.NewLine}{e.StackTrace}"); } return null; } }
原型管理者
public class PrototypeCaretaker { private OriginatorPrototype orpt; public void SetMemento(OriginatorPrototype orpt) { this.orpt = orpt; } public OriginatorPrototype GetMemento() { return orpt; } }
客户端
class Client { static void Main(string[] args) { OriginatorPrototype orgr = new OriginatorPrototype(); PrototypeCaretaker crtr = new PrototypeCaretaker(); orgr.SetState("S0"); //发起人设置状态 Console.WriteLine("初始状态:" + orgr.GetState()); crtr.SetMemento(orgr.CreateMemento()); //管理者保存发起人创建的备忘录 orgr.SetState("S1"); //发起人设置新状态 Console.WriteLine("新的状态:" + orgr.GetState()); orgr.RestoreMemento(crtr.GetMemento()); //发起人恢复出管理者保存的备忘录 Console.WriteLine("恢复状态:" + orgr.GetState()); Console.Read(); } }
解释器(类)模式(Interpreter)
来源
在软件开发中,会遇到有些问题多次重复出现,而且有一定的相似性和规律性。如果将它们归纳成一种简单的语言,那么这些问题实例将是该语言的一些句子,这样就可以用“编译原理”中的解释器模式来实现了。 虽然使用解释器模式的实例不是很多,但对于满足以上特点,且对运行效率要求不是很高的应用实例,如果用解释器模式来实现,其效果是非常好的,本文将介绍其工作原理与使用方法。
定义
给分析对象定义一个语言,并定义该语言的文法表示,再设计一个解析器来解释语言中的句子。也就是说,用编译语言的方式来分析应用中的实例。 这种模式实现了文法表达式处理的接口,该接口解释一个特定的上下文。
这里提到的文法和句子的概念同编译原理中的描述相同,“文法”指语言的语法规则,而“句子”是语言集中的元素。例如,汉语中的句子有很多,“我是中国人”是其中的一个句子,可以用一棵语法树来直观地描述语言中的句子。
特点
优点
扩展性好。由于在解释器模式中使用类来表示语言的文法规则,因此可以通过继承等机制来改变或扩展文法。
容易实现。在语法树中的每个表达式节点类都是相似的,所以实现其文法较为容易。
缺点
执行效率较低。解释器模式中通常使用大量的循环和递归调用,当要解释的句子较复杂时,其运行速度很慢,且代码的调试过程也比较麻烦。
会引起类膨胀。解释器模式中的每条规则至少需要定义一个类,当包含的文法规则很多时,类的个数将急剧增加,导致系统难以管理与维护。
可应用的场景比较少。在软件开发中,需要定义语言文法的应用实例非常少,所以这种模式很少被使用到。
应用场景
注意:解释器模式在实际的软件开发中使用比较少,因为它会引起效率、性能以及维护等问题。如果碰到对表达式的解释,在 Java 中可以用 Expression4J 或 Jep 等来设计。
当语言的文法较为简单,且执行效率不是关键问题时。
当问题重复出现,且可以用一种简单的语言来进行表达时。
当一个语言需要解释执行,并且语言中的句子可以表示为一个抽象语法树的时候,如 XML 文档解释。
结构
基本概念
文法
定义
文法是用于描述语言的语法结构的形式规则。
说明
没有规矩不成方圆,例如,有些人认为完美爱情的准则是“相互吸引、感情专一、任何一方都没有恋爱经历”,虽然最后一条准则较苛刻,但任何事情都要有规则,语言也一样,不管它是机器语言还是自然语言,都有它自己的文法规则。例如,中文中的“句子”的文法如下:
〈句子〉::=〈主语〉〈谓语〉〈宾语〉 〈主语〉::=〈代词〉|〈名词〉 〈谓语〉::=〈动词〉 〈宾语〉::=〈代词〉|〈名词〉 〈代词〉你|我|他 〈名词〉7大学生I筱霞I英语 〈动词〉::=是|学习 注:这里的符号“::=”表示“定义为”的意思,用“〈”和“〉”括住的是非终结符,没有括住的是终结符。 一、非终结符: 1、非终结符可以再分成更细的东西。 2、不是终结符的都是非终结符。非终结符可理解为一个可拆分元素,而终结符是不可拆分的最小元素。终结符号就是语言中用到的基本元素,名词、动词、形容词、助词等等基本语言单位。 二、终结符: 1、终结符直接就代表一个意思,比如关键字if就不能再分成i和f了。 2、通俗的说就是不能单独出现在推导式左边的符号,也就是说终结符不能再进行推导。非终结符则是"语法"中用到的元素,除非谈论"语法",一般交谈语言中并不会用到非终结符。比如:主语、短语、词组、句子。
句子
定义
句子是语言的基本单位,是语言集中的一个元素,它由终结符构成,能由“文法”推导出。
说明
例如,上述文法可以推出“我是大学生”,所以它是句子。
语法树
定义
语法树是句子结构的一种树型表示,它代表了句子的推导结果,它有利于理解句子语法结构的层次。
说明
图示

主要角色
抽象表达式(Abstract Expression)角色
定义解释器的接口,约定解释器的解释操作,主要包含解释方法 interpret()。
终结符表达式(Terminal Expression)角色
是抽象表达式的子类,用来实现文法中与终结符相关的操作,文法中的每一个终结符都有一个具体终结表达式与之相对应。
非终结符表达式(Nonterminal Expression)角色
也是抽象表达式的子类,用来实现文法中与非终结符相关的操作,文法中的每条规则都对应于一个非终结符表达式。
环境(Context)角色
通常包含各个解释器需要的数据或是公共的功能,一般用来传递被所有解释器共享的数据,后面的解释器可以从这里获取这些值。
客户端(Client)
主要任务是将需要分析的句子或表达式转换成使用解释器对象描述的抽象语法树,然后调用解释器的解释方法,当然也可以通过环境角色间接访问解释器的解释方法。
图示

实现
解释器模式实现的关键是定义文法规则、设计终结符类与非终结符类、画出结构图,必要时构建语法树。
实例
场景
【例1】用解释器模式设计一个“韶粵通”公交车卡的读卡器程序。 说明:假如“韶粵通”公交车读卡器可以判断乘客的身份,如果是“韶关”或者“广州”的“老人”“妇女”“儿童”就可以免费乘车,其他人员乘车一次扣 2 元。 分析:本实例用“解释器模式”设计比较适合,首先设计其文法规则如下。 <expression> ::= <city>的<person> <city> ::= 韶关|广州 <person> ::= 老人|妇女|儿童 然后,根据文法规则按以下步骤设计公交车卡的读卡器程序的类图。 定义一个抽象表达式(Expression)接口,它包含了解释方法 interpret(String info)。 定义一个终结符表达式(Terminal Expression)类,它用集合(Set)类来保存满足条件的城市或人,并实现抽象表达式接口中的解释方法 interpret(String info),用来判断被分析的字符串是否是集合中的终结符。 定义一个非终结符表达式(AndExpressicm)类,它也是抽象表达式的子类,它包含满足条件的城市的终结符表达式对象和满足条件的人员的终结符表达式对象,并实现 interpret(String info) 方法,用来判断被分析的字符串是否是满足条件的城市中的满足条件的人员。 最后,定义一个环境(Context)类,它包含解释器需要的数据,完成对终结符表达式的初始化,并定义一个方法 freeRide(String info) 调用表达式对象的解释方法来对被分析的字符串进行解释。其结构图如图所示。 
代码
抽象表达式类
public interface IExpression { bool Interpret(string info); }
终结符表达式类
public class TerminalExpression : IExpression { private HashSet<string> set = new HashSet<string>(); public TerminalExpression(string[] data) { foreach (var item in data) { set.Add(item); } } public bool Interpret(string info) { if (set.Contains(info)) { return true; } return false; } }
非终结符表达式类
public class AndExpression : IExpression { private IExpression city = null; private IExpression person = null; public AndExpression(IExpression city, IExpression person) { this.city = city; this.person = person; } public bool Interpret(string info) { string[] s = info.Split("的".ToCharArray(), StringSplitOptions.RemoveEmptyEntries); return city.Interpret(s[0]) && person.Interpret(s[1]); } }
环境类
public class Context { private string[] citys = { "韶关", "广州" }; private string[] persons = { "老人", "妇女", "儿童" }; private IExpression cityPerson; public Context() { IExpression city = new TerminalExpression(citys); IExpression person = new TerminalExpression(persons); cityPerson = new AndExpression(city, person); } public void FreeRide(string info) { bool ok = cityPerson.Interpret(info); if (ok) Console.WriteLine("您是" + info + ",您本次乘车免费!"); else Console.WriteLine(info + ",您不是免费人员,本次乘车扣费2元!"); } }
客户端
class Client { static void Main(string[] args) { Context bus = new Context(); bus.FreeRide("韶关的老人"); bus.FreeRide("韶关的年轻人"); bus.FreeRide("广州的妇女"); bus.FreeRide("广州的儿童"); bus.FreeRide("山东的儿童"); Console.Read(); } }
扩展
空对象模式(Null Object)
来源
定义
一个空对象取代 NULL 对象实例的检查。Null 对象不是检查空值,而是反应一个不做任何动作的关系。这样的 Null 对象也可以在数据不可用的时候提供默认的行为。
特点
优点
缺点
应用场景
结构
主要角色
图示
实现
实例
场景
我们将创建一个定义了相关操作的 AbstractCustomer 抽象类,和扩展了 AbstractCustomer 类的具体对象类。工厂类 CustomerFactory 基于客户端传递的名字来返回 RealCustomer 或 NullCustomer 对象。 NullPatternDemo,我们的演示类使用 CustomerFactory 来演示空对象模式的用法。 
代码
抽象对象类
public abstract class AbstractCustomer { protected string name; public abstract bool IsNull(); public abstract string GetName(); }
具体对象类
//真实对象类 public class RealCustomer : AbstractCustomer { public RealCustomer(string name) { this.name = name; } public override bool IsNull() { return false; } public override string GetName() { return name; } } //空对象类 public class NullCustomer : AbstractCustomer { public override bool IsNull() { return true; } public override string GetName() { return "Not Available in Customer Database"; } }
对象工厂类
public class CustomerFactory { public static readonly string[] names = { "Rob", "Joe", "Julie" }; public static AbstractCustomer GetCustomer(string name) { foreach (var item in names) { if (item.Equals(name, StringComparison.OrdinalIgnoreCase)) { return new RealCustomer(name); } } return new NullCustomer(); } }
客户端
class Client { static void Main(string[] args) { //使用 CustomerFactory,基于客户传递的名字,来获取 RealCustomer 或 NullCustomer 对象。 AbstractCustomer customer1 = CustomerFactory.GetCustomer("Rob"); AbstractCustomer customer2 = CustomerFactory.GetCustomer("Bob"); AbstractCustomer customer3 = CustomerFactory.GetCustomer("Julie"); AbstractCustomer customer4 = CustomerFactory.GetCustomer("Laura"); Console.WriteLine("Customers:"); Console.WriteLine($"Name: {customer1.GetName()}, is Null: {customer1.IsNull()}"); Console.WriteLine($"Name: {customer2.GetName()}, is Null: {customer2.IsNull()}"); Console.WriteLine($"Name: {customer3.GetName()}, is Null: {customer3.IsNull()}"); Console.WriteLine($"Name: {customer4.GetName()}, is Null: {customer4.IsNull()}"); Console.Read(); } }
扩展
其他
J2EE 设计模式
说明
关注对象的表示层。
分类
MVC 模式(MVC)
来源
定义
MVC 模式即 Model-View-Controller(模型-视图-控制器) 模式。
特点
优点
缺点
应用场景
结构
主要角色
Model(模型)
模型代表一个存取数据的对象或 JAVA POJO。它也可以带有逻辑,在数据变化时更新控制器。
View(视图)
视图代表模型包含的数据的可视化。
Controller(控制器)
控制器作用于模型和视图上。它控制数据流向模型对象,并在数据变化时更新视图。它使视图与模型分离开。
图示

实现
实例
场景
我们将创建一个作为模型的 Student 对象。StudentView 是一个把学生详细信息输出到控制台的视图类,StudentController 是负责存储数据到 Student 对象中的控制器类,并相应地更新视图 StudentView。 MVCPatternDemo,我们的演示类使用 StudentController 来演示 MVC 模式的用法。 
代码
模型
public class Student { private String rollNo; private String name; public String getRollNo() { return rollNo; } public void setRollNo(String rollNo) { this.rollNo = rollNo; } public String getName() { return name; } public void setName(String name) { this.name = name; } }
视图
public class StudentView { public void printStudentDetails(String studentName, String studentRollNo) { System.out.println("Student: "); System.out.println("Name: " + studentName); System.out.println("Roll No: " + studentRollNo); } }
控制器
public class StudentController { private Student model; private StudentView view; public StudentController(Student model, StudentView view) { this.model = model; this.view = view; } public void setStudentName(String name) { model.setName(name); } public String getStudentName(){ return model.getName(); } public void setStudentRollNo(String rollNo) { model.setRollNo(rollNo); } public String getStudentRollNo() { return model.getRollNo(); } public void updateView() { view.printStudentDetails(model.getName(), model.getRollNo()); } }
演示
public class MVCPatternDemo { public static void main(String[] args) { //从数据库获取学生记录 Student model = retrieveStudentFromDatabase(); //创建一个视图,输出学生信息 StudentView view = new StudentView(); StudentController controller = new StudentController(model, view); controller.updateView(); //更新模型数据,并输出学生信息 controller.setStudentName("John"); controller.updateView(); } private static Student retrieveStudentFromDatabase() { Student student = new Student(); student.setName("Robert"); student.setRollNo("10"); return student; } }
扩展
MVC和三层架构的关系
三层架构
概念
就是将整个应用程序划分为表现层(UI)、业务逻辑层(Service)、数据访问层(DAO/Repository)。
组成
数据访问层
与数据库进行交互的持久层,被Service调用。在Spring Data JPA中由Hibernate来实现。
业务逻辑层
是三层架构的服务层,负责业务逻辑处理,主要是调用DAO层对数据进行增加、删除、修改和查询等操作。
表现层
用于展示界面。主要对用户的请求进行接收,以及进行数据的访问。它为客户端(用户)提供应用程序的访问接口(界面)。
通过MVC进行分层。
图示

业务代表模式(Business Delegate)
来源
定义
用于对表示层和业务层解耦。它基本上是用来减少通信或对表示层代码中的业务层代码的远程查询功能。
特点
优点
缺点
应用场景
结构
主要角色
业务服务(Business Service)
业务服务接口。实现了该业务服务的实体类,提供了实际的业务实现逻辑。
查询服务(LookUp Service)
查找服务对象负责获取相关的业务实现,并提供业务对象对业务代表对象的访问。
业务代表(Business Delegate)
一个为客户端实体提供的入口类,它提供了对业务服务方法的访问。
客户端(Client)
表示层代码可以是 JSP、servlet 或 UI java 代码。
图示
实现
实例
场景
我们将创建 Client、BusinessDelegate、BusinessService、LookUpService、JMSService 和 EJBService 来表示业务代表模式中的各种实体。 BusinessDelegatePatternDemo 类使用 BusinessDelegate 和 Client 来演示业务代表模式的用法。 
代码
抽象业务服务
public interface BusinessService { public void doProcessing(); }
具体业务服务
public class EJBService implements BusinessService { @Override public void doProcessing() { System.out.println("Processing task by invoking EJB Service"); } } public class JMSService implements BusinessService { @Override public void doProcessing() { System.out.println("Processing task by invoking JMS Service"); } }
查询服务
public class BusinessLookUp { public BusinessService getBusinessService(String serviceType) { if(serviceType.equalsIgnoreCase("EJB")) { return new EJBService(); } else { return new JMSService(); } } }
业务代表
public class BusinessDelegate { private BusinessLookUp lookupService = new BusinessLookUp(); private BusinessService businessService; private String serviceType; public void setServiceType(String serviceType) { this.serviceType = serviceType; } public void doTask() { businessService = lookupService.getBusinessService(serviceType); businessService.doProcessing(); } }
客户端
public class Client { BusinessDelegate businessService; public Client(BusinessDelegate businessService) { this.businessService = businessService; } public void doTask() { businessService.doTask(); } }
演示
public class BusinessDelegatePatternDemo { public static void main(String[] args) { BusinessDelegate businessDelegate = new BusinessDelegate(); businessDelegate.setServiceType("EJB"); Client client = new Client(businessDelegate); client.doTask(); businessDelegate.setServiceType("JMS"); client.doTask(); } }
扩展
组合实体模式(Composite Entity)
来源
定义
用在 EJB 持久化机制中。一个组合实体是一个 EJB 实体 bean,代表了对象的图解。当更新一个组合实体时,内部依赖对象 beans 会自动更新,因为它们是由 EJB 实体 bean 管理的。
特点
优点
缺点
应用场景
结构
主要角色
依赖对象(Dependent Object)
是一个持续生命周期依赖于粗粒度对象的对象。
粗粒度对象(Coarse-Grained Object)
该对象包含依赖对象。它有自己的生命周期,也能管理依赖对象的生命周期。
组合实体(Composite Entity)
它是主要的实体 bean。它可以是粗粒的,或者可以包含一个粗粒度对象,用于持续生命周期。
策略(Strategies)
策略表示如何实现组合实体。
图示
实现
实例
场景
我们将创建作为组合实体的 CompositeEntity 对象。CoarseGrainedObject 是一个包含依赖对象的类。 CompositeEntityPatternDemo,我们的演示类使用 Client 类来演示组合实体模式的用法。 
代码
依赖对象
public class DependentObject1 { private String data; public void setData(String data) { this.data = data; } public String getData() { return data; } } public class DependentObject2 { private String data; public void setData(String data) { this.data = data; } public String getData() { return data; } }
粗粒度对象
public class CoarseGrainedObject { DependentObject1 do1 = new DependentObject1(); DependentObject2 do2 = new DependentObject2(); public void setData(String data1, String data2) { do1.setData(data1); do2.setData(data2); } public String[] getData() { return new String[] {do1.getData(), do2.getData()}; } }
组合实体
public class CompositeEntity { private CoarseGrainedObject cgo = new CoarseGrainedObject(); public void setData(String data1, String data2) { cgo.setData(data1, data2); } public String[] getData() { return cgo.getData(); } }
客户端
public class Client { private CompositeEntity compositeEntity = new CompositeEntity(); public void printData() { for (int i = 0; i < compositeEntity.getData().length; i++) { System.out.println("Data: " + compositeEntity.getData()[i]); } } public void setData(String data1, String data2) { compositeEntity.setData(data1, data2); } }
演示
public class CompositeEntityPatternDemo { public static void main(String[] args) { Client client = new Client(); client.setData("Test", "Data"); client.printData(); client.setData("Second Test", "Data1"); client.printData(); } }
扩展
数据访问对象模式(Data Access Object)
来源
定义
用于把低级的数据访问 API 或操作从高级的业务服务中分离出来。
特点
优点
缺点
应用场景
结构
主要角色
模型对象/数值对象(Model Object/Value Object)
该对象是简单的 POJO,包含了 get/set 方法来存储通过使用 DAO 类检索到的数据。
数据访问对象接口(Data Access Object Interface)
该接口定义了在一个模型对象上要执行的标准操作。
数据访问对象具体类(Data Access Object concrete class)
该类实现了上述的接口。该类负责从数据源获取数据,数据源可以是数据库,也可以是 xml,或者是其他的存储机制。
图示
实现
实例
场景
我们将创建一个作为模型对象或数值对象的 Student 对象。StudentDao 是数据访问对象接口。StudentDaoImpl 是实现了数据访问对象接口的实体类。 DaoPatternDemo,我们的演示类使用 StudentDao 来演示数据访问对象模式的用法。 
代码
数值对象
public class Student { private String name; private int rollNo; Student(String name, int rollNo) { this.name = name; this.rollNo = rollNo; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getRollNo() { return rollNo; } public void setRollNo(int rollNo) { this.rollNo = rollNo; } }
数据访问对象接口
import java.util.List; public interface StudentDao { public List<Student> getAllStudents(); public Student getStudent(int rollNo); public void updateStudent(Student student); public void deleteStudent(Student student); }
数据访问对象具体类
import java.util.ArrayList; import java.util.List; public class StudentDaoImpl implements StudentDao { //列表是当作一个数据库 List<Student> students; public StudentDaoImpl() { students = new ArrayList<Student>(); Student student1 = new Student("Robert", 0); Student student2 = new Student("John", 1); students.add(student1); students.add(student2); } @Override public void deleteStudent(Student student) { students.remove(student.getRollNo()); System.out.println("Student: Roll No " + student.getRollNo() + ", deleted from database"); } //从数据库中检索学生名单 @Override public List<Student> getAllStudents() { return students; } @Override public Student getStudent(int rollNo) { return students.get(rollNo); } @Override public void updateStudent(Student student) { students.get(student.getRollNo()).setName(student.getName()); System.out.println("Student: Roll No " + student.getRollNo() + ", updated in the database"); } }
演示
public class DaoPatternDemo { public static void main(String[] args) { StudentDao studentDao = new StudentDaoImpl(); //输出所有的学生信息 for (Student student : studentDao.getAllStudents()) { System.out.println("Student: [RollNo : " + student.getRollNo() + ", Name : " + student.getName() + " ]"); } //更新学生信息 Student student =studentDao.getAllStudents().get(0); student.setName("Michael"); studentDao.updateStudent(student); //获取学生信息 studentDao.getStudent(0); System.out.println("Student: [RollNo : " + student.getRollNo() + ", Name : " + student.getName() + " ]"); } }
扩展
前端控制器模式(Front Controller)
来源
定义
用来提供一个集中的请求处理机制,所有的请求都将由一个单一的处理程序处理。该处理程序可以做认证/授权/记录日志,或者跟踪请求,然后把请求传给相应的处理程序。
特点
优点
缺点
应用场景
结构
主要角色
视图(View)
视图是为请求而创建的对象。
调度器(Dispatcher)
前端控制器可能使用一个调度器对象来调度请求到相应的具体处理程序。
前端控制器(Front Controller)
处理应用程序所有类型请求的单个处理程序,应用程序可以是基于 web 的应用程序,也可以是基于桌面的应用程序。
图示
实现
实例
场景
我们将创建 FrontController、Dispatcher 分别当作前端控制器和调度器。HomeView 和 StudentView 表示各种为前端控制器接收到的请求而创建的视图。 FrontControllerPatternDemo,我们的演示类使用 FrontController 来演示前端控制器设计模式。 
代码
视图
public class HomeView { public void show() { System.out.println("Displaying Home Page"); } } public class StudentView { public void show() { System.out.println("Displaying Student Page"); } }
调度器
public class Dispatcher { private StudentView studentView; private HomeView homeView; public Dispatcher() { studentView = new StudentView(); homeView = new HomeView(); } public void dispatch(String request) { if(request.equalsIgnoreCase("STUDENT")) { studentView.show(); } else { homeView.show(); } } }
前端控制器
public class FrontController { private Dispatcher dispatcher; public FrontController() { dispatcher = new Dispatcher(); } private boolean isAuthenticUser() { System.out.println("User is authenticated successfully."); return true; } private void trackRequest(String request) { System.out.println("Page requested: " + request); } public void dispatchRequest(String request) { //记录每一个请求 trackRequest(request); //对用户进行身份验证 if(isAuthenticUser()){ dispatcher.dispatch(request); } } }
演示
public class FrontControllerPatternDemo { public static void main(String[] args) { FrontController frontController = new FrontController(); frontController.dispatchRequest("HOME"); frontController.dispatchRequest("STUDENT"); } }
扩展
拦截过滤器模式(Intercepting Filter)
来源
定义
用于对应用程序的请求或响应做一些预处理/后处理。定义过滤器,并在把请求传给实际目标应用程序之前应用在请求上。过滤器可以做认证/授权/记录日志,或者跟踪请求,然后把请求传给相应的处理程序。
特点
优点
缺点
应用场景
结构
主要角色
过滤器(Filter)
过滤器在请求处理程序执行请求之前或之后,执行某些任务。
Target
Target 对象是请求处理程序。
过滤器链(Filter Chain)
过滤器链带有多个过滤器,并在 Target 上按照定义的顺序执行这些过滤器。
过滤管理器(Filter Manager)
过滤管理器管理过滤器和过滤器链
客户端
Client 是向 Target 对象发送请求的对象。
图示
实现
实例
场景
我们将创建 FilterChain、FilterManager、Target、Client 作为表示实体的各种对象。AuthenticationFilter 和 DebugFilter 表示实体过滤器。 InterceptingFilterDemo 类使用 Client 来演示拦截过滤器设计模式。 
代码
抽象过滤器
public interface Filter { public void execute(String request); }
具体过滤器
public class AuthenticationFilter implements Filter { public void execute(String request) { System.out.println("Authenticating request: " + request); } } public class DebugFilter implements Filter { public void execute(String request) { System.out.println("request log: " + request); } }
Target
public class Target { public void execute(String request) { System.out.println("Executing request: " + request); } }
过滤器链
import java.util.ArrayList; import java.util.List; public class FilterChain { private List<Filter> filters = new ArrayList<Filter>(); private Target target; public void addFilter(Filter filter) { filters.add(filter); } public void execute(String request) { //过滤器执行 for (Filter filter : filters) { filter.execute(request); } //请求处理程序执行 target.execute(request); } public void setTarget(Target target) { this.target = target; } }
过滤管理器
public class FilterManager { FilterChain filterChain; public FilterManager(Target target) { filterChain = new FilterChain(); filterChain.setTarget(target); } public void setFilter(Filter filter) { filterChain.addFilter(filter); } public void filterRequest(String request) { filterChain.execute(request); } }
客户端
public class Client { FilterManager filterManager; public void setFilterManager(FilterManager filterManager) { this.filterManager = filterManager; } public void sendRequest(String request) { filterManager.filterRequest(request); } }
演示
public class InterceptingFilterDemo { public static void main(String[] args) { FilterManager filterManager = new FilterManager(new Target()); filterManager.setFilter(new AuthenticationFilter()); filterManager.setFilter(new DebugFilter()); Client client = new Client(); client.setFilterManager(filterManager); client.sendRequest("HOME"); } }
扩展
服务定位器模式(Service Locator)
来源
定义
用在我们想使用 JNDI 查询定位各种服务的时候。考虑到为某个服务查找 JNDI 的代价很高,服务定位器模式充分利用了缓存技术。在首次请求某个服务时,服务定位器在 JNDI 中查找服务,并缓存该服务对象。当再次请求相同的服务时,服务定位器会在它的缓存中查找,这样可以在很大程度上提高应用程序的性能。
特点
优点
缺点
应用场景
结构
主要角色
服务(Service)
实际处理请求的服务。对这种服务的引用可以在 JNDI 服务器中查找到。
Context / 初始的 Context
JNDI Context 带有对要查找的服务的引用。
缓存(Cache)
缓存存储服务的引用,以便复用它们。
服务定位器(Service Locator)
服务定位器是通过 JNDI 查找和缓存服务来获取服务的单点接触。
客户端(Client)
Client 是通过 ServiceLocator 调用服务的对象。
图示
实现
实例
场景
我们将创建 ServiceLocator、InitialContext、Cache、Service 作为表示实体的各种对象。Service1 和 Service2 表示实体服务。 ServiceLocatorPatternDemo 类在这里是作为一个客户端,将使用 ServiceLocator 来演示服务定位器设计模式。 
代码
抽象服务
public interface Service { public String getName(); public void execute(); }
具体服务
public class Service1 implements Service { @Override public String getName() { return "Service1"; } public void execute() { System.out.println("Executing Service1"); } } public class Service2 implements Service { @Override public String getName() { return "Service2"; } public void execute() { System.out.println("Executing Service2"); } }
Context
public class InitialContext { public Object lookup(String jndiName) { if(jndiName.equalsIgnoreCase("SERVICE1")) { System.out.println("Looking up and creating a new Service1 object"); return new Service1(); } else if (jndiName.equalsIgnoreCase("SERVICE2")) { System.out.println("Looking up and creating a new Service2 object"); return new Service2(); } return null; } }
缓存
import java.util.ArrayList; import java.util.List; public class Cache { private List<Service> services; public Cache() { services = new ArrayList<Service>(); } public Service getService(String serviceName) { for (Service service : services) { if(service.getName().equalsIgnoreCase(serviceName)) { System.out.println("Returning cached "+serviceName+" object"); return service; } } return null; } public void addService(Service newService) { boolean exists = false; for (Service service : services) { if(service.getName().equalsIgnoreCase(newService.getName())) { exists = true; } } if(!exists) { services.add(newService); } } }
服务定位器
public class ServiceLocator { private static Cache cache; static { cache = new Cache(); } public static Service getService(String jndiName) { Service service = cache.getService(jndiName); if(service != null) { return service; } InitialContext context = new InitialContext(); Service service1 = (Service)context.lookup(jndiName); cache.addService(service1); return service1; } }
演示
public class ServiceLocatorPatternDemo { public static void main(String[] args) { Service service = ServiceLocator.getService("Service1"); service.execute(); service = ServiceLocator.getService("Service2"); service.execute(); service = ServiceLocator.getService("Service1"); service.execute(); service = ServiceLocator.getService("Service2"); service.execute(); } }
扩展
传输对象模式(Transfer Object)
来源
定义
于从客户端向服务器一次性传递带有多个属性的数据。传输对象也被称为数值对象。传输对象是一个具有 getter/setter 方法的简单的 POJO 类,它是可序列化的,所以它可以通过网络传输。它没有任何的行为。服务器端的业务类通常从数据库读取数据,然后填充 POJO,并把它发送到客户端或按值传递它。对于客户端,传输对象是只读的。客户端可以创建自己的传输对象,并把它传递给服务器,以便一次性更新数据库中的数值。
特点
优点
缺点
应用场景
结构
主要角色
传输对象(Transfer Object)
简单的 POJO,只有设置/获取属性的方法。
业务对象(Business Object)
为传输对象填充数据的业务服务。
客户端(Client)
客户端可以发送请求或者发送传输对象到业务对象。
图示
实现
实例
场景
我们将创建一个作为业务对象的 StudentBO 和作为传输对象的 StudentVO,它们都代表了我们的实体。 TransferObjectPatternDemo 类在这里是作为一个客户端,将使用 StudentBO 和 Student 来演示传输对象设计模式。 
代码
传输对象
public class StudentVO { private String name; private int rollNo; StudentVO(String name, int rollNo) { this.name = name; this.rollNo = rollNo; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getRollNo() { return rollNo; } public void setRollNo(int rollNo) { this.rollNo = rollNo; } }
业务对象
import java.util.ArrayList; import java.util.List; public class StudentBO { //列表是当作一个数据库 List<StudentVO> students; public StudentBO() { students = new ArrayList<StudentVO>(); StudentVO student1 = new StudentVO("Robert", 0); StudentVO student2 = new StudentVO("John", 1); students.add(student1); students.add(student2); } public void deleteStudent(StudentVO student) { students.remove(student.getRollNo()); System.out.println("Student: Roll No " + student.getRollNo() + ", deleted from database"); } //从数据库中检索学生名单 public List<StudentVO> getAllStudents() { return students; } public StudentVO getStudent(int rollNo) { return students.get(rollNo); } public void updateStudent(StudentVO student) { students.get(student.getRollNo()).setName(student.getName()); System.out.println("Student: Roll No " + student.getRollNo() + ", updated in the database"); } }
演示
public class TransferObjectPatternDemo { public static void main(String[] args) { StudentBO studentBusinessObject = new StudentBO(); //输出所有的学生信息 for (StudentVO student : studentBusinessObject.getAllStudents()) { System.out.println("Student: [RollNo : " + student.getRollNo() + ", Name : " + student.getName() + " ]"); } //更新学生信息 StudentVO student =studentBusinessObject.getAllStudents().get(0); student.setName("Michael"); studentBusinessObject.updateStudent(student); //获取学生信息 studentBusinessObject.getStudent(0); System.out.println("Student: [RollNo : " + student.getRollNo() + ", Name : " + student.getName() + " ]"); } }
扩展