设计模式 “续”

观察者模式

再次申明,本文学习自 程杰 兄的 “大话设计模式” 根据书中的内容和自己的体会而来。

观察者模式也叫 发布、订阅模式,在这个模式里,一个目标物件管理所有依赖它的观察者物件,在本身状态更改时发出通知,通知将被观察者接收到。

书中用例子 炒股 来形容了这一点,在炒股的同事是观察者,依赖于前台妹子。当老板来的时候向炒股的同事发出通知,但是问题是 前台妹子与炒股的同事耦合在一起,这并不符合依赖倒置原则。所以把发布者与观察者都进行抽象,它们都依赖于抽象,这样就解开了之间的关系。

解决了依赖问题,但是新的问题又来了,因为不是所有的观察者方法名都是一样的,都叫 Update可能不是很严谨,而且在使用别人的代码的时候这些就不是你能控制的了。使用委托,把方法做为参数代入发布者的通知方法则可以解决这下问题。

这种模式通常被用在实现事件处理系统,当一个对象的改变需要同时改变其它对象,而且它不知道具体有多少对象待改变时。

抽象工厂模式

从简单工厂、工厂、到抽象工厂,都是为了解决客户端或者调用类与被调用类的依赖,以防后期需求变更需要更换很多的代码。比如之前是使用sqlserver,现在变成了mysql,那么就要把使用了sqlserver的地方统统更改成mysql。依赖倒置原则让抽象不应该依赖于细节,细节应该依赖于抽象。但是这里明显就是依赖了细节。

抽象工厂解决了依赖的问题

一个抽象工厂的接口

sqlserver的工厂与mysql的工厂继承自抽象工厂接口,返回各自的实现(实现继承自接口,实际是返回接口,这样客户端就不用管你是什么实现)

这要对于调用者而已,它只需要知道IFactory就可以,但是虽然解决了依赖的问题,但是又违背了 开放、封闭原则 。因为如果有新的功能添加进来,我们除了要添加实现接口提供实现还要去修改工厂内的逻辑或者说提供一个获取实现的方法,新加一个新的功能成本是很高的。

所以这就是属于过度设计,当一个设计不能让自己感到使用愉快的时候,那就是过度设计。无论是是sqlserver还是使用mysql我都不想要关心,什么劳子工厂我也不想写这么多,我只想要一个符合我需求的实现类

使用简单工厂去改造抽象工厂

  • 去掉抽象工厂接口
  • 去除劳子工厂
  • 提供一个类返回我想要的实现的方法(这里还是返回接口,怎么精简,这里都精简不掉了,否则就失去了设计的意义)

想要正确的返回,根据是sqlserver还是mysql,还是绕不开switch case 判断 ,判断是sqlserver就返回sqlserver的实现,当然这是不好的,书中提供了一个方式是使用反射,使用变量或者写入到xml中,比如变量中存的是sqlserver

public static IUser GetUserManager()

{

// Manager.SqlServerUser

string className = “Manager.”+db+“User”;

return (IUserManager)Assembly.Load(“Manager”).CreateInstance(className);

}

其实说了这么多,最佳实践还是IOC,也就是依赖注入。当然上面也是依赖注入的一种,但是,现在各种IOC容器可以完美的帮我们解决这些问题,使用简单粗暴功能又强大。上面这么麻烦的事情  -.- 哈哈,还算了吧

IOC容器

  1. AutoFac
  2. Ninject
  3. Untity(微软亲儿子)
  4. CastleWindsor

状态模式

啊,又是一个深得我心的设计模式。假如有一个功能,但是前提是需要根据订单的状态来进行后面的操作 ,于是代码会出现下面这种情况,相信每个人都是感同身受,if else等分支语句是为开发带来了很多好处,但是往往会让代码臃肿不堪。

if(order.Stats==OrderSstats.OK){
//todo....
}else if(order.Stats==OrderStats.NoPay){
//todo....
}else if(order.Stats==OderStats.Expired){
//todo....
}else if().......

代码的坏味道就像是厨师炒菜时放了半袋盐,虽然可以吃。但是很不好吃,对于这情况已经有很了很多的解决方式,而状态模式针对这种坏味道的最佳选择之一,状态模式主要解决的是当控制一个对象的状态转换条件表达式过于复杂时的情况,把状态的判断逻辑转移到表示不同状态的一系统类当中,可以把复杂的逻辑判断简化、

定义抽象状态类 state 其中有抽象方法 handle 供子类实现

在子类状态类中对 单一 的某个状态进行判断,如果不符合则设置下一个状态,并且移交下一个状态类进行处理

维护状态类的context类

继续拿上面的例子做演示,用了状态模式后。我们需要定义一个抽象状态类

public abstract class State
{
public abstrat void Handle(Order order)
}

像OK、Nopay、Expired 的判断则是三个状态子类

public class PayOkStats:State
{
public override void Handle(Context context)
{
if(context.Order.Stats==Order.OK)
{
//todo.....
}else
{
//设置下一状态,并且移交下个状态类去处理
context.SetStats(new NoPayStats());context.Handle();
}
}
}

适配器模式

人们忘说亡羊补牢为时不晚,确实在已经发生这种事情后,把牢补上可以避免以后再发生类似的事,但是如果在建牢的时候就建筑的牢固那不就不会亡羊也不用再去补牢。

但是既然有亡羊补牢的典故,代码中也会有这种类似的情况,项目急着上线,但是后台接口又不兼容。这时就需要迅速给出一个补救方法。使用适配器模式做出兼容的接口供调用,让项目快速上线。

系统的数据和行为都正确,但接口不符时,我们应该考虑使用适配器,目的是使控制范围之外的一个原有对象与某个接口匹配,适配器模式主要应用于希望复用一些现存的类,但是接口又与复用环境要示不一致的情况

假如有一个订单接口,某个app需要用到订单中的某个字段但是原有的接口又没有提供,订单获取的逻辑又复杂且冗长,直接拷贝一个一样的方法显的很不好,使用适配器模式包装旧接口使其输出与我们需要的一致。

但是如果有时间的话,还是花时间去重构最好 ,毕竟不能每到紧急的时候靠适配器来补牢

备忘录模式

在我们做项目的时候,也许会遇到这样的情况,也就是某个对象的某细信息我们要暂时的存储起来,后面再进行恢复。就像是游戏中的存档。也许我们会创建一个新的对象,把信息存起来,也许会用几个变量存起来。如果有五六个字段需要存储的话,那么代码立即就会出来坏味道了。

面向对象中的封装要时刻的记起来,这些 备份 ,恢复的细节都存在我们的代码中,我们需要把它们封装起来。也就是可以使用设计模式中的备忘录模式

备忘录类,里面有我们需要进行存储的字段属性

对象类中需要有方法,存储信息、恢复信息方法。存储信息方法把数据给备忘录对象,恢复则是从备忘录把数据读取出来再赋值给自己

管理者类,管理备忘录类

也不是说所有的这种情况都要加上备忘录模式、模式加多了也会拖慢开发效率,比如如果对象所有的字段都需要进行备份呢?我们直接使用克隆模式就可以了,模式再好,还是要看是否适合,硬套上去,不如不套

组合模式

组合模式理会的不是很透彻,这里标个记号,以后再慢慢体会吧

书中原话 组合模式将对象组合成树形结构以表示 部分-整体 的层次结构,组合模式使得用户对单个对象和组合对象的使用具有一致性。需求中是体现部分与整体层次的结构时,以及用户可以忽略组合对象和单个对象的不同,统一地使用组合结构中所有对象时,就应该考虑使用组合模式了。

也就是二叉树的关系,可以无限的往下分节点,但是树叶是不可以再分的。在组合模式中树叶往往是功能而不是对象

比如现在的超市都是连锁的,办了一个超市的会员卡,在其它的分店中使用都是可以被使用的,有个需求在总店消费的积分在其它分店也是进行加分的,而在其它的分店中消费只可以在本省内的店中进行增加积分

对于用户来说,他的行为只是消费,而超市的功能是增加积分,所有的分店都有的功能。但是因为地域问题,总不能让用户到一个城市办一张会员卡吧,对于用户来说,我不想知道你是什么劳子总店还是分店或者是哪个省的店,我只是想买个东西而已。

抽象类、Add、Remove 方法添加节点要移除,以及节点的共有行为 比如增加积分

枝节点….

叶节点

迭代器模式

当写着 foreach 的时候不禁感叹,遍历对象如此简单。用起来如此爽快,即使我们想要实现迭代器也是很简单的一件事,只需要实现IEnumerable接口即可。

而这个迭代器模式似乎越来越被忘却,倒是如果看到了这一模式,会觉得似乎IEnumerable也是迭代器模式的一种实现,但是做的更好。是的,目前的高级语言C#、Java等都内置了迭代器

迭代器模式就是分离了集合对象的遍历行为,抽象出一个迭代器类来负责,这既可以做到不暴露集合的内部结构,又可让外部代码透明地访问集合内部的数据。

单例设计模式

单例是使用广泛且大多程序员都知道的一个设计模式,比如EF的上下文对象,我们一般都是线程内单例的。除了有助于性能,还为事务带来了方便。

单例,顾名思义也就是只有一个实例。

饿汉式单例  程序初始化或请求进入时就创建对象。不管是否使用

懒汉式单例  只有在使用的时候才进行创建对象且返回

一般来的单例设计,都是把类的构造方法修饰为private,然后提供一个静态方法去获取对象。方法里只new一次,如果有实例了则直接返回

桥接模式

不得不提一下 组合/聚合原则  尽量使用合成/聚合 尽量不要使用类继承

组合关系是表示一个强的拥有关系,比如艾斯奥特曼,那变身的一男一女就是组合,没了对方谁也无法成为奥特曼

聚合则不同,M78星云有一堆奥特曼。奥特曼在m78星云。也就是奥特曼是M78星云的一部分这是没有疑问的,但是不可以说M78星云是奥特曼的一部分

又是一个深得我心的原则 ,而桥接模式似乎是聚合原则的最佳实现

优先使用对象的合成/聚合将有助于保持每个类被封装,并被集中在单个任务上,这样类和类的继承层次会保持较小的规模,并且不太可能增长为不可控制的庞然大物。

继续拿我们的奥特曼举例子,M78星云有很多的奥特曼,我们可以有一个奥特曼父类

每个奥特曼都有奥义,比如躺着发射激光,站着发射激光,扔出飞镖等等

奥特曼父类    奥义方法

赛文奥特曼:奥特曼父类

赛文奥特曼发射激光子类

奥义(){发射激光}

赛文奥特曼扔出飞镖子类

奥义(){ 扔出飞镖 }

为什么要有子类?因为说不定作者哪天高兴又给赛文奥特曼增加了一个奥义呢。难道要修改已有的类吗?增加子类成了现有的不错的选择。但是这时候就有一个窘境,如果哪天给赛文加了三四个奥义呢?又来三四个子类,而我们的其它若干奥特曼呢。。发射激光的基本奥义应该每个都会有。都要给他们写一个子类吗

所以我们使用考虑使用桥接的模式

把奥特曼拆出来做为一部分,奥义又是一部分。 奥特曼拥有奥义这是没问题的,而奥义属于奥特曼这也是没问题的。为了奥义的复用我们把奥义也抽取出来

奥义抽象类

奥义方法()

奥特曼父类

protected 奥义抽象  成员;

设置奥义(奥义抽象 参数)

{

this.成员=参数;

}

运行奥义方法()

{

成员.奥义方法();

}

这样如果赛文需要什么奥义我们只要有一个奥义的子类并且使用赛文对象去设计奥义就可以了。而这个奥义其它的奥特曼也可以使用。这就是桥接模式带来的好处

命令模式

将求请封装为一个对象,从而使你可用不同的请求对客户进行参数化,对请求排队或记录请求日志,以及支持可撤销操作

分析上面的话,重点在 请求队列的批量执行 可撤销操作。说实话看的一知半解,,打个记号以后再研究吧……...

命令模式的经典角色

Command抽象类,用来声明执行操作的接口

ConcreteCommand类,将一个接收者对象绑定于一个动作,调用接收者相应的操作,以实现Exceute

Invoker类,要求该命令执行这个请求

Receiver类,知道如何实施与执行一个与请求相关的操作,任何类都可能做为一个接收者

设计模式 “续”

职责链模式

OA系统中常常会涉及到权限,组长、主管、总监、总经理有不同的权限 。 像员工的申请,比如病假、事假等都是逐级向上。一般权限够的话就直接处理不够的话就要提交给上级处理。

手动的去指定上级肯定会造成各种各样的问题,比如需求更换、上级调整、权限重新分配等,如果仅为分支语句判断的话无疑是灾难性的。

职责链模式就是处理这种层级处理的最佳实践,请看职责链的结构图

Handler 接口(抽象)类

private Handler nextHandler;

public void SetSuccessor(Handler handler)

{

this.nextHandler = handler;

}

public abstract void HandlerRequest(int request);

ConereteHandler : Hanldler

Handler 是职责的接口,而ConereteHandler是具体的实现,像是 组长、主管给出具体的处理,SetSuccessor 设置继任者也就是上级,当前权限不足时请示上一级的管理者去处理

中介者模式

相信大多在北漂的大部分同学对租房都有着很深的印象,租房很不容易,信息渠道窄往往都要各处奔跑去找房源,费时费力。而去找中介则大大的方便了租房的过程。

中介负责联系你与房东们,你提出自己的需求,中介者去寻找合适的租房。租房过程完成后你甚至可以不用知道房东的任何信息。

设计者模式中的中介者模式也是这样的道理

首先需要有 中介者接口,可以有丰台中介者、朝阳中介者、海淀中介者等

然后是 消费者(租房/房东)抽象类

中介者需要知道所有的租房者和房东,而对于租房者和房东 他们只需要知道中介者即可

然后通过中介 ,他们就可以进行交流,完成租房过程

设计模式 “续”

享元模式

UML类图

设计模式 “续”

每个人的电脑都有输入法功能,每敲一下键盘就会打出相应的字,敲键盘的操作是重复且大量的,但是不同的是我们每次敲入的键位是不一样的,如果每敲出一个键就创建一个对象,无疑是非常耗费性能的。在这里打字是内部状态,而不同的键位是外部状态。

通过享元模式可以解决此问题,享元模式使用共享实例,适合用于只是因重复而导致使用让人无法接受的大量内存的类。通过享元工厂来创建管理对象的实例,使用 HashMap来存储对象。

访问者模式

访问者模式可以使得作用于某对象结构中的各元素操作,它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作。

上句话中可以用更多的话去解释,作用于某对象结构中各元素的操作,首先这个结构一定需要是稳定的,只有在一个稳定的结构下才可以去使用访问者模式。

结构图

设计模式 “续”

visitor 为该对象结构中的ConcreteElement的每一个类声明一个visit操作

concretevisitor则是visitor的实现类,即对元素具体的操作

element 则是结构的基类,它的子类应当是在稳定的数量内

object structure 则是对象结构,可以进行添加删除及进行accept操作

上一篇:项目开发中的一些注意事项以及技巧总结 基于Repository模式设计项目架构—你可以参考的项目架构设计 Asp.Net Core中使用RSA加密 EF Core中的多对多映射如何实现? asp.net core下的如何给网站做安全设置 获取服务端https证书 Js异常捕获


下一篇:PHP合并数组的三种方法的分析与比较