设计模式概要
本文学习自程杰兄的大话设计模式所记心得,建议各位看官去看看这一本书。本文留楼主日后回忆时阅读使用
简单工厂模式
把对象的获取放到工厂中,可以减少依赖。对类进行使用继承。使用父类去获取,在需求变更的时候,需要改变的地方只需要加一个子类和工厂中的逻辑。不会对表现层(客户端)有任何的变更,这无疑降低了依赖关系。对后续的迭代和需求变更可以从容应对。
就像是 人(表现层) 超市(工厂) 果园(业务实现)
我们要吃的水果只然是果园中生产出来的,但是如果每次想要吃水果都要去果园采摘的话,这无疑是很麻烦的。人和果园就产生了依赖, 假如有一天想要吃进口的水果或者是其它果园的水果呢? 人就需要再去查资料去找相应的果园。
而超市引进了各种果园的水果,人可以根据自己的需要购买。
策略模式
策略模式定义了算法家庭,分别封装起来,让它们之间可以互相替换,此模式让算法的变化,不会影响到使用算法的客户。
一个操作有多种行为的时候,就需要使用条件语句去判断寻找合适的行为,而把行为独立开。就可以避免这些条件判断,而且也会利于代码的整洁性与可读性。
策略模式就是应对需求变更而生的,我们的算法本身就是一种策略,实现这个行为的策略。 而行为是随时都有可能被替换,这就是变化的点。而策略模式封装了变化。
一个操作具有多少行为,而这些行为是为了完成这个操作,只是行为与行为的实现有点不同。策略模式可以以相同的方式去调用所有的算法。
单一职责原则
无论做什么都要有原则 ,而单一职责是软件开发中开发人员应当遵守的原则之一,当一个类有超过一个动机去改变的时候,就要考虑去重构代码了。单一职责就是应该一个类的职责应该是单一的。在它的范围内处理的同一个类型的事情 。
一个类承担的职责过多,就等于把这些职责耦合在一起,一个职责的变化可能会影响到这个类完成其它职责的能力,这种耦合会导致脆弱的设计。
开放 封闭原则
开放封闭原则,这个很抽象 。我只是照搬出书中的原话去记录
对程序的改动应该是通过增加新代码进行的,而不是更新现有的代码。对原有的代码要留足扩展的空间,原则只是通过多种方式去遵守,像工厂、依赖倒置就是体现这一原则的作法
开发人员应在开始写代码之间理清可能以及可能会发生的事情 ,在可能会更改的地方使用抽象,以避免需求不断地更改
依赖倒置原则
之前写过一篇DIP、IOC、DI、IOC容器的文章,DIP就是依赖倒置原则
高层模块不应该依赖底层模块,两个都应该依赖抽象(接口)
抽象不应该依赖细节,细节应该依赖对象
书中提到了里氏代换原则,在代码中,把父类都替换成子类,程序的行为也不会有变化。这就比较严格,子类必须完全实现父类。在开发的时候使用父类接口子类对象,需求更换时更新其子类其可。由于子类型的可替换性才使得使用父类类型的模块在无需修改的情况下就可以扩展,使开放、封闭成为了可能
装饰模式
装饰模式是为已有的功能动态的添加更多的功能的一种方式。
很多次的提到,新功能不应该写到原有的类中,应该放到一个新的类中去完成。而装饰模式就很好的体现了。
主类有着核心的功能,当我们需要添加附加功能的时候,就可以用一个类继承我们的装饰类,并重写其装饰方法,可以选择在主类的核心功能前添加也可以选择在主类的核心功能后添加,最后在客户端使用我们新的装饰类即可。
最大的好处就是有效地把类的核心职责和装饰功能区分开了,而且可以去除相关类中重复的装饰(分支)逻辑
代理模式
代理模式自认为不是很容易理解 ,个人认为代理模式是用一个代理对象去包装隐藏实际被调用的对象。让调用者只使用代理类去进行操作,而看不到真实的对象。
下面有一个生活中的例子
小明喜欢小红,但是小明天生就比较胆小于是他委托小王代理自己去向小红表白。
在小红看到的追求者是小王。 然后他们,哈哈在一起了
代理模式的应用场景
远程代理:也就是为一个对象在不同的地址空间提供局部代表,这样可以隐藏一个对象存在于不同的地址空间的事实
虚拟代理:是根据需要创建开销很大的对象,通过它来存放实例化需要很长时间的真实对象
安全代理:用来控制真实对象访问时的权限。
智能指引:是指当调用真实的对象时,代理处理另外一些事
工厂模式
之前有说过简单工厂,在简单工厂中根据外部传来的参数返回匹配的实例,就如同之前的例子 人 超市 果园
人带着需求去超市找到自己想要的水果。
简单工厂模式的最大优点在于工厂类中包含了必要的逻辑判断,根据客户端的选择条件动态实例化相关的类,但是每当有一个新的需求时除了添加子类还需要在工厂类中添加一条逻辑判断,这违背了开放、封闭原则
工厂方式模式,定义一个用于创建对象的接口,让子类工厂决定实例化哪一个类。工厂方法使一个类的实例化延迟到其子类
这时候问题又来了,超市只是一个统称,可能会有百货超市、零食超市、水果超市….. 这时候可以定义一个超市的工厂接口,然后水果超市工厂继承自超市,并实现其方法。
这时候人可以直接去水果超市得到自己想要的。这里会发现,简单工厂中的逻辑判断没有了,更清真。更干净。但是问题也来了,增加了很多的子类,也把逻辑判断又丢给客户端了。这些问题,在后面都会一一解决
原型模式
在大话设计模式一书中用了简历去举例子,准备十份简历如果要写十份的话效率很低,而且如果在复制粘贴时有些信息写错,最后就要更改同样的多的代码。使用原型模式(Prototype)用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
微软给我们内置了接口ICloneable,里面需要实现的方法就是Clone()
所以在需要拷贝的类中实现这个接口,这样就实现了一个浅表拷贝【创建一个新对象,然后将当前对象的非静态字段复制到新对象,如果字段是值类型则进行逐位复制,引用类型只复制引用不复制引用的对象】
public object Clone() { return (object)this.MemberwiseClone(); }
这样就简单的实现了一个拷贝方法,一般在初始化信息不发生变化的情况下,克隆是最好的方法,隐藏了对象的创建细节,对性能也是大大的提高(减少了构造函数的执行时间) 不用重新初始化对象,而是动态的获得对象运行时的状态。
但是对于对象中的引用类型,就会出问题,因为浅表拷贝对于引用对象拷贝的只是引用 ,当我在任意某个实例中进行更改,那么其它的都会改变,这是我们不希望看到的。
解决方法也很简单,把要复制的对象的所有引用的对象都复制一遍,这种方式叫做深复制,深复制把引用对象的变量指定复制过的新对象,而不是原有的对象。
在被引用对象类中去实现ICloneable方法像上面的代码一样,也写一个浅表复制当前的对象,在我们的去引用这个对象的类中,做出点改变
在私有构造函数中传入被引用的对象实例,然后新对象的引用对象为原引用对象的浅表副本, 在原有的类里的Clone方法调用私有的构造函数,拿到引用对象的浅表副本后再把需要赋值的属性逐个赋值返回
private 私有构造函数(引用对象类 引用对象) { this.引用对象 = 引用对象.Clone(); }
public object Clone() { 对象 = new 对象 (引用对象); //进行属性赋值 对象.property = this.property; return 对象; }
模板方式模式
这个设计模式从名字听起来就比较易理解,定义一个模板提供规则与抽象方法。子类中重复出现的代码全部上升到父类中,子类不用重复这些代码。把不同的做成抽象方法,子类提供实现。
书中有一句话说出了模板方法的应用场景: 当我们要完成在某一细节层次一致的一个过程或一系列步骤,但是其个别步骤在更详细的层次上实现可能不同,这时候通常可以考虑使用模板方法模式。
而关于模板方法定义则是:定义一个操作中算法的顶级骨架,而将一些步骤延迟到子类中(因为是不确定的)模板方法使得子类可以不改变一个算法的结构即可重新定义该算法的某些特定步骤。
举一个生活中的例子
人每天早上起床要刷牙洗脸 中午要吃饭 而晚上要睡觉。
这是人一天的大致组成,但是 中间要做事情则是不确定的。 可能去逛街、可能去图书馆也可能写代码没。有模板方法之前code是怎样的呢? 这就会发现,同样的事情是每天都要做的。但是在子类中重复的存在。这时候我们需要模板方法模式来帮助我们优化代码。
public void 二狗子的一天() { //周一 起床,去洗脸刷牙; 吃早饭; 去逛街; 睡觉; } public void 二狗子的一天() { //周二 起床,去洗脸刷牙; 吃早饭; 图书馆; 睡觉; }
可以看到顶级骨架已经存在于父类了,子类只需要提供不同的地方即可
public abstract class ToDay { public void 二狗子的一天() { 起床,去洗脸刷牙; 吃早饭; 今天做什么(); 睡觉; } public abstract string 今天做什么(); } public class 周一 : ToDay { public override string 今天做什么() { return "写代码"; } } ToDay day = new 周一(); day.二狗子的一天();
迪米特法则
迪米特法则与依赖倒置原则听起来很类似,如果两个类不必彼此直接通信,那么这两个类就不应当发生直接的相互作用,如果其中一个类需要调用另一个类的某一个方法的话,可以通过第三者转发这个调用。
生活中会有这样的例子,电脑在使用中会出现各种各校的情况。当出现问题我们就需要维修,假如认识一个朋友懂得修电脑 ,可以通过电话找到他来进行维修。但是如果这个朋友联系不上或者正在忙其它的事情 ,这时候就需要等待
如果这里持有一个电脑维修店的引用 ,维修店会派一个闲置的修理工来进行维修,不必有等待时间。
外观模式
个人感觉有点像软件架构中的三层架构,分出数据访问层、业务逻辑层、表现层。把原本耦合在表现层的代码分出来,在层与层之间建立一个类(外观),外观封装了一些细节,使得调用者不用考虑一些繁琐、重复的事情 。
比如电脑的开机过程
5VSB出来通过MOS管转出3VSB,给南桥(ICH),SIO.
南桥发出POWBTN#给SIO和插排,将POWBTN#拉低,在RTCRST发出,32.768K起震后,
南桥发出SLP_S3给SIO,SLp_s3通过开关MOS管将PSON#拉低,此时电源开启,发出
正负5V,正负2V,3V. 在发出ATX_POWEROK
这很复杂,而且过程繁琐。如果这些都需要手工去做。一个没有经过专门培训的人甚至不会知道怎么开机!而外观 facade 给了使用者一个按钮,按钮里把这些细节都准备好了,你要做的只是按下去。由于按钮的存在,使用者完全不需要知道后面到底发生了什么。
来看看外观模式的定义: 为子系统中的一组接口提供一个一致的界面,此模式定义了一个高层接口,这个接口使得这一子系统更加容易使用.
建造者模式
-.- 看的越来越晕了,硬着头皮看下去吧。
当抽象去依赖细节,带来的后果就是细节的缺失,为什么这么说,开发人员的粗心少写了一部分的代码,就会让 系统 坏了味道。书中的例子是中餐与西餐。 同样一份西红柿炒蛋为什么每个餐馆做出来的口味不一样,因为主厨的实力不一样,各地口味不一样,以及主厨的心情如何,好的厨师让你吃到难忘的西红柿炒鸡蛋,而坏的厨师会让一份菜坏了味道
为什么肯德基哪个店吃吃起来都是一个味道,不会因为地域,主厨的不同而不同,这是他们有着严格的流程,这些暂且说成做汉堡的流程
烤面包,放材料,放主料…..
上面那些说的都只是抽象,拷面包。拷什么面包,拷多久,这些是细节,每一个产品的细节都不一样,比如奥尔良鸡腿堡,主料是鸡腿,牛肉饼主料就是牛肉。但是每一个产品的细节都是相同的,在过程中把细节放进去,拷贝多少份出来 ,都是一样的味道
所以这就是洋快餐为何风靡中国的原因,高效且一致。相比较中餐就要找一个靠谱的餐厅了(我还是觉得中餐好,百花争鸣,有苦有乐像人生一样)
下面继续以洋快餐的例子来说建造者模式
有人也会说,中餐也有标准啊,网上的教程很明确,准备什么食材,放多少勺盐,不也是有着过程+细节吗? 这样看起来还真是的这样,但是什么会有这样的情况呢?反观肯德基,他们是一个标准,一个流程,一个公司。像奥尔良鸡腿堡,他们用的是同样的方法,材料,流程。分布各地的肯德基分店才能做味道一致的汉堡,这有点像什么? 对就是高内聚低耦合。
回到建造者模式本身
模式定义了一个建造者 (抽象接口) 里面有定义的是 做出一个汉堡的抽象流程
具体建造者 实现建造者接口 给出实现细节,比如奥尔良鸡腿堡的实现。
指挥者,封装了具体的建造细节,因为消费者不需要知道流程,只需要拿到汉堡。
建造者模式是在当创建复杂对象的算法应该独立于该对象的组成部分以及它们的装配方式时适用的模式,