简介
GoF是设计模式的经典名著Design Patterns: Elements of Reusable Object-Oriented Software(中译本名为《设计模式——可复用面向对象软件的基础》)的四位作者,他们分为是:Elich Gamma、Richard Helm、Ralph Johnson、以及John Vlissides。这四个人常被称为Gang of Four, 即四人组,简称GoF。
为解决某一类普遍存在的问题而提供的一种解决方案
设计模式不是万能的,只有合理利用设计模式才能写出合理的代码
划分
根据目的划分
根据模式是用来完成什么工作来划分,这种方式可分为创建型模式、结构型模式和行为型模式 3 种。
创建型模式:用于描述“怎样创建对象”,它的主要特点是“将对象的创建与使用分离”。GoF 中提供了单例、原型、工厂方法、抽象工厂、建造者等 5 种创建型模式。
结构型模式:用于描述如何将类或对象按某种布局组成更大的结构,GoF 中提供了代理、适配器、桥接、装饰、外观、享元、组合等 7 种结构型模式。
行为型模式:用于描述类或对象之间怎样相互协作共同完成单个对象都无法单独完成的任务,以及怎样分配职责。GoF 中提供了模板方法、策略、命令、职责链、状态、观察者、中介者、迭代器、访问者、备忘录、解释器等 11 种行为型模式。
根据作用范围来分
根据模式主要用于类上还是对象上,这种方式可分为类模式、对象模式
类模式:用于处理类与子类之间的关系,这些关系通过继承来建立,是静态的,在编译时刻便确定下来了。GoF中的工厂方法、(类)适配器、模板方法、解释器属于该模式。
对象模式:用于处理对象之间的关系,这些关系可以通过组合或聚合来实现,在运行时刻是可以变化的,更具动态性。GoF 中除了以上 4 种,其他的都是对象模式。
范围\目的 | 创建型模式 | 结构型模式 | 行为型模式 |
---|---|---|---|
类模式 | 工厂方法 | (类)适配器 | 模板方法、解释器 |
对象模式 | 单例 | 代理 | 策略 |
(对象)适配器 | 命令 | ||
原型 | 桥接 | 职责链 | |
装饰 | 状态 | ||
抽象工厂 | 外观 | 观察者 | |
享元 | 中介者 | ||
建造者 | 迭代器 | ||
组合 | 访问者 | ||
备忘录 |
设计模式解析
工厂方法模式
基本介绍
简单工厂模式就是创建一个工厂类,由这个类来封装实例化对象的行为,根据传入的参数类型来创建具体的产品对象,并返回产品对象的实例
优点
工厂方法模式的优势在于完全符合了开闭原则,在新增产品时不需要再改动已存在的代码,使工厂类和产品类的代码完全解耦,更利于程序的扩展
缺点
当新增产品时,需要同时新增产品类和工厂类,导致系统中的类是成对增加,增加了系统的复杂度
适用场景
主要适用于调用者不知道应该创建哪个具体的对象,只能根据传入的条件返回相应对象的场景
工厂类是不知道要创建哪个产品对象的,只能根据用户提供的条件才能知道需要哪个产品对象
简单工厂模式的好处在于将对象的创建过程和使用过程进行解耦,减少新增具体产品时修改代码的复杂度
演示代码
//提供一个创建对象实例的功能,而无需关系其具体实现,被创建实例的类型可以是接口、抽象类,也可以是具体的类.
class Program
{
static void Main(string[] args)
{
Food food1 = FoodFactory.CreatFood("土豆");
food1.Cook();
Food food2 = FoodFactory.CreatFood("鸡蛋");
food2.Cook();
Console.ReadKey();
}
}
public abstract class Food
{
public abstract void Cook();
}
public class Tomato : Food
{
public override void Cook()
{
Console.WriteLine("土豆");
}
}
public class Eggs : Food
{
public override void Cook()
{
Console.WriteLine("鸡蛋");
}
}
/// <summary>
/// 简单工厂
/// </summary>
public class FoodFactory
{
public static Food CreatFood(string type)
{
Food food = null;
if (type.Equals("土豆"))
{
food = new Tomato();
}
if (type.Equals("鸡蛋"))
{
food = new Eggs();
}
return food;
}
}
抽象工厂模式
基本介绍
抽象工厂模式是将具有一定共性的产品封装到一块,由工厂类分别为这些产品提供创建对象的方法,调用者可以根据不同的需求调用工厂类的具体方法来获得产品实例
优点
将具有一定共性的产品集合封装到一起,在实际开发中更符合具体的业务场景
- 隔离产品代码 : 在应用层隔离具体产品的代码 , 客户端无须关心产品创建的细节 ;
- 创建产品族 : 将一个系列的产品族 , 统一到一起创建 ;
缺点
降低了系统的扩展性,当新增产品时需要修改工厂类,在工厂类的基类和实现类中都需要增加对应的方法。比如说,用户也想订购VR眼镜来玩游戏。那么工厂基类中需要增加创建VR眼镜的方法,所有的工厂实现类中都需要增加对该方法的实现,系统扩展性比较差
- 扩展困难 : 规定了所有可能被创建的产品集合 , 产品族中扩展新的产品困难 , 需要修改抽象工厂的接口 ;
- 增加难度 : 增加了系统的抽象性和理解难度 ;
适用场景
- 忽略创建细节 : 客户端不关心产品实例如何被创建 , 实现等细节 ;
- 创建产品族 : 强调一系列相关的产品对象 , 一般是同一个产品族 , 一起使用创建对象需要大量重复的代码 ;
- 产品类库 : 提供一个产品类的库 , 所有的产品以同样的接口出现 , 使客户端不依赖于具体实现 ;
使用抽象工厂模式 , 可以在工厂变化时 , 不需要修改客户端使用工厂的代码
演示代码
//提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。
class Program
{
static void Main(string[] args)
{
//获取图形抽象工厂
AbstractFactory shapeFactory = FactoryProducer.getFactory(Option.SHAPE);
//获取图形抽象类
shape shape1 = shapeFactory.getShape(ShapeType.Triangle);
//图形类方法
shape1.draw();
shape shape2 = shapeFactory.getShape(ShapeType.Circle);
shape2.draw();
//获取颜色抽象工厂
AbstractFactory colorFactory = FactoryProducer.getFactory(Option.COLOR);
//获取颜色抽象类
Color color1 = colorFactory.getColor(ColorType.Red);
//颜色类方法
color1.fill();
Color color2 = colorFactory.getColor(ColorType.Green);
color2.fill();
Console.ReadKey();
}
}
public interface shape
{
void draw();
}
public class Rectangle : shape
{
public void draw()
{
Console.WriteLine("矩形实现形状的接口方法draw()。");
}
}
public class Square : shape
{
public void draw()
{
Console.WriteLine("正方形实现形状的接口方法draw()。");
}
}
public class Circle : shape
{
public void draw()
{
Console.WriteLine("圆形实现形状的接口方法draw()。");
}
}
public class Triangle : shape
{
public void draw()
{
Console.WriteLine("三角形实现形状的接口方法draw()。");
}
}
public enum ShapeType
{
Rectangle,
Square,
Circle,
Triangle
}
public enum ColorType
{
Red,
Yellow,
Green
}
public interface Color
{
void fill();
}
public class Red : Color
{
public void fill()
{
Console.WriteLine("给形状填充红色!");
}
}
public class Yellow : Color
{
public void fill()
{
Console.WriteLine("给形状填充黄色!");
}
}
public class Green : Color
{
public void fill()
{
Console.WriteLine("给形状填充绿色!");
}
}
public abstract class AbstractFactory
{
public abstract shape getShape(ShapeType s);
public abstract Color getColor(ColorType c);
}
public class ShapeFactory : AbstractFactory
{
public override Color getColor(ColorType c)
{
return null;
}
public override shape getShape(ShapeType shapetype)
{
if (shapetype == ShapeType.Circle)
{
return new Circle();
}
else if (shapetype == ShapeType.Rectangle)
{
return new Rectangle();
}
else if (shapetype == ShapeType.Triangle)
{
return new Triangle();
}
else
{
return new Square();
}
}
}
public class ColorFactory : AbstractFactory
{
public override Color getColor(ColorType colortype)
{
if (colortype == ColorType.Green)
{
return new Green();
}
else if (colortype == ColorType.Red)
{
return new Red();
}
else
{
return new Yellow();
}
}
public override shape getShape(ShapeType s)
{
return null;
}
}
public enum Option
{
SHAPE,
COLOR
}
public class FactoryProducer
{
public static AbstractFactory getFactory(Option t)
{
if (t == Option.COLOR)
{
return new ColorFactory();
}
else
{
return new ShapeFactory();
}
}
}
观察者模式
基本介绍
定义了对象之间一对多的依赖 , 令多个观察者对象同时监听某一个主题对象 , 当主题对象 发生改变时 , 所有的观察者都会收到通知并更新 ;
观察者有多个 , 被观察的主题对象只有一个
优点
抽象耦合:在观察者和被观察者之间 , 建立了一个抽象的耦合;
由于耦合是抽象的 , 可以很容易扩展观察者和被观察者;
广播通信:观察者模式支持广播通信 , 类似于消息广播 , 如果需要接收消息 , 只需要注册一下即可;
缺点
依赖过多:观察者之间细节依赖过多,会增加时间消耗和程序的复杂程度;
这里的细节依赖指的是触发机制,触发链条;如果观察者设置过多,每次触发都要花很长时间去处理通知;
循环调用:避免循环调用,观察者与被观察者之间绝对不允许循环依赖,否则会触发二者之间的循环调用,导致系统崩溃;
适用场景
如用户关注某个商品的价格,降价时进行通知,这样用户和商品产生了关联,触发机制就是商品降价
演示代码
//定义对象间的一种一对多的依赖关系,以便当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并自动更新。 ——《设计模式》GoF
class Program
{
static void Main(string[] args)
{
//我们有了三位储户,都是武林高手,也比较有钱
Depositor huangFeiHong = new BeiJingDepositor("黄飞鸿", 3000);
Depositor fangShiYu = new BeiJingDepositor("方世玉", 1300);
Depositor hongXiGuan = new BeiJingDepositor("洪熙官", 2500);
BankMessageSystem beijingBank = new BeiJingBankMessageSystem();
//这三位开始订阅银行短信业务
beijingBank.Add(huangFeiHong);
beijingBank.Add(fangShiYu);
beijingBank.Add(hongXiGuan);
//黄飞鸿取100块钱
huangFeiHong.GetMoney(100);
beijingBank.Notify();
//黄飞鸿和方世玉都取了钱
huangFeiHong.GetMoney(200);
fangShiYu.GetMoney(200);
beijingBank.Notify();
//他们三个都取了钱
huangFeiHong.GetMoney(320);
fangShiYu.GetMoney(4330);
hongXiGuan.GetMoney(332);
beijingBank.Notify();
Console.Read();
}
}
public abstract class BankMessageSystem
{
protected IList<Depositor> observers;
//构造函数初始化观察者列表实例
protected BankMessageSystem()
{
observers = new List<Depositor>();
}
//增加预约储户
public abstract void Add(Depositor depositor);
//删除预约储户
public abstract void Delete(Depositor depositor);
//通知储户
public void Notify()
{
foreach (Depositor depositor in observers)
{
if (depositor.AccountIsChanged)
{
depositor.Update(depositor.Balance, depositor.OperationDateTime);
//账户发生了变化,并且通知了,储户的账户就认为没有变化
depositor.AccountIsChanged = false;
}
}
}
}
//北京银行短信系统,是被观察者--该类型相当于具体主体角色ConcreteSubject
public sealed class BeiJingBankMessageSystem : BankMessageSystem
{
//增加预约储户
public override void Add(Depositor depositor)
{
//应该先判断该用户是否存在,存在不操作,不存在则增加到储户列表中,这里简化了
observers.Add(depositor);
}
//删除预约储户
public override void Delete(Depositor depositor)
{
//应该先判断该用户是否存在,存在则删除,不存在无操作,这里简化了
observers.Remove(depositor);
}
}
//储户的抽象接口--相当于抽象观察者角色(Observer)
public abstract class Depositor
{
//状态数据
private string _name;
private int _balance;
private int _total;
private bool _isChanged;
//初始化状态数据
protected Depositor(string name, int total)
{
this._name = name;
this._balance = total;//存款总额等于余额
this._isChanged = false;//账户未发生变化
}
//储户的名称,假设可以唯一区别的
public string Name
{
get { return _name; }
private set { this._name = value; }
}
public int Balance
{
get { return this._balance; }
}
//取钱
public void GetMoney(int num)
{
if (num <= this._balance && num > 0)
{
this._balance = this._balance - num;
this._isChanged = true;
OperationDateTime = DateTime.Now;
}
}
//账户操作时间
public DateTime OperationDateTime { get; set; }
//账户是否发生变化
public bool AccountIsChanged
{
get { return this._isChanged; }
set { this._isChanged = value; }
}
//更新储户状态
public abstract void Update(int currentBalance, DateTime dateTime);
}
//北京的具体储户--相当于具体观察者角色ConcreteObserver
public sealed class BeiJingDepositor : Depositor
{
public BeiJingDepositor(string name, int total) : base(name, total) { }
public override void Update(int currentBalance, DateTime dateTime)
{
Console.WriteLine(Name + ":账户发生了变化,变化时间是" + dateTime.ToString() + ",当前余额是" + currentBalance.ToString());
}
}
桥接模式
基本介绍
将对象的行为抽象为接口,作为抽象类的成员属性在抽象层进行组合
桥接模式是比较常用的设计模式之一,在创建型模式、结构型模式和行为型模式分类中,桥接模式归属于结构型模式
桥接模式可以将一个大类或者一系列紧密相关的类拆分成 控制层 和 实现层 两个相互独立的层次结构,从而能在开发时分别使用
优点
桥接模式最大的优点在于将一个具有多重功能的系统进行解耦,拆分成相互独立的层次,从而提高系统的可扩展性
桥接模式符合开闭原则,不管新增控制层或实现层的代码都可以很容易实现,而且它们不会相互影响
也符合单一原则,控制层专注于控制层的逻辑处理,实现层专注于实现层的逻辑处理
业务系统在调用时只与控制层交互,不用关心实现层的具体逻辑
缺点
桥接模式的主要缺点是增加了系统的设计与理解难度,并且,想要从一个多重功能中识别到相互独立的层次结构也不是一件容易的事情
适用场景
桥接模式的主要特点是将一个具有多重功能的系统拆分成两个相互独立的层次结构。所以,在识别到一个类具有两个相互独立变化的维度时,可以优先考虑使用该模式
桥接模式也常用于解决由于继承造成的代码侵入问题,在不希望或者不适用于继承的场景下也可以考虑使用该模式
如果想拆分一个复杂庞大的类时,也可以考虑使用该模式,前提是这个类可以拆分成多个相互独立的层次结构
桥接模式在处理跨平台应用、 支持多种类型的数据库服务器或与多个特定种类 (例如云平台和社交网络等) 的 API 供应商协作时会特别有用
当一个类存在两个独立变化的维度,且这两个维度都需要进行扩展时。
当一个系统不希望使用继承或因为多层次继承导致系统类的个数急剧增加时。
当一个系统需要在构件的抽象化角色和具体化角色之间增加更多的灵活性时。
演示代码
//将抽象部分与实现部分分离,使它们都可以独立地变化。
class Program
{
static void Main(string[] args)
{
TankPlatform tankPlatform = new PCTankPlatform();
T50 t50 = new T50(tankPlatform);
t50.Run();
t50.Shot();
Console.ReadKey();
}
}
//细节抽象类
public abstract class Tank
{
//组合抽象类,桥接变化的平台
private TankPlatform tankPlatform;
/// <summary>
/// _tankPlatform
/// </summary>
public TankPlatform _tankPlatform
{
get
{
return tankPlatform;
}
set
{
tankPlatform = value;
}
}
public abstract void Shot();
public abstract void Run();
}
//平台抽象类
public abstract class TankPlatform
{
public abstract void DrawTank();
public abstract void DoShot();
}
public class PCTankPlatform : TankPlatform
{
public override void DoShot()
{
Console.WriteLine("pc平台坦克开火");
}
public override void DrawTank()
{
Console.WriteLine("pc平台坦克运行");
}
}
public class T50 : Tank
{
public T50(TankPlatform tankPlatform)
{
_tankPlatform = tankPlatform;
}
public override void Run()
{
_tankPlatform.DrawTank();
}
public override void Shot()
{
_tankPlatform.DoShot();
}
}
外观模式
基本介绍
外观模式也叫门面模式,在创建型模式、结构型模式和行为型模式分类中,外观模式归属于结构型模式
在我们实际的工作中,外观模式是最常用的一种设计模式之一
比如,我们把几个方法中共用的逻辑抽象成一个独立的方法,那么这个方法就可以理解成外观模式
外观模式是通过引入一个外观角色来简化调用者与各个子系统之间的交互,为复杂的子系统调用提供一个统一的入口,降低子系统与调用者的耦合度,使得扩展更加方便
外观模式实现步骤:
- 抽象各个子系统的业务逻辑
- 将抽象过的业务逻辑封装到门面类中
- 调用者使用门面类中的方法完成自己的逻辑
优点
外观模式给各个子系统提供统一的入口,调用者使用起来很简单
外观模式把各个子系统和调用者解耦,扩展性会更好。比如,想要增加一个子系统时,只需要按照外观模式的规范进行开发,调用者和外观类都不用修改
缺点
如果设计不合理,增加新的子系统时可能需要修改外观类或调用者的源代码,违背了“开闭原则”
适用场景
- 需要为一个复杂的子系统提供一系列逻辑支持的时候,可以考虑使用外观模式
中台系统需要给业务系统提供多个方法支持,可以用外观模式
- 当调用者需要调用多个子系统来完成自己的逻辑时,可以考虑使用外观模式
比如日志处理框架 SLF4J,它对调用者提供接口。logback、log4j等各种日志框架作为子系统去实现这些接口
业务系统需要记录日志时,调用SLF4J门面类即可,而不必关心具体使用的是哪一个框架
演示代码
// 为子系统中的一组接口提供一个一致的界面,Facade模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。 ——《设计模式》GoF
static void Main(string[] args)
{
SystemFacade facade = new SystemFacade();
facade.Buy();
Console.ReadKey();
}
// 身份认证子系统A
public class AuthoriationSystemA
{
public void MethodA()
{
Console.WriteLine("执行身份认证");
}
}
// 系统安全子系统B
public class SecuritySystemB
{
public void MethodB()
{
Console.WriteLine("执行系统安全检查");
}
}
// 网银安全子系统C
public class NetBankSystemC
{
public void MethodC()
{
Console.WriteLine("执行网银安全检测");
}
}
//更高层的Facade
public class SystemFacade
{
private AuthoriationSystemA auth;
private SecuritySystemB security;
private NetBankSystemC netbank;
public SystemFacade()
{
auth = new AuthoriationSystemA();
security = new SecuritySystemB();
netbank = new NetBankSystemC();
}
public void Buy()
{
auth.MethodA();//身份认证子系统
security.MethodB();//系统安全子系统
netbank.MethodC();//网银安全子系统
Console.WriteLine("我已经成功购买了!");
}
}
}
GoF的23种设计模式总览
前面说明了 GoF 的 23 种设计模式的分类,现在对各个模式的功能进行介绍。
分类及定义
名称 | 功能 | 使用频率 |
---|---|---|
单例(Singleton)模式 | 某个类只能生成一个实例,该类提供了一个全局访问点供外部获取该实例,其拓展是有限多例模式。 | ★★★★☆ |
原型(Prototype)模式 | 将一个对象作为原型,通过对其进行复制而克隆出多个和原型类似的新实例。 | ★★★☆☆ |
工厂方法(Factory Method)模式 | 定义一个用于创建产品的接口,由子类决定生产什么产品。 | ★★★★★ |
抽象工厂(AbstractFactory)模式 | 提供一个创建产品族的接口,其每个子类可以生产一系列相关的产品。 | ★★★★★ |
建造者(Builder)模式 | 将一个复杂对象分解成多个相对简单的部分,然后根据不同需要分别创建它们,最后构建成该复杂对象。 | ★★☆☆☆ |
代理(Proxy)模式 | 为某对象提供一种代理以控制对该对象的访问。即客户端通过代理间接地访问该对象,从而限制、增强或修改该对象的一些特性。 | ★★★★☆ |
适配器(Adapter)模式 | 将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类能一起工作。 | ★★★★☆ |
桥接(Bridge)模式 | 将抽象与实现分离,使它们可以独立变化。它是用组合关系代替继承关系来实现,从而降低了抽象和实现这两个可变维度的耦合度。 | ★★★☆☆ |
装饰(Decorator)模式 | 动态的给对象增加一些职责,即增加其额外的功能。 | ★★★☆☆ |
外观(Facade)模式 | 为多个复杂的子系统提供一个一致的接口,使这些子系统更加容易被访问。 | ★★★★★ |
享元(Flyweight)模式 | 运用共享技术来有效地支持大量细粒度对象的复用。 | ★☆☆☆☆ |
组合(Composite)模式 | 将对象组合成树状层次结构,使用户对单个对象和组合对象具有一致的访问性。 | ★★★★☆ |
模板方法(TemplateMethod)模式 | 定义一个操作中的算法骨架,而将算法的一些步骤延迟到子类中,使得子类可以不改变该算法结构的情况下重定义该算法的某些特定步骤。 | ★★★☆☆ |
策略(Strategy)模式 | 定义了一系列算法,并将每个算法封装起来,使它们可以相互替换,且算法的改变不会影响使用算法的客户。 | ★★★★☆ |
命令(Command)模式 | 将一个请求封装为一个对象,使发出请求的责任和执行请求的责任分割开。 | ★★★★☆ |
职责链(Chain of Responsibility)模式 | 把请求从链中的一个对象传到下一个对象,直到请求被响应为止。通过这种方式去除对象之间的耦合。 | ★★★☆☆ |
状态(State)模式 | 允许一个对象在其内部状态发生改变时改变其行为能力。 | ★★★☆☆ |
观察者(Observer)模式 | 多个对象间存在一对多关系,当一个对象发生改变时,把这种改变通知给其他多个对象,从而影响其他对象的行为。 | ★★★★★ |
中介者(Mediator)模式 | 定义一个中介对象来简化原有对象之间的交互关系,降低系统中对象间的耦合度,使原有对象之间不必相互了解。 | ★★☆☆☆ |
迭代器(Iterator)模式 | 提供一种方法来顺序访问聚合对象中的一系列数据,而不暴露聚合对象的内部表示。 | ★★★★★ |
访问者(Visitor)模式 | 在不改变集合元素的前提下,为一个集合中的每个元素提供多种访问方式,即每个元素有多个访问者对象访问。 | ★☆☆☆☆ |
备忘录(Memento)模式 | 在不破坏封装性的前提下,获取并保存一个对象的内部状态,以便以后恢复它。 | ★★☆☆☆ |
解释器(Interpreter)模式 | 提供如何定义语言的文法,以及对语言句子的解释方法,即解释器。 | ★☆☆☆☆ |
使用频率排序
创建型模式(5)
工厂方法模式:★★★★★
抽象工厂模式:★★★★★
单例模式:★★★★☆
简单工厂模式:★★★★☆ // 不属于 gof-23
原型模式:★★★☆☆
生成器模式:★★☆☆☆
结构型模式(7)
外观模式:★★★★★
代理模式:★★★★☆
组合模式:★★★★☆
适配器模式:★★★★☆
桥接模式:★★★☆☆
装饰模式:★★★☆☆
享元模式:★☆☆☆☆
行为型模式(11)
迭代器模式:★★★★★
观察者模式:★★★★★
命令模式:★★★★☆
策略模式:★★★★☆
模板方法模式:★★★☆☆
责任链模式:★★★☆☆
状态模式:★★★☆☆
备忘录模式:★★☆☆☆
中介者模式:★★☆☆☆
解释器模式:★☆☆☆☆
访问者模式:★☆☆☆☆
总结图片
[图片上传失败...(image-d9aee1-1655796975559)]
引用
https://www.cnblogs.com/chenpi/category/780173.html
https://blog.csdn.net/as576228266?type=blog
https://www.yuque.com/kshare/2020/353292e1-daf5-496a-9f1f-609d2a46f54d
https://blog.csdn.net/guyuweilove/article/details/117507343
https://blog.csdn.net/Wenhao_China/article/details/124019926