什么是设计模式?设计模式有什么作用?
刚开始学Csharp的时候,听说设计模式很有用,就硬着头皮去读,发现读完了,一无所获。后来对面向对象语言稍微了解了一点,再去读,还是模模糊糊,只能说按着他那样写,也不知道具体为了什么?后来一直在抄别人的代码,也想不明白为什么要这么写。唯一能记起来的词就是“解耦”,因为要解耦,所以我们才去这么做。
那么什么是解耦呢?
百科上是这样定义的,耦合是指两个或两个以上的体系或两种运动形式间通过相互作用而彼此影响以至联合起来的现象。 解耦就是用数学方法将两种运动分离开来处理问题,常用解耦方法就是忽略或简化对所研究问题影响较小的一种运动,只分析主要的运动。
听起来很抽象,而且跟我们程序的解耦似乎不一样,但是有一个观念是相似的,就是将两者分离开来,改变其中的一个,不会影响另一个。比如在做一个程序的时候,我们经常会创建多个类,并且由于功能需求的变化而改变程序,高度耦合就是因为改变了这个类,我们不得不去改变另一个类,而解耦就是当我们要改变的时候,就算改变了A藕,也不用去改变B藕了,这样就减少了许多不必要的工作。
于是,前辈们为了在设计程序的时候就预见性的做好良好的规划,以达到解耦的目的,提出了怎么解耦的方法——设计模式。这里我选择使用了“意图”——解决什么问题,“概括”——用自己的方式概括干了什么,“示例”——写一个最简洁的表达设计模式思想的示例,“优缺点”——何时使用和使用承担的风险,“附加”——这里写上更为实用的例子和暂时能想到的设计。
(其实我现在还是一知半解,也经常看到有人批评,“现在什么阿猫阿狗也敢发分享博客了,博客的门槛真低”,如果有人抱着学习权威知识的心态,去看待我的分享,我想,到这里您就可以停下来了,因为我什么都不是。我意识到,所有的认知,在初期(也许在任何时刻)都是极其片面的,但是我不能因此而放弃接受片面的知识,并且责怪教会我片面知识的人,因为在我学到片面知识之前,我仍一无所有,只有当我不断的了解,不停的自我挑战,再实践,不停的问自己,这样真的对吗?我才能找到认识问题的一个新的角度,避免走入习惯的漩涡,并且能更新一个几经思考抽象的模型,我觉得这是认识的必经之路。愿你能在分享中找到起码一点点有意思的地方,或者能直言觉得我哪里写错了,能够有思考和共鸣,这便是我分享的目的。)
我们首先来了解七个原则的设计目的——高内聚,低耦合。
这句话的从两个角度理解:
就一个类而言,应该只有一个引起他变化的原因。
专注单一功能。
软件实体(类,模块,函数等等)应该可以扩展,但是不可以修改。
也就是对拓展开放,对修改关闭。
A.高层模块不应该依赖底层模块。两个都应该依赖抽象。
B.抽象不应该依赖细节,细节应该依赖抽象。
依赖抽象不依赖具体实现。
如果两个类不必彼此直接通信,那么这两个类不应该直接的相互作用,如果其中一个类需要调用另一个类的某一个方法的话,可以通过第三者转发这个调用。
对象中的成员尽量少拥有别的类的成员。
任何父类出现的地方,子类可以替代。
比如用父类容器装载子类对象,因为子类对象包含了父类所有的内容。
不应该强迫别人依赖他们不需要使用的方法。
一个接口应该尽量只提供一个对外的功能,让别人去选择需要实现什么样的行为,而不是把所有的行为都封装到一个接口中。就是说一个接口对应一个功能。
尽量使用合成/聚合的方式,而非继承。
聚合表现的是一种弱的拥有关系,体现的是A可以拥有B,比如公司可以拥有我,但是我不是公司的一部分,公司没了我也不会垮,我们的生命周期是不一致的。
合成是一种强的拥有关系,体现的是整体和部分的关系,比如我和我的身体,(正常情况下)不可分割,是同生共死的,在程序中,我没了,我的身体也会消失。
怎么使用聚合的原则呢?
也就是可以通过调用一个类,来达到聚合的目的。比如用更抽象的手机品牌,去安装一个更具体的手机功能类。
命令模式是游戏中最常用的设计模式。
之前我们的方式是,请求者发出请求,实现者立马实现。这样高度耦合,导致如果想要发出请求后,延迟执行,排队执行或者撤销,重做等等,无论想干什么,都没法执行。这就是行为请求者和行为实现者紧耦合导致的问题。
这样,我们就提出了命令模式:
将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化:对请求排队或记录日志,以及支持可撤销的操作。
也就是在请求者和接受者之间加上一个Invoker作为存储命令的容器。
这里命令模式的示例如下:
设计上好在加入了一个Invoker,可以实现存储的功能,但实现上,关键点在于,在命令模式中将接受者和命令绑定,Invoker调用的时候只需要调用命令,自然就能让接受者执行了。这迪米特法则。
//接受者类
//接受者是最简单的,他只要做一个具体的事情就行了。
class Receiver
{
public void Action()
{
Console.WriteLine("具体行为");
}
}
//命令类
//也就是请求者,他的职责是发出命令,即绑定一个接受者者,让接受者执行。
abstract class Command
{
protected Receiver receiver;
public Command(Receiver receiver)
{
this.receiver = receiver;
}
abstract public void Execute();
}
class ConcreteCommand : Command
{
public ConcreteCommand(Receiver receiver):base(receiver){}
public override void Execute()
{
receiver.Action();
}
}
//用作客户端沟通的唤醒类
//设置一个命令,并执行这个命令
class Invoker
{
private Command command;
public void SetCommand(Command command)//这里的行为和职责链模式是很像的。
{
this.command = command
}
public void ExecuteCommand()
{
command.Execute();//这样不需要知道谁是接受者,只需要执行命令
}
}
class CommandMain
{
//客户端——具体的流程
void Main()
{
Receiver r = new Receiver();
//在命令中,将接受者和命令绑带
Command c = new ConcreteCommand(r);
Invoker i = new Invoker();
//存储命令c
i.SetCommand(c);
//执行命令
i.ExecuteCommand();//调用后:命令执行——>绑定的接受者执行。
Console.Read();
}
}
这里为了更方便理解,将游戏编程模式书中的案例改写成Csharp:
//这儿相当于上面接受者执行的Action函数
public interface IJump
{
abstract void Jump();
}
public interface IMoveDown
{
abstract void MoveDown();
}
//这里相当于接收者
public abstract class GameActor:IJump,IMoveDown
{
public virtual void Jump()
{
Debug.Log("跳");
}
public virtual void MoveDown()
{
Debug.Log("下");
}
}
public class Player :GameActor
{
public override void Jump()
{
Debug.Log("我跳!");
}
public override void MoveDown()
{
Debug.Log("我向下移动!");
}
}
public abstract class Command
{
protected GameActor _actor;
public Command(GameActor actor)
{
_actor = actor;
}
abstract public void Execute();
}
public class JumpCommand : Command
{
public JumpCommand(GameActor actor) : base(actor)
{
}
public override void Execute()
{
_actor.Jump();
}
}
//这里就是相当于Invoker,存放命令的功能不能掉了。
public class InputHandle
{
//将命令和按键绑定——这里可以做在界面上,动态更改
public Command btnSpace;
public Command btnS;
//创造命令的工厂
public Command HanldeInput()
{
if (Input.GetKeyDown(KeyCode.Space))
{
return btnSpace;
}
else if (Input.GetKeyDown(KeyCode.S))
{
return btnS;
}
else
{
//这里最好不要返回null,可以弄一个NullCommand里面execute是null
return null;
}
}
}
//测试类
public class CPTest : MonoBehaviour
{
private InputHandle _inputHandle;
private Player _player;
// Start is called before the first frame update
void Start()
{
_inputHandle = new InputHandle();
_player = new Player();
_inputHandle.btnSpace = new JumpCommand(_player);
}
// Update is called once per frame
void Update()
{
_inputHandle.HanldeInput()?.Execute();
}
}
如果要撤销怎么做呢?就在Command里加上Undo函数。多次撤销就是弄一个列表记录,并设置一个标识记录当前命令,撤销就是undo当前命令,标识前移,撤销后如果要重做,那么就是再execute标志后一个命令(重做是基于已经撤销),如果撤销后按了别的命令,那么就清除当前标识之后的所有命令。
运用共享技术有效地支持大量细粒度的对象。
这个思想其实和ScriptableObject很像,就是如果要创建大量的重复实例,可以把不会变的基本的属性做成一个共享的数据类型,然后要创建实例的类就去引用它。这样就能节省内存。
也就是说,享元模式的设计和对象池很像,就是在享元工厂的列表里加入要生产的享元(共享的数据类型或结构体),然后通过唯一的key去获取这个享元的引用,如果有就引用,没有就新建一个放进去,由于通过享元工厂函数输入相同key得到的对象是相同数据,这里不像对象池一样,拿完了就没有了,由于这里是数据,并且不会更改数据的内容,所以可以一直重复引用,减少内存的占用。
using System.Collections.Generic;
//外部状态类,这个类实际上跟享元模式没什么关系,
//说是为了放入一个想变化的量,但是我觉得,要加上相变化的,方式多种多样
class User
{
public string name;
public User(string name)
{
this.name = name;
}
}
//享元接口
interface IFlyweight
{
public abstract void Operation();
}
class ConcreteFlyweight : IFlyweight
{
//比如有一个共享的网格数据
public Mesh mesh;
public override void Operation(User user)
{
Console.WriteLine("用户" + user.name);
}
}
//享元制造工厂,其实这个不是必须的,享元的精髓是相同的部分共享引用。减小内存开销。
class FlyweightFactory
{
public Dictionary<string,IFlyweight> flyweightDic;
public FlyweightFactory()
{
flyweightDic = new Dictionary<string, IFlyweight>();
}
public IFlyweight GetFlyweight(string key)
{
//如果有多个,这里可以用枚举和switch去选
if(!flyweightDic.ContainsKey(key))
{
flyweightDic.Add(key,new ConcreteFlyweight());
}
return flyweightDic[key];
}
}
class FlyweightMain
{
void Main()
{
FlyweightFactory factory = new FlyweightFactory();
ConcreteFlyweight concreteFlyweight = factory.GetFlyweight("具体工厂") as ConcreteFlyweight;
}
}
观察者模式定义了一种一对多的依赖关系,让多个观察者对象监听某一个主题对象。主题对象发生变化,会通知所有的观察者对象,使他们能够自动更新自己。
观察者模式又叫**发布—订阅模式,即一个主题,依赖多个观察者,或者一份报纸发布,多个顾客订阅。**Unity中有一个event概念,直接让这个模式散布在引擎各处。
abstract class Subject
{
public List<Observer> subList;
public Subject()
{
subList = new List<Observer>();
}
public virtual void AddListener(Observer observer)
{
if(!subList.Contains(observer))
{
subList.Add(observer);
}
return null;
}
public virtual void RemoveListener(Observer observer)
{
if(subList.Contains(observer))
{
subList.Remove(observer);
}
return null;
}
public virtual void Noticefy()
{
foreach(var item in subList)
{
item.Update();
}
}
}
class ConcreteSubjectA : Subject
{
//表示A状态,
public string concreteSubjectAState;
public ConcreteSubjectA(): base()
{
}
public override void AddListener(Observer observer)
{
base.AddListener(observer);
}
public override void RemoveListener(Observer observer)
{
base.RemoveListener(observer);
}
public override void Noticefy()
{
base.Noticefy();
}
}
abstract class Observer
{
public abstract void Update();
}
class ObserverA : Observer
{
//观察者的名字
public string name;
//观察的主题
public ConcreteSubjectA subject;
public ObserverA(string name,ConcreteSubjectA subject)
{
this.name = name;
this.subject = subject;
}
public override void Update()
{
Console.WriteLine("通知已经收到。" + subject.concreteSubjectAState);
}
}
讲述的就是主题和观察者之间的关系,主要还是主题里面添加移除和通知监听者,监听者有一个收到通知就更新的方法,调用的时候只需要通知,就能通知主题里所有添加过的观察者。具体传参暂不讨论。
用原型实例指定创建对象的种类,并通过拷贝这些原型创建新的对象。
主要用于简化代码,通过完整的原型定制新的对象,而后面的模版方法是对象还有一些细节不确定,延后到具体类中写。该模式就是就是在原型中放一个Clone方法,当已经有这个原型的实例的时候,可以通过实例.Clone()
来复制一个原型,然后可以自由定制由原型出发的新对象。
abstract class Prototype
{
//这里是原型的内容,是变化的
private int id;
public Prototype(int id)
{
this.id = id;
}
//重要的是克隆的方法
public abstract Prototype Clone();
}
class ConcretePrototype : Prototype
{
public ConcretePrototype(int id):base(id)
{
}
//主要的方法
public override Prototype Clone()
{
//.Net的浅拷贝方法,值开新空间,引用的引用地址。
return (Prototype)this.MemberwiseClone();
}
}
class PrototypeMain
{
void Main()
{
//在原型中放一个Clone方法,当已经有这个原型的实例的时候,可以通过实例.Clone()来定制由原型出发的新对象。
ConcretePrototype a = new ConcretePrototype(1);
ConcretePrototype b = a.Clone() as ConcretePrototype;
}
}
其实在.Net的System Namespace中提供了ICloneable接口,其中只有Clone方法,只要实现这个接口就能完成原型模式。
我们知道MemberwiseClone()是浅拷贝方法,开空间复制值,复用引用对象,那么深拷贝如何实现呢?
深拷贝是将需要克隆的对象直接new一个,然后传入要复制的值,返回的时候直接返回这个new的。由于是new出来的,所以即便是引用的也是新的空间。也就是说,深拷贝是一定要在堆上开辟新空间,而浅拷贝是不用的。
保证一个类仅有一个实例,并提供一个访问它的全局访问点。
也就是说,用来解决,该类只应该创建一个实例,还能被外部访问。
饿汉模式实例:也就是说,这个模式对资源比较饥渴,第一次引用类的任何成员时创建实例。
sealed密封类的作用是防止继承,组织产生派生类;readonly的作用是只能在静态初始化或者类构造函数中分配变量,在之后都不能改了。
//sealed密封类的作用是防止继承,组织产生派生类
//readonly的作用是只能在静态初始化或者类构造函数中分配变量,
sealed class HungerSingleton
{
private static readonly HungerSingleton instance = new HungerSingleton();
private HungerSingleton(){}
public static HungerSingleton GetInstance()
{
return instance;
}
}
懒汉模式:挤牙膏式的,只有我要用他,GetInstance的时候,他才创建实例,所以说他懒。
class LazySingleton
{
private static LazySingleton instance;
public static LazySingleton GetInstance()
{
if(instance == null)
{
instance = new LazySingleton();
}
return instance;
}
}
一般来说,Csharp中的饿汉模式就能直接解决多线程环境不安全的模式。但是这里提一下,多线程中,双重锁定的单例模式。
lock(obj)是用来确保只有一个线程进入临界区,其他线程进入临界区被阻塞。
class DCLSingleton
{
private static DCLSingleton instance;
//创建一个对象当做线程锁
private static readonly object synRoot = new object();
private DCLSingleton(){}
public static DCLSingleton GetInstance()
{
//这里是优化,不让每次都加锁
if(instance == null)
{
//这里是防止第二个线程进入创建实例,但是加锁是消耗性能的
lock(synRoot)
{
if(instance == null)
{
instance = new DCLSingleton();
}
}
}
}
}
为什么要双锁呢?因为当创建第一个instance的时候,有两个线程同时进入了lock这里,这时候,只有一个进入lock,另一个阻塞了,此时如果没有instance是否为null的判断,那么创建完第一个实例后,阻塞的那个就会创建第二个instance,显然不合理,所以要判断两次形成双锁。
当一个对象的内在状态改变时允许改变其行为,这个对象看起来像是改变了其类。
一个状态接口,为每个状态写个类,
也就是说,状态模式的核心是Context和状态接口,通过Context处理当前的状态和请求对用当前状态的处理函数,状态的处理函数设置Context的当前状态,这样就实现了状态的改变。State里主要一个状态改变的函数。
//存放当前的状态
//和策略模式中一样,是用来存放状态,声明时候传入状态,然后使用统一的方式调用更改状态
class Context
{
public State nowState;
public Context(State state)
{
this.nowState = state;
}
//发出状态变化请求
public void Request()
{
nowState.Update(this);
}
}
abstract class State
{
public abstract void Update();
}
class StateA : State
{
//连接下一个状态
public override void Update(Context currentState)
{
currentState.nowState = new StateB();
}
}
class StateB : State
{
public override void Update(Context currentState)
{
currentState.nowState = new StateA();
}
}
我认为,所谓工厂,就是一个生产实例的地方。那么简单工厂,就是直接通过Switch去选择生产哪个实例,简单工厂模式是要解决“到底要实例化谁,将来会不会增加实例化对象。”
用计算器作为实例:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
public class SimpleFactory
{
public Operation CreateOpration(string sign)
{
switch (sign)
{
case "+":
return new OperationAdd();
case "-":
return new OperationSub();
default:
return new OperationNull();
}
}
}
public abstract class Operation
{
public double a;
public double b;
public abstract double Execute();
}
class OperationAdd : Operation
{
public override double Execute()
{
return a + b;
}
}
class OperationSub : Operation
{
public override double Execute()
{
return a - b;
}
}
class OperationNull : Operation
{
public override double Execute()
{
throw new NotImplementedException();
}
}
class Program
{
static void Main()
{
SimpleFactory factory = new SimpleFactory();
Operation add = factory.CreateOpration("+");
add.a = 1;
add.b = 1;
double result = add.Execute();
Console.WriteLine(result);
}
}
定义一个创建对象的接口,让子类决定实例化哪一个类,工厂方法使一个类的实例化延迟到其子类。
工厂方法就是让一个工厂创造对应的某一个对象,把简单工厂的内部switch判断移动到了客户端,现在加功能不再需要改switch,只用加工厂,然后在客户端改。
//工厂接口和工厂
interface IFactory
{
public abstract Operation CreatOperation();
}
class AddFactory : IFactory
{
public Operation CreatOperation()
{
return new OperationAdd();
}
}
//产品抽象类和产品
abstract class Operation
{
public double a;
public double b;
public abstract double GetResult();
}
class OperationAdd : Operation
{
public override double GetResult()
{
return a + b;
}
}
class Program
{
static void Main()
{
AddFactory addFactory = new AddFactory();
Operation add = addFactory.CreatOperation();//工厂生产产品
add.a = 3;
add.b = 3;
double c = add.GetResult();//产品运用
Console.WriteLine(c);
}
}
提供一个创建一系列相关或相互依赖对象的接口,而无需指定他们具体的类。
和工厂方法的不同在于,工厂方法的工厂类里生产的是某个特定的产品,而抽象工厂模式里面,工厂接口中会有该系列所有的抽象产品AbstractProductA和AbstractProductB(因为他们的实现不同)创建的抽象方法,然后在用具体工厂来完成不同的实现产品AbstractProductA和AbstractProductB。
举个例子,比如京东方和三星都给苹果生产手机屏幕和电池,京东方和三星就是两个具体的工厂,要生产手机屏幕和电池就是抽象产品(定义成接口),那么具体怎么生产呢?工厂只需要new就行了,具体的方案继承抽象产品的接口去各自完成。
代码:
//工厂接口
interface IFactory
{
//某品牌工厂创建的产品
IProductA ProductASpawner();
IProductB ProductBSpawner();
}
//抽象的产品。不需要知道是谁做的
interface IProductA
{
void Make();
}
interface IProductB
{
void Make();
}
//王家工厂工艺做的AB
class WangA : IProductA
{
public void Make()
{
Console.WriteLine("WangA工艺就是牛!");
}
}
class WangB : IProductB
{
public void Make()
{
Console.WriteLine("WangB工艺使用B7架构,牛牛牛!");
}
}
//王家工厂拿出来的AB
class WangFactory : IFactory
{
public IProductA ProductASpawner()
{
return new WangA();
}
public IProductB ProductBSpawner()
{
return new WangB();
}
}
//苏家工厂工艺做的AB
class SuA : IProductA
{
public void Make()
{
Console.WriteLine("我们SuA领先业界2年!");
}
}
class SuB : IProductB
{
public void Make()
{
Console.WriteLine("我们SuB已经用上了5nm工艺!高效节能!");
}
}
//苏家工厂拿出来的AB
class SuFactory : IFactory
{
public IProductA ProductASpawner()
{
return new SuA();
}
public IProductB ProductBSpawner()
{
return new SuB();
}
}
class Program
{
static void Main()
{
WangFactory wang = new WangFactory();
SuFactory su = new SuFactory();
IProductB wangB = wang.ProductBSpawner();
IProductB suB = su.ProductBSpawner();
wangB.Make();
suB.Make();
}
}
运行之后的结果:
第一点是易于交换产品系列,只需要创建一个具体工厂,就能配置具体要用什么算法去生产一个产品;
第二点是创建实例过程和客户端分离,具体的类名被具体工厂的实现分离,不会出现在客户代码中。
另外这里提到了反射,这里反射的好处就是创建实例的时候可以通过字符串来控制。而使用简单工厂必须使用switch,违背了开闭原则。所有使用简单工厂的地方,都可以考虑用反射来去掉switch或if,解除分支判断带来的耦合。
面向对象并不是类越多越好,类的划分是为了封装,但是分类的基础是抽象,具有相同属性和功能的对象的抽象集合才是类。
策略模式定义了算法家族,分别封装起来,让他们之间可以互相替换。让模式让算法的变化,不会影响到算法的用户。
//抽象策略模式
abstract class Strategy
{
public abstract void AlorithmInterface();
}
class ConcreteStrategyA : Strategy
{
public override void AlorithmInterface()
{
Console.WriteLine("具体的策略A");
}
}
class ConcreteStrategyB : Strategy
{
public override void AlorithmInterface()
{
Console.WriteLine("具体的策略B");
}
}
//这是用来包装算法的类,只需要创建时传入一个算法
//包装之后所有的算法都能用一个名称来使用。
//在状态模式中也有类似的类
class Context
{
//声明的时候传进来算法
Strategy strategy;
public Context(Strategy strategy)
{
this.strategy = strategy;
}
public void Alorithm()
{
strategy.AlorithmInterface();
}
}
class Program
{
static void Main()
{
Context context = new Context(new ConcreteStrategyA());
context.Alorithm();
}
}
基本上策略模式的精髓就在于,Context这个存储算法,传入算法,用同一个函数名调用算法上。可以和其他模式结合。
动态的给一个对象添加一些额外的职责,就增加功能来说,装饰模式比生成子类更为灵活。
当我们需要把所需的功能按正确的顺序串联起来控制的时候。我感觉装饰模式和ECS的思想很像,给一个实体不停地加上组件。
//组件接口
interface IComponent
{
public abstract void Operation();
}
//具体的一个组件,比如说是一个人
class ConcreteCompoent : IComponent
{
public void Operation()
{
Console.WriteLine("具体的组件");
}
}
//装饰类,给组件添加功能,比如给人顺序穿衣服
//存储组件和执行,这里的SetComponent
abstract class Decorator : IComponent
{
protected IComponent? component;
public void SetComponent(IComponent component)
{
this.component = component;
}
public virtual void Operation()
{
if (component != null)
component.Operation();
}
}
class ADecorator : Decorator
{
public string? AFunc;
public override void Operation()
{
base.Operation();
AFunc = "组件添加功能A";
Console.WriteLine(AFunc);
}
}
class BDecorator : Decorator
{
public string? BFunc;
public override void Operation()
{
base.Operation();
BFunc = "组件添加功能B";
Console.WriteLine(BFunc);
}
}
class Program
{
static void Main()
{
ConcreteCompoent concreteComponent = new ConcreteCompoent();
ADecorator aDecorator = new ADecorator();
BDecorator bDecorator = new BDecorator();
aDecorator.SetComponent(concreteComponent);
bDecorator.SetComponent(aDecorator);
bDecorator.Operation();
}
}
由于多态中,base的存在,这样一层层的包装之后,每一个具体的装饰都会按顺序执行。那么这个模式和策略模式有什么不同呢?策略模式是通过给不同的策略名字加一个统一的包装放在Context里;装饰模式是,不同的类型,一个本体和装饰品都继承同一个接口,完成功能,然后通过多态base.()去安排调用顺序。
也就是说,这里的Component是一个对象接口,它本质上代表了一个对象,ConcreteComponent就是一个具体的对象,然后通过Decorator这个专门添加功能的对象去给之前的对象动态的添加职责,第一个对象无需知道后面的对象,后面的对象只需要知道他要给谁添加职责。
装饰模式是为已有功能动态的添加更多功能的一种方式,当系统需要新功能的时候,装饰模式的方案是把每个需要装饰的功能放在单独的类中,并让这个类包装它所要装饰的对象,因此,客户代码就能有选择有顺序的使用装饰功能包装对象。
Component是一个本体,Decorator是一个穿衣器,一个穿衣器穿一种特定的衣服。
为其他对象提供一种代理以控制对这个对象的访问。
//公用接口
interface ISubject
{
public abstract void Request();
}
class ASubject : ISubject
{
public void Request()
{
Console.WriteLine("A真实的请求");
}
}
class AProxy : ISubject
{
ASubject? aSubject;
public void Request()
{
if (aSubject == null)
{
aSubject = new ASubject();
}
aSubject.Request();
}
}
class Program
{
static void Main()
{
AProxy aProxy = new AProxy();
aProxy.Request();
}
}
这个代理模式,似乎就像给具体的实体A单独做了一个单例模式,任何用到A的时候,只需要使用A代理就行。也就是在访问对象时引入一定程度的间接性,也就是真实对象的代表。
主要使用在远程代理,为一个对象在不同的地址空间提供局部代表,这样可以隐藏一个对象存在于不同地址空间的事实。???
虚拟代理,根据需要创建开销很大的对象,通过代理来存放实例化需要很长时间的真实对象。
安全代理,控制真实对象访问时的权限,智能指引,当调用真实对象时,代理处理另外一些事。
定义一个操作中的算法的骨架,将一些步骤延迟到子类中。模版方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
也就是将一些具体操作从模板类放到子类,模版只需要定义一些抽象的方法,子类可以填写不同的操作。
简单来说就是模版用一个函数搭骨架,其他函数做抽象,子类实现抽象方法,就能通过模版做出不同的产品。
//模版
abstract class TemplateClass
{
public abstract void PrimitiveOperation1();
public abstract void PrimitiveOperation2();
public void TemplateMethon()
{
PrimitiveOperation1();
PrimitiveOperation2();
}
}
//具体产品A
class A : TemplateClass
{
public override void PrimitiveOperation1()
{
Console.WriteLine("制作A的第一步");
}
public override void PrimitiveOperation2()
{
Console.WriteLine("制作A的第二步");
}
}
class B : TemplateClass
{
public override void PrimitiveOperation1()
{
Console.WriteLine("制作B的第一步");
}
public override void PrimitiveOperation2()
{
Console.WriteLine("制作B的第二步");
}
}
class Program
{
static void Main()
{
A a = new A();
B b = new B();
a.TemplateMethon();
b.TemplateMethon();
}
}
也就是把不变的行为搬到超类,去除子类中的重复代码,提供一个很好的代码复用的平台。
为子系统中的一组接口提供一个一致的界面,次模式定义了一个高层接口,这个接口使得这一子系统更容易使用。
也就是把所有子系统调用的功能封装到Facade里,把子系统一个个的功能,放到Facade里形成功能组,这里客户端就不需要知道系统,只用Facade里的功能组就行。
//三个系统是一样的
class SystemOne
{
public void One()
{
Console.WriteLine("我是第一个功能");
}
}
class SystemTwo
{
public void Two()
{
Console.WriteLine("我是第二个功能");
}
}
class SystemThree
{
public void Three()
{
Console.WriteLine("我是第三个功能");
}
}
class Facade
{
SystemOne one;
SystemTwo two;
SystemThree three;
public Facade()
{
one = new SystemOne();
two = new SystemTwo();
three = new SystemThree();
}
//这里是方法组
public void MethonA()
{
one.One();
three.Three();
}
public void MethonB()
{
two.Two();
three.Three();
}
}
class Program
{
static void Main()
{
Facade fa = new Facade();
fa.MethonA();
Console.WriteLine("``````");
fa.MethonB();
}
}
将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
建造者模式就是,主要是三个部分,产品,建造者和指挥者,建造者里面有建造函数,指挥者指挥建造者怎样的步骤顺序使用建造函数,最后客户端调用指挥者建造,获得建造者建造的产品。
也就是说,产品是由很多个部分按顺序组装的,建造者能够建造所有的部件,建造完就加到产品上,做完之后可以得到产品,指挥者指挥建造者什么时候去制造哪个部件。
这里就是建造者依赖产品,指挥者依赖建造者。
主要是用在产品对象内部的建造顺序是稳定的(指挥者的指挥是相同的,同时通过指挥者隐藏了部件组装的顺序),但是构建的方法可能不同(建造者建造部件的方式不同),所以如果要改变内部的部件,只需要再做一个建造者。
![image.png](https://img-blog.csdnimg.cn/img_convert/aefcee24c4ae497fd73284fb65428350.png#clientId=ued49e8fe-7d29-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=524&id=uf352be0a&margin=[object Object]&name=image.png&originHeight=858&originWidth=1186&originalType=binary&ratio=1&rotation=0&showTitle=false&size=352076&status=done&style=none&taskId=uff5491f2-cf00-40d3-9347-83d29c9c672&title=&width=725)
class Product
{
List<string> partsList;
public Product()
{
partsList = new List<string>();
}
public void Add(string part)
{
partsList.Add(part);
}
public void Show()
{
foreach (var part in partsList)
{
Console.WriteLine(part);
}
}
}
abstract class Builder
{
public abstract void BuildPartA();
public abstract void BuildPartB();
public abstract Product GetProduct();
}
class ConcreteBuilder : Builder
{
Product product = new Product();
public override void BuildPartA()
{
product.Add("制造A部分");
}
public override void BuildPartB()
{
product.Add("制造B部分");
}
public override Product GetProduct()
{
return product;
}
}
class Director
{
public void Construct(Builder builder)
{
builder.BuildPartB();
builder.BuildPartA();
}
}
class Program
{
static void Main()
{
Director d = new Director();
ConcreteBuilder b = new ConcreteBuilder();
d.Construct(b);
Product p = b.GetProduct();
p.Show();
}
}
将一个类的接口转换成客户希望的另外一个接口,Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
这个模式就是由于某个类A的行为不是按照其他的标准行为来命名的,于是我们在客户端不好调用,这个时候,就使用一个适配器Target,在适配器里面申明一个A,将他的行为包裹成标准的行为,这样客户端就能调用了。也就是适配器是一个标准格式的包装。被适应者,经过Target适配之后,变成适应者Adapter。
//这个是适配接口
interface Target
{
public void Reguest();
}
class Adapter : Target
{
Adaptee adaptee = new Adaptee();
public void Reguest()
{
adaptee.Kill();
}
}
//这个就是不和谐的那个东西,需要被适配的东西
class Adaptee
{
public void Kill()
{
Console.WriteLine("我一个杀死请求!我的名字就叫杀死");
}
}
class Program
{
static void Main()
{
Adapter t = new Adapter();
t.Reguest();
}
}
在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到原先保存的状态。
这个模式有三个部分,备忘录(相当于一个结构体数据),备忘录创建者(创建结构体数据,并初始化,展示备忘录),备忘录管理者用来设置和存放备忘录。
那么为什么不让备忘录和发起者直接交互呢?原因在于备忘录有多个,我们可以用管理者来存储所有的备忘录。为什么要用备忘录模式?因为我们只需要保存一些特定的状态,不需要存储所有的状态,那么就建立备忘录类型来存储部分数据(将细节封装),然后通过创建者创建和初始化(用来传给管理者保存),恢复,管理者存储备忘录。
class Mementor
{
public string data;
//比如有一个怪物传进来,就能把他的部分数据存到data里
public Mementor(string data)
{
this.data = data;
}
}
class Organizer
{
//为什么这里要传数据,而不把Mementor保存呢?可能是为了节省资源,而且管理者已经保存了
public string data;
//创建的时候传入创建者的状态,传给管理者保存
public Mementor CreateMementor()
{
return new Mementor(data);
}
//宽接口,可以恢复备忘录中所有数据
public void SetMementor(Mementor mementor)
{
this.data = mementor.data;
}
public void Use()
{
Console.WriteLine("恢复数据" + data);
}
}
class CareTaker
{
public Mementor mementor;
}
class Program
{
static void Main()
{
Organizer organizer = new Organizer();
organizer.data = "On";
organizer.Use();
//保存
CareTaker careTaker = new CareTaker();
careTaker.mementor = organizer.CreateMementor();
//修改
organizer.data = "Off";
organizer.Use();
//读取
organizer.SetMementor(careTaker.mementor);
organizer.Use();
}
}
将对象组合成树形结构以表示“部分—整体”的层次结构。组合模式使得用户对单个对象和组合对的使用具有一致性。
也就是说,叶子和分枝内容是一致的,但是叶子里面没有列表,而分枝里面有,只要把孩子加到分枝列表里,在打印的时候就能打印出来,添加和删除的方法都是一样的。所以组合模式是,都有添加删除的方法。以达成树结构。
下面使用的就是透明方式,显然叶子里的Add和Remove无用,如果用安全方式,那么组件里的Add和Remove就不写在接口里,但会导致客户端需要相应判断。
用在当需要体现部分和整体的层次结构,希望用户可以忽略组合对象与单个对象的不同。把对象组成树形,不管哪个都有移除添加显示的功能。
abstract class Component
{
public string name;
public Component(string name)
{
this.name = name;
}
public abstract void Add(Component component);
public abstract void Remove(Component component);
public abstract void Display(int depth);
}
class Leaf : Component
{
public Leaf(string name) : base(name)
{
}
public override void Add(Component component)
{
Console.WriteLine("加入失败!叶子没有子结点。");
}
public override void Display(int depth)
{
//创建字符
Console.WriteLine(new String('-', depth) + name);
}
public override void Remove(Component component)
{
Console.WriteLine("移除失败!叶子没有子结点。");
}
}
class Composite : Component
{
public List<Component> children = new List<Component>();
public Composite(string name) : base(name)
{
}
public override void Add(Component component)
{
children.Add(component);
}
public override void Display(int depth)
{
Console.WriteLine(new String('-', depth) + name);
foreach (var item in children)
{
item.Display(depth + 2);
}
}
public override void Remove(Component component)
{
children.Remove(component);
}
}
class Program
{
static void Main()
{
Composite root = new Composite("Root");
Leaf leafA = new Leaf("leafA");
Composite c1 = new Composite("C1");
Composite c2 = new Composite("C2");
root.Add(c1);
root.Add(leafA);
c1.Add(c2);
root.Display(0);
}
}
提供一种方法顺序访问一个聚合对象中各个元素,而又不暴露该对象的内部表示。
这个模式在C++或者C#里,基本上已经算不上是一个模式了,因为两者都提供了直接的接口,直接可以调用。但这里,我们首先来了解迭代器模式的思想,再来学会使用接口即可。
将抽象部分与他的实现部分分离,使他们都可以独立的变化。
关键是将抽象实体(一种形容上的抽象,比如某品牌的手机),和实现实体(软件实现)分离。抽象实体就是包装,他通过函数将实现包装。实现类就是具体的各种实现。
//抽象实体,包装
class Abstraction
{
Implemention implemention;
public void SetImplemention(Implemention implemention)
{
this.implemention = implemention;
}
public virtual void Excute()
{
implemention.Implement();
}
}//然后这个抽象类还能细分成更多的具体的抽象品牌
abstract class Implemention
{
public abstract void Implement();
}
class ConcreteImplemention : Implemention
{
public override void Implement()
{
Console.WriteLine("具体实现1");
}
}
class Program
{
static void Main()
{
ConcreteImplemention i = new ConcreteImplemention();
Abstraction abstraction = new Abstraction();
abstraction.SetImplemention(i);
abstraction.Excute();
}
}
使多个对象都有机会处理请求,从而避免请求发送者和接受者之间的耦合关系。将这个对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。
并不知道谁会处理,所以一层层的传递。接口里两个函数,一个设置职责链下一个,一个处理函数(如果不能处理,调用设置的继承者的处理)。然后new的时候就要设置职责链。
代码很简单,略了。
用一个中介对象来封装一系列的对象交互,中介者使各对象不需要显式的相互引用,从而使其耦合松散,而且可以独立的改变他们之间的交互。
这个和事件中心类似吗?并不,事件中心是基于观察者模式。而这个是通过中介者,来传递两个对象之间的消息,在中介者中连接两个对象的发送和通知。中介者认识这两个对象,但是这两个对象并不认识彼此。
客户端只用调用各个对象的发送信息,另一个对象就会收到通知。
abstract class Colleage
{
Mediator mediator;
public Colleage(Mediator mediator)
{
this.mediator = mediator;
}
public virtual void Send(string message)
{
mediator.Send(this, message);
}
public virtual void Noticfy(string message)
{
Console.WriteLine("" + message);
}
}
class AColl : Colleage
{
public AColl(Mediator mediator) : base(mediator)
{
}
public override void Noticfy(string message)
{
Console.WriteLine("A收到通知" + message);
}
}
class BColl : Colleage
{
public BColl(Mediator mediator) : base(mediator)
{
}
public override void Noticfy(string message)
{
Console.WriteLine("B收到通知" + message);
}
}
abstract class Mediator
{
public abstract void Send(Colleage colleage, string message);
}
class ConcreteMediator : Mediator
{
public AColl? a;
public BColl? b;
//发送函数实际上是通知逻辑,谁发给对方什么消息?
public override void Send(Colleage colleage, string message)
{
if (colleage == a)
{
b?.Noticfy(message);
}
else
{
a?.Noticfy(message);
}
}
}
class Program
{
static void Main()
{
ConcreteMediator cm = new ConcreteMediator();
AColl a = new AColl(cm);
BColl b = new BColl(cm);
cm.a = a;
cm.b = b;
a.Send("你吃了吗");
b.Send("还没");
}
}
给定一个语言,定义他的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子。
比如脚本翻译成汇编语言就需要解释器。如果一类特性类型的问题发生的频率足够高,那么就值得将这个问题的各个实例简写,构建一个解释器,解释这些句子。
也就是Context就是一些具体的内容的简写,通过解释器进行翻译之后,然后在解释器中翻译出结果,
表示一个作用于某对象结构中的各元素的操作。它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作。
也就是说 ,访问者模式适合对象结构稳定的,元素已经是稳定的了,只有访问者增加。也就是说,每个访问者只需要实现访问每个对象的时候要干嘛。这个模式和之前的抽象工厂模式很像,那个是在抽象工厂里,有所有的产品,即能生产所有的对象,这里是一个访问者访问的稳定枚举。
这里,访问者的职责是访问元素,元素的职责是接收访问者。在枚举元素类中,控制元素和访问者的关系,职责是,到底是哪个访问者要访问哪些元素?在客户端,枚举元素类调用接受访问者,就能遍历该访问者访问的所有添加了的元素。
也就是说,通过枚举元素类添加和移除元素,然后选择某个访问者,访问所有添加了的元素元素。