原创

设计模式

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://guoruibiao.blog.csdn.net/article/details/88750996

六大原则

追根溯源,六大原则都有一个同样的理念:对拓展开放,对修改封闭。 然后根据这一个理念衍生出6个原则:

单一职责原则

一个类应该只实现单一职责,不能既当爹又当妈,一定可以找到合适的分界,对实现了多个职责的类进行拆分。

里式替换原则

所有引用基类的地方必须能透明地使用其子类的对象。
按我的理解就是子类继承父类后,不应该改变父类的职责,尽量不要复写父类同名方法,基类代表了定义好的结构,通过这个规范的接口与外界交互,子类不应该随便破坏它。就像发版,不能因为新版本某些功能的添加或者优化,就导致老版本crash掉了。详细参考自:里氏替换原则

依赖倒置原则

面向接口编程,而不是面向实际的类编程,也就是实现某个功能要调用不同的底层方法的时候,尽量不要调用实际类的方法,而是调用完成那个功能的接口,具体的实现扔给下方的实现类。交互期间只是两个或者多个接口的交流。

接口隔离原则

接口中原则上来讲不存在子类用不到却不得不实现的方法,“占着茅坑不拉💩”是不被允许的,写代码也是。接口内部的方法,要有存在的价值。

迪米特(最少知道)法则

一个类对其依赖的类的了解程度越少越好,类与类之间通过暴露public方法进行沟通,想到一个不怎么得体的例子:好朋友的老婆好是好,但你知道的越少越好。

合成复用原则

优先使用组合的方式,而不是继承,来实现新功能。


分类

涉及模式从大的角度看有三类,分别是:

工厂方法模式

之前刚接触Android编程的时候经常遇到XXFactory.build(xxx)之类的代码,知道是工厂模式,而且有很多变种,但是为什么要这么设计,我当时也不是很明白,当时觉得可能就是为了封装吧,对外暴露公共接口即可。此包括三种类型:

  1. 通过参数指定创建某个类的实例

    如果文档清晰,调用方调用明确,这种方式能减少“重复”(非真重复,这里指逻辑上的重复)代码。

  2. 通过多个方法创建指定类的实例

    明确,不像第一种方法,不够精确。

  3. 通过静态方法,无需实例化工厂创建指定的类的实例

    无需实例化工厂,静态方法可以直接调用。

考虑到系统肯定会不断添加新的功能,导致对工厂类进行代码修改,一定程度上破坏了封闭原则。为了解决这一个问题,将之前的工厂从类的范畴中释放出来,做成接口形式。这样在新增功能类的时候,就不会对原有的类有修改,也达到了我们的目的。

抽象工厂模式

抽象工厂顾名思义更抽象,意思就是不局限于对某一类“产品”的生产了,这一点是区别于工厂方法的核心。相比起来:

  • 抽象工厂模式是一个“工厂”,可以生产多个类型的产品。
  • 工厂模式更像是一个产品线,生产同类型的产品。

单例模式

说到单例模式,目前我接触的PHP,在某些场景下就显得捉襟见肘,甚至没什么用处。对单个客户端的请求,如果一个类只会被用一次,这种单例模式应用的意义并不大。我认为的单例模式适合的场景为:

  • 同一次请求中需要多次实例化某一个类,而又不必要每次都实例化,这样可以减少内存占用。
  • 内存中不能被任意变更或者变更后对内存中调用方均可见的配置型对象,比如NGINX的部分配置reload后就生效。

建造者模式

  1. 优点
  • 产品的建造和表示分离,实现了解耦。
  • 隐藏了产品的建造细节,用户只需关心产品的表示,而不需要了解是如何创建产品的。
  • 体现了开闭原则,如上代码所示,如果需要再生产其他共享单车,只需要再开一条生产线即可,不影响其他生产线的作业。
  1. 缺点
  • 当建造者过多时,会产生很多类,难以维护。

感觉建造者模式需要好多类啊,建造者作为一个大管家,让手底下的搬砖的,提泥的,抹粉的(实际建造者)按照固有的顺序分别行动起来,对于调用方而言,最终收获的是一个完整的产品–房子,至于怎么去完成这种细节性的流程,就交给指挥者这个大管家去处理好了。

不同的房东会有不同的需求,这个时候还是交给指挥者大管家,让他去处理底层建造者细节上的顺序,优化就可以了。

原型模式

复制类实例,保存某个类实例的某个时刻的状态,算了,感觉这玩意儿意义不是特别大啊。有特殊需求的话可以参考原型模式

适配器模式

适配器模式关注的点是**“适配”**,生活中也有很多类似的例子,比如手机的type-c转换头,让一个圆孔耳机插口也能在type-c口的手机上工作。这个转接头就起到了适配的作用。适配器模式有好几种,分别为:

  • 类的适配:

让猪像鸟一样飞起来看做是一个接口,是一个类,是另一个类。所以让八戒继承猪基类,然后实现升仙这个接口,然后他就可以像鸟一样飞起来了。官方术语为:

当希望将一个类转换成满足另一个新接口的类时,可以使用类的适配器模式,创建一个新类,继承原有的类,实现新的接口即可。

  • 对象的适配

对象的适配就好比:男耕女织,非得实现“女耕男织”的话,就让他俩结婚好了,组成一个家庭。这样,这个家庭就可以对外宣称既可以耕,又可以织。

  • 接口的适配

接口的适配我也想到一个不怎么正式的比喻。做神仙可以永葆青春,长生不死,但不能谈情说爱;做人可以尝遍人间七情六欲,却抵不过生老病死。张三既想长生不老又想谈个恋爱娶妻生子,所以只能继承“半仙”。例子不是很正式,但是大致的意思就是这样:

一个类既想实现接口中的某几个方法,却不想实现所有的方法。那就用一个抽象类来实现所有的接口,自己去继承这个类即可。

装饰器模式

装饰器模式重点也在装饰,也就是说装饰器除了要具备 被装饰 自身所具备的功能之外,还应该有额外的特色,来达到装饰的效果。我个人觉得三国时期的曹操,就是一个很典型的装饰器。“挟天子以令诸侯”,皇帝可以发号施令,想打谁,就打谁。曹操把汉献帝笼到许昌,圈起来以天子的名义干自己的事业,既达到了号令天下的目的,又美化了自己的声誉,岂不是一箭双雕。

在面向对象编程的世界中,对于以下场景:

  • 动态地拓展一个类的功能
  • 动态地为一个类增加,动态地撤销某一个功能

都可以用装饰器模式的思想来实现,外层包一下,对外就可以被应用了。

代理模式

生活中有很多代理公司,最接近程序员的代理恐怕就是VPN。VPN作为一个中继器,将我们的某些web请求发到外面的世界,拿到返回结果后退回给我们。面向对象编程中也有这么个理念。有时候会有这样的需求,让原本承担A功能的某个类同时实现B功能,这就打破了对外修改封闭的原则,不是我们期待的。而这个时候将另一个类包裹下此类,同时自己实现B功能,这样就可以满足需求了。

wrapper这个角度看,代理模式和上面的装饰器模式有点类似,但是实际上,这二者关注的点不太一致。装饰器模式重点在于decorate,而不是extend,可以动态控制,而代理模式则是在原类的基础上进行处理(当然也可以不作处理,还是得看目标需求)。

使用代理模式,可以将功能划分的更加清晰,有助于后期维护!代理模式虽然好,但是也会坑人,了解原类的工作内容后再看看适不适合代理模式,不信的话就看看你的租房史吧。

外观模式

我眼中的外观模式是韩信,刘邦最终赢得楚汉之争,和千古战神韩信是分不开的。刘邦对韩信说,我要出关,然后韩信部署兵力,并提出明修栈道,暗度陈仓一计,至于怎么办成这件事,在韩信的部署下,樊哙佯装修道,而主力则开始过关。

在面向对象编程中,用一个类作为Facade调度管家来和调用方交互,客户端无需知道底层细节,只需要和调度管家沟通即可,调度管家去办理调度就完事了,拿到结果给客户端即可。

桥接模式

桥接模式就是把事物和其具体实现分开,使他们可以各自独立的变化。将抽象化与实现化解耦,使得二者可以独立变化.

摘抄自设计模式之桥接模式 的一段话,我觉得特贴切,如下:

像我们常用的JDBC桥DriverManager一样,JDBC进行连接数据库的时候,在各个数据库之间进行切换,基本不需要动太多的代码,甚至丝毫不用动,原因就是JDBC提供统一接口,每个数据库提供各自的实现,用一个叫做数据库驱动的程序来桥接就行了。

对这个bridge而言,面对不同的场景采用不同的动作,调用不同的底层实现来完成相对应的功能,这就是它的工作了。

组合模式

不太懂这块,先pass

享元模式

说到享元模式,我就想到了汤圆,想象一下,一个锅里面煮着很多个白白的汤圆,想吃就捞一个,大家共同“享”用。在JDBCPool中就用到了这种模式,系统帮我们维护一个数据库连接对象的池子,我们要调用的时候就申请一个,用完了就还回去,省去了每次调用都要new和release的开销,减少了系统负载,提搞了程序的运行效率。

策略模式

之前使用python的hashlib,发现它有16个子算法,可以看做是工厂模式的代表,这点要和策略模式区分开来。

在策略模式中,我们创建表示各种策略的对象和一个行为随着策略对象改变而改变的 context 对象。策略对象改变 context 对象的执行算法。

用context来控制使用不同的策略底层实现类对象,使用场景有:

  1. 如果在一个系统里面有许多类,它们之间的区别仅在于它们的行为,那么使用策略模式可以动态地让一个对象在许多行为中选择一种行为。 2. 一个系统需要动态地在几种算法中选择一种。
  2. 如果一个对象有很多的行为,如果不用恰当的模式,这些行为就只好使用多重的条件选择语句来实现。

需要注意的是策略不宜过多,不然不好管理,在**“新增”**场景下对context大管家的拓展也会违背“对修改封闭”的原则。

模板方法模式

模板方法模式是多态应用的最佳体现,抽象类定义一整套待用方法,被诸多子类按场景自行实现。

function main() 
    AbstructClass = ChildAbstructClass1
    AbstructClass.method() # call ChildAbstructClass1's method 
    AbstructClass = ChildAbstructClass2
    AbstructClass.method() # call ChildAbstructClass2's method 

观察者模式

类似于订阅模型,观察者自己有一套update方法,用于更新自身的某些信息;被订阅资源本身笼一堆观察者,当自身的数据发生变化时,依次告诉每一个订阅者:“你们该更新了”。感觉和Redis的发布订阅频道的底层实现有一点相似,server告诉每一个“slave”,XXX发生了改变。

# add some observers to subject's chain
subject.add(observer1).add(observer2).add(observer3)

# subject notify all observers 
func notifyall():
    for observer in subject.observers:
        observer.update()

迭代子模式

用于顺序访问集合对象的元素,不需要知道集合对象的底层表示。

想到Redis对一个list的实现,小数据量采用ziplist,大数据量采用链表数据结构,对外提供的lrange方法无需知道底层到底是用的什么数据结构,但是却同样完美的实现遍历的需求,这就是分离了底层集合对象的遍历行为。

责任链模式

提到链,总是让人情不自禁地想到链表这个数据结构,Java的struts2框架中有一套过滤器,就像是一个链,每一个请求都要沿着这条链走个遍,一个都不能放过。算是一个典型的应用吧。

chain.add(handler1).add(handler2).add(handler3)
# 举个简单的例子,当然也可以使用while配合chain来决定是否放行
func handle():
    for handler in self.handlers:
        canpass = handler.operate()
        if canpass == True:
            continue
        else:
            break

命令模式

TODO

备忘录模式

保存某个实例在某个时间点的状态信息,再在需要的时候返回重置。但是实在想不出这玩意儿还能有啥应用价值了。代码运行中的“备份-重置”,其实也还有蛮多的可选项。

状态模式

借用网上看到的不错的比喻,如下:

当对象的状态改变时,同时改变其行为,很好理解!就拿QQ来说,有几种状态,在线、隐身、忙碌等,每个状态对应不同的操作,而且你的好友也能看到你的状态,所以,状态模式就两点:1、可以通过改变状态来获得不同的行为。2、你的好友能同时看到你的变化。状态模式在日常开发中用的挺多的,尤其是做网站的时候,我们有时希望根据对象的某一属性,区别开他们的一些功能,比如说简单的权限控制等。

访问者模式

TODO

中介者模式

和上面的建造者模式有点相似,但是也有自己的特点,总的来说还是对类的维护。定义了一个中介对象来封装一系列对象之间的交互关系。中介者使各个对象之间不需要显式地相互引用,从而使耦合性降低,而且可以独立地改变它们之间的交互行为。

在引入中介者模式之前,A, B, C 之间相互引用,新增一个功能会导致每个部分都要修改;而引入中介者模式之后,用一个中介者D来维护A, B, C,不会引起相互引用的问题,这个时候新加一个功能,只需要把类E放入中介者D的怀抱,让它管理就好了。

中介者模式的引入大大降低了耦合程度。

解释器模式

TODO


文章最后发布于: 2019-03-22 21:14:59
展开阅读全文
0 个人打赏

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 数字20 设计师: CSDN官方博客

分享到微信朋友圈

×

扫一扫,手机浏览