浅读设计模式


——最近更改时间:2022-05-26 22:49
——推荐阅读设备:手机

1.引言

    今年我大三,一顿能干三碗饭。在考研和“技术”上兜兜转转,因为考试快要到了,得赶紧学习一下,当然记录下来和大家分享,因为自己一直使用的是JavaScript,所以在面向对象里面没有Java方向的好兄弟们深。理解可能有些片面,希望大家能够理解。自我感觉设计模式可能在短时间内很难体现出价值,正如其定义一样,是四位大佬(GOF,Gang of four),总结出来的在软件开发中可以反复使用的经验,能够帮助我们提高代码的重用性、系统的可维护性等,帮助解决软件开发中的复杂问题。如果自己写的项目太少了,很难以感觉到或者很快区分出来使用了哪种设计模式。可能更多的是重复的对代码优化、学长的指点等给自己的印象更深。因此可以当做必要的了解知识,当自己有一定的项目积累了,再回过头来复习复习,反思反思。

2.重新认识一下UML

    可能你和我一样,虽然是学软件工程的,但是对于软件工程里面的常用的“符号”不是很清晰。因此,这里再来回顾一下。
    UML,统一建模语言,是一种为面向对象系统的产品进行说明、可视化和编制文档的标准语言。简单来说,就是一些图形符号类的语言,使得系统的中组成部分更加清晰,易于理解。简单介绍一下基本构建(也可以理解成图形或者符号):
在这里插入图片描述
下面简单介绍一下常用的9大图:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

3.设计模式的七大原则

(1)开闭原则:软件实体如类、模块和函数应该对扩展开放、对修改关闭,强调利用抽象构建框架,用实现扩展细节,提高系统的复用性和可维护性;
(2)依赖倒置原则:高层模块不应该依赖底层模块,二者都应该依赖于抽象;抽象不应该依赖于细节,细节应该依赖于抽象;应尽可能降低代码之间的耦合度,提高系统稳定性,其实就是为了保证代码良好的扩展性;
(3)单一职责原则:不要存在一个以上导致类变更的原因,比如一个course类同时管理“直播课”和“录播课”,应该将类拆分成直播课类和录播课类,而不是每次都根据课程的名称来判断并执行对应的方法;
(4)接口隔离原则:应该使用多个接口而不是单一接口,因为接口被继承必须要实现里面的方法,但是有些方法其实用不上,这样是为了防止不必要的实现接口里面的方法;
(5)迪米特法则:“不要和陌生人说话”,强调一个对象尽可能少被其他对象了解,这样有利于降低类之间的耦合度;
(6)里氏代换原则:子类可以实现父类的方法但不能修改父类的方法,子类可增加父类没有的方法,子类实现父类方法时应比父类更加严格或者相等;
(7)合成复用原则:尽可能使用对象组合(has-a)或者聚合(contains-a)的方法来实现代码的复用,而不是利用继承实现,降低代码的耦合度。

4.设计模式的分类

(1)创建型模式:单例模式、工厂模式(简单工厂、工厂方法、抽象工厂)、创建者模式、原型模式;
(2)结构型模式:适配器模式、桥接模式、装饰模式、组合模式、外观模式、享元模式、代理模式;
(3)行为型模式:模版方法模式、命令模式、迭代器模式、观察者模式、中介者模式、备忘录模式、解释器模式、状态模式、策略模式、职责链模式、访问者模式。

5.设计模式的具体说明

(1)简单工厂模式,包含简单工程、抽象产品、具体产品,得到的UML类图如下:
在这里插入图片描述
应用场景:产品种类相对比较少;
优点:简单工厂模式结构简单,调用方便,对于外界给定的信息,可以很方便的创建出相应的产品,工厂和产品的职责区分明确。
缺点:简单工厂模式的工厂类单一,负责所有产品的创建,但当产品基数增多时,工厂代码会非常臃肿,违背了高聚合原则。
(2)工厂方法模式,包含抽象工厂、具体工厂、抽象产品和具体产品四大部分,UML类图如下:
在这里插入图片描述
应用场景:创建对象需要大量的重复代码,客户端不依赖,产品类实例如何被创建或者实现等细节;一个类通过其子类指定创建哪个对象。
优点:工厂方法模式对于新产品的创建,只需要写一个相应的工厂类;是一种比较典型的解耦框架。高层模块只需要知道产品的抽象类,无需关心其他类,满足迪米特法则、依赖倒置原则以及里氏代换原则。
缺点:类的个数容易过多,增加了系统的抽象性和理解程度。
(3)抽象工厂模式,包含简单工程、抽象产品、具体产品,得到的UML类图如下:
在这里插入图片描述
应用场景:适用于需要生成产品族;
优点:当需要使用产品族的时候,能够保证客户端只使用一个产品族;符合开闭原则,当产品族增加,只需要实现一个新的具体工厂即可。
缺点:规定了所有可能被创建的产品集合,产品产品族扩展新的产品困难,需要修改抽象工厂的接口;增加了系统的抽象性和理解难度。需要说明的是,增加一个新的产品困难,新建一个能够生产AB的产品族容易。
(4)单例模式,含有一个特殊的单例类,通过隐藏构造方法,内部初始化一次,提供一个全局访问点。对应的UML类图如下:
在这里插入图片描述
应用场景:对于需要频繁创建的一些类,使用单例模式能够降低系统的内存压力,为了解决某些类创建实例的占用资源过多,或者耗时比较长,频繁访问数据库的对象,对于一些控制硬件级别的操作,如果存在多个实例可能会导致系统紊乱;
优点:单例模式可以保证内存中只有一个实例,减少系统中的内容开销,可以避免资源的多重占用,通过全局访问点的设置可以优化和共享内存的访问。
缺点:单例模式一般没有接口,扩展困难,违背了开闭原则;在并发测试中单例模式不利于代码调试,单例模式通常写在一个类中,如果功能设计不合理很有可能违背单一职责原则。
(5)原型模式,包含客户、抽象原型和具体原型3个角色,具体的UML类图如下:
在这里插入图片描述
应用场景:创建对象成本较大,初始化时间长,占用CPU较多,或者占用网络资源较多;创建对象需要繁琐的数据准备或者访问权限,系统中大量使用某个对象而且都需要为该对象重新赋值;
优点:原型模式基于内存二进制流的复制,在性能上比new一个对象更加优良,可以使用深拷贝的方式保存对象的状态。
缺点:每一个对象都需要配置一个clone方法,对象当前已有的类进行改造的时候需要修改代码,违背了开闭原则;实现深拷贝的时候需要编写较为复杂的代码,每一层对象都需要实现深克隆,实现起来相对复杂。
(6)建造者模式,将一个复杂对象的构建过程和它的表示分开,由产品、抽象建造者、建造者以及调用者三部分组成,具体的UML类图如下:
在这里插入图片描述
应用场景:相同的方法,不同的执行顺序,产生不同的结果初始化一个类比较复杂;
优点:封装性好,构建和表示分离,扩展性好,建造类之间的独立,在一定程度上解耦,便于控制细节,构建者能够对建造过程逐步细化,并不对其他模块产生影响。
缺点:需要多创建一个IBuilder对象,如果内部发生变化,那么建造者也要同步更改,后期维护成本较大。
区别:与工厂模式的不同在于,建造者模式创建的对象更加复杂,并且建造者模式关注的是对象的建造过程,而工厂模式创建的对象比较简单,关注的被创建的对象。
(7)代理模式,包含抽象主题角色、真实主题角色和代理主题角色三个部分,具体的UML类图如下:
在这里插入图片描述
需要说明的是代理模式包含两种:静态代理模式和动态代理模式,静态代理模式只能通过手动完成代理操作,如果代理类增加新的方法,代理类需要同步增加,违背了开闭原则,动态代理模式能够在运行时动态生成代码,取消了对扩展的限制,满足开闭原则。
应用场景:代理模式一般用于直接引用某个对象比较困难或者访问存在困难,通常是为了保护目标对象和增强目标对象;
优点:代理模式能够将代理对象和真实被调用的对象分离,一定程度上能够降低耦合性,能够增强目标对象的功能,起到保护目标对象的作用。代理对象的创建举例如下:

	final User user = new User();//创建被代理的真实对象
    User proxyUser = (User) Proxy.newProxyInstance(//调用该方法生成代理对象
            user.getClass().getClassLoader(),//传入某个真实对象的类加载器
            user.getClass().getInterfaces(),//传入该真实类实现的接口数组
            new InvocationHandler() {//实现该接口的匿名类
            	 /**
                 *该方法会在代理对象调用真实对象的方法时被调用
                 * @param proxy
                 * @param method 封装代理对象调用真实对象的Method对象
                 * @param args 封装代理对象调用的方法中的参数数组
                 * @return  返回值为
                 * @throws Throwable
                 */
                @Override
                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                    //必须用method对象调用invoke方法,并传入真实对象才能真正实现代理调用真实对象的过程
                    Object obj = method.invoke(user,args);//该方法的返回值实际上就是代理对象调用真实对象方法的返回值
                    System.out.println("调用invoke");
                    return obj;//将invoke方法(实际对象的某个被调用的方法)的返回值返回,在外部的表现为一个正常的代理现象
                }
            });
    String name= proxyUser.showInfo();
    System.out.println(name);

上面invoke方法中的args参数是method方法的参数数组,因此如果被代理对象调用的方法中有参数时,我们可以通过在invoke方法体中编写代码逻辑来实现该对某方法的功能增强。
缺点:会造成系统中对象的增加,增加代理对象会导致处理请求速度下降,增加了系统的复杂度。
(8)门面模式,定义一个高层接口,使子系统更加容易使用,对应的UML类图如下:
在这里插入图片描述
应用场景:为复杂的模块或者是子系统提供一个简洁的供外部访问的接口,需要提高子系统的独立性,由于子系统当前出现了bug不能正常使用,需要建立一个高层,对子系统进行隔离,避免干扰其他子系统的正常运作;
优点:遵从迪米特法则,简化了调用流程,不需要深入了解子系统,能够减少系统的耦合度,更好的对系统进行层次划分,有利于增强系统的安全性。
缺点:当增加子系统或者扩展子系统的行为时,可能带来未知风险,不符合开闭原则,可能会违背单一职责模式,这里所站立的角度是门面模式能够修改子系统的可访问状态,如果不能访问,那么客户端那块必然需要对用户关闭访问,也就是存在其他地方的代码的调整,另外从整体上来看,门面模式可能会存在同时管理多个子系统的类,故而存在违背单一职责模式的可能。
(9)装饰器模式,包含抽象组件、具体组件、抽象装饰器、具体装饰器四个组成部分,具体的UML类图如下:
在这里插入图片描述
应用场景:用于扩展类的功能或者添加附加职责,需要为一些平行的兄弟进行改装或者加装功能;
优点:装饰者模式是继承的有力补充,比继承更加灵活,可以在不改变原有对象的条件下扩展功能,即插即用不同的装饰器组合可以实现不同的功能,完全遵守开闭原则。
缺点:会出现更多的类,增加了代码的复杂性,动态装饰在多层装饰的时候会更加复杂。
(10)享元模式,是对象池的一种实现,可以避免不停地创建和销毁对象,消耗性能,其宗旨是共享细粒度对象,将多个对象的访问集中起来,不必为每一个对象都创建一个单独的对象,以此降低内存消耗。包含享元工厂、抽象享元角色、具体享元角色三大部分,具体的UML类图如下:
在这里插入图片描述
应用场景:比较典型的场景是各中介机构的房源共享、全国社保联网等系统多出需要同一组信息时,需要将这些信息封装到一个对象中,然后对该对象进行缓存,将对象提供给需要使用的地方,避免多次创建降低系统的内存开销;
优点:减少了对象的创建,降低了内存中对象的数量,降低了系统的内存消耗,有助于提高系统效率,减少了内存之外的其他资源的占用。
缺点:使系统的程序逻辑复杂化,可能会导致线程不安全,多线程并发执行某个代码时,产生了逻辑上的错误,结果和预期值不相同。
(11)组合模式,将单个对象和组合对象用相同的接口进行表示,使得客户对单个对象和组合对象的使用具有一致性。对应的UML类图如下:
在这里插入图片描述
应用场景:当子系统与其内各个对象层次呈树状结构时,可以使用组合模式让系统内各个对象层次的行为操作具有一致性;
优点:能够清楚地定义各层次的复杂对象,让客户端忽略层次之间的差异,简化客户端代码,符合开闭原则;
缺点:使系统的设计变得更加抽象,限制类型会比较复杂,例如某个目录中只能包含文本文件,使用组合模式时,不能依赖类型系统施加约束,它们都来自于节点的抽象层;在这种情况下 , 必须通过在运行时进行类型检查,这样就变得比较复杂。
(12)适配器模式,将类的接口变成客户端所期望的另一种接口,使得原本因接口不匹配的类能够一起工作。包含目标角色、源角色和适配器三种角色,对应的UML类图如下:
在这里插入图片描述
应用场景:适配器模式是在软件维护阶段使用的设计模式,有一种“亡羊补牢”的意思,用于解决已经存在的类的方法与需求不一致的情况;
优点:在不改变现有类复用的情况下,能够提高类的透明性和复用,适配器和原角色类解耦能够提高程序的扩展性,很多业务场景符合开闭原则;
缺点:适配器的使用往往需要全面考虑系统业务场景,可能会增加程序的复杂性,适配器可能导致代码更加凌乱,降低可读性。
(13)桥接模式,将抽象部分和实现部分分离,使他们可以独立变化,包含抽象、修正抽象、实现、具体实现四大类,详细的UML类图如下:
在这里插入图片描述
给出一个典型的例子(DriverManager就是里面的桥):
在这里插入图片描述
应用场景:在抽象和具体实现之间需要增加更多的灵活性场景,一个类存在多个维度的变化并且都需要在各自的维度上进行扩展,但是不想使用多层继承来解决这个问题;
优点:符合开闭原则和合成复用原则,扩展性好,有效的分离了抽象部分和具体的实现部分;
缺点:增加了系统的理解和设计难度,需要开发者能够正确的识别这两个(或者多个)不同的“维度”。
(14)委派模式,委派模式的基本作用是负责任务的调用和分配,相比于代理模式,委派模式更加注重结果,由抽象任务角色、委派者角色和具体任务角色三部分组成,具体的UML类图如下:
在这里插入图片描述
应用场景:老板给项目经理下发任务,项目经理将任务下发给程序员,程序员写好了开会将成果反馈给项目经理,项目经理再将情况汇报给老板,抽象点就是需要实现表现层和业务层的松耦合、编排多个服务器的调用、封装一层服务查找和调用;
优点:能够将一个大型任务细化,然后统一管理这些任务的进度实现任务跟进提高任务执行效率;
缺点:当任务比较复杂的时候可能存在多层委派,这样可能产生紊乱。
(15)模板方法模式,定义一个操作中的算法框架,将一些步骤延迟到子类中,使得子类不需要改变算法结构,就能够重新定义某些特定的步骤,这里可以联想一下前端里面“模板引擎”这个概念,由抽象模板和具体实现两大部分组成,具体的UML类图如下:
在这里插入图片描述
应用场景:一次性实现一个算法不变的部分,将可变的行为留给子类来实现,各个子类的公有行为被提取到父类里面,提高程序的复用性;
优点:有效提高代码复用性,通过对子类的扩展增加新的行为提高代码的扩展性,较为良好地满足了开闭原则;
缺点:每一个抽象类都需要子类来实现,容易导致类数量的增加,同时也增加了代码的复杂度,如果父类修改了,那么子类将需要全部修改。
(16)策略模式,将算法家族封装起来,它们可以相互替换,从而算法的变化不会影响到使用算法的程序,由上下文角色、抽象策略角色、具体策略角色三部分组成,对应的UML类图如下:
在这里插入图片描述
应用场景:例如一个人纳税比率可能跟他的薪资水平有关,对应的税款计算方式也就不一样等。抽象点来说,针对一种问题,有多种处理方式,每一种都能独立解决问题,或者需要自由切换算法,又或者需要屏蔽算法规则;
优点:符合开闭原则,避免了使用多重条件转移语句,可以提高算法的保密性和安全性;
缺点:客户端必须要知道所有的策略并且自行决定使用哪一种策略,使用了多种策略类,增加了代码的维护难度。
(17)责任链模式,有点像“流水线作业”,责任链上的每一个节点都被视为一个对象,每个节点处理的请求不一样,一个请求会从链首一直传递到链尾,被沿途经过的各个对象依次处理,知道被最后一个链上的节点处理为止。由抽象处理者和具体处理着两个部分组成,详细的UML类图如下:

应用场景:多个对象处理同一个请求,但具体由哪个对象处理则是在运行时确定的,在不明确指定接收者的情况下,向多个对象中的一个处理请求,或者需要动态指定一组对象来处理请求;
优点:将请求与处理解耦,请求处理着只需要处理自己感兴趣的请求即可,具备链式传递处理请求功能,请求发送者不需要关注具体的处理细节,链路结构灵活,可以动态改变链路来增减责任,易于扩展,符合开闭原则;
缺点:责任链太长可能会导致请求被处理的时间较长从而影响性能,如果责任链路循环,系统直接崩溃。
(18)迭代器模式,可以联想一个软件项目的“迭代开发”,提供一种按照顺序访问集合/容器对象元素的方法,而又无需暴露集合内部表示,如果说责任链模式不需要关注处理责任的是哪一个节点,那么迭代器则不需要关注进来的是哪一个被处理的对象,就像是安全传送带上方的扫描仪,对于不同的物品进行相同的操作。由抽象迭代器、具体迭代器、抽象容器和具体容器四大部分组成。最终得到的UML类图如下:
在这里插入图片描述
应用场景:为遍历不同的结构提供统一的访问接口,或者是访问集合对象的内容而无需暴露它的内部表示;
优点:采用多态迭代的形式为不同的聚合结构提供了一致的遍历接口,封装了具体的迭代算法,算法变化不会影响到集合对象的架构,每个集合对象都可以提供一个或者多个不同的迭代器,使得各种元素的聚合结构可以有不同的迭代行为;
缺点:对于简单的遍历使用迭代器模式会显得比较繁琐。
(19)命令模式,对命令的封装,每一个命令都是一个操作,请求方只需要发送请求,接收方收到请求处理请求,能够解耦请求方和处理方。由接受者角色、命令角色、具体命令角色以及请求者角色四部分组成,对应的UML类图如下:
在这里插入图片描述
应用场景:遥控器切换频道就是比较典型的例子,我们只需要按下按钮,电视机就执行相应操作。通常用于需要将请求方和命令方解耦的场景,现实语义中具有“命令”的操作。
优点:引入中间抽象接口,解耦了命令的请求与实现,扩展性较为良好,能够很容易的增加新的命令;
缺点:容易导致具体的命令类过多,引入额外的请求方和抽象接口会增加程序的复杂性。
(20)状态模式,允许对象在内部状态发生改变的时候改变它的行为,由环境类角色、抽象状态角色、具体状态角色组成,具体的UML类图如下:
在这里插入图片描述
应用场景:行为随状态改变而改变的场景,一个操作中含有庞大的多分支结构,并且这些分支取决于对象的状态;
优点:结构清晰,消除了冗余的if…else判断结构,使得代码更加简洁,提高系统的可维护性,以不同的类来表示不同的状态,是得系统中状态的改变更加明确,具备良好的扩展性;
缺点:容易导致类膨胀,状态模式的结构和实现相对复杂,如果使用不当容易导致程序结构和代码的混乱,对于开闭原则支持的不够充分,新增状态需要修改负责切换管理状态的责任类。
区别:与责任链模式相比,状态模式的各个状态对象知道自己需要进入的下一个状态对象,而责任链模式并不知道自己要进入的下一个对象,因为链式组装由客户端负责。与策略模式相比,策略模式选择其中一个就能够解决问题,但是状态模式的各个子状态是一种相互切换的关系,并且用户是无法选择子状态的,只能设置初始状态。
(21)备忘录模式,这里可以联想一下虚拟机的“快照”,通过存储系统中各个历史状态的快照,使得在任一时刻都能将系统恢复到某一个历史状态。由发起人角色、备忘录角色以及备忘录管理员角色组成,具体的UML类图如下:
在这里插入图片描述
应用场景:例如Git的版本回退,需要保存历史快照的场景,希望在对象之外保存状态,出自己外其他类将无法访问状态保存的具体内容;
优点:简化了实体发起人的职责,隔离了状态存储和获取,实现了信息的封装,客户端无需关系保存的细节,提供状态回滚功能。
缺点:消耗资源,就比如虚拟机的快照会占用4-5G资源一样,如果保存的状态过多,那么依次保存占用的资源也是比较庞大的。
(22)中介者模式,使各对象不需要显式地相互作用,减低耦合度,由抽象中介者、具体中介者、抽象同事类、具体同事类四部分组成,具体的UML类图如下:
在这里插入图片描述
应用场景:系统中对象之间存在复杂的引用关系(可以联想蜘蛛网,蜘蛛就相当于中介类),产生的依赖关系结构混乱并且难以理解,或者是对于交互的公共对象需要改变行为,可以增加新的中介类;
优点:符合迪米特法则,减少类之间的依赖,降低了系统类与类之间的耦合性。
缺点:将多个类之间的相互依赖变成了中介者与多个类之间的依赖,当同事类比较多时,中介者会变得复杂难以维护。
(23)解释器模式,功能与编译器类似,用于解释固定的语法,提取句子中重要的部分,由抽象表达式、终结符表达式、非终结符表达式和上下文环境类四个部分组成,具体的UML类图如下:
在这里插入图片描述
应用场景:类似于摩斯密码和乐谱这样具备固定语法表示的场景,主要将一种负责且重复出现的问题用一种简单的语言表示出来;
优点:由于语法是有很多类表示的,当语法规则更改时,可以修改相应的非终结符表达式,需要扩展语法时只需要添加相应的非终结符类即可,增加了新的解释表达式的方式。
缺点:当语法规则较为复杂时,会产生大量的解释类,引起系统类膨胀,解释器使用递归实现,当表达式层级较深时,容易降低解释效率,并且一旦出现错误,调试起来会比较困难。
(24)观察者模式,定义一种一对多依赖关系,一个主题对象可以被多个观察者同时监听,当主题对象改变时,依赖它的主题对象会收到通知并自动刷新,由抽象主题、具体主题、抽象观察者以及具体观察者组成,对应的UML类图如下:
在这里插入图片描述

应用场景:系统的一方行为依赖于另一方的行为或者需要使得系统中一方的变动被通知到其感兴趣的另一方,从而让另一方也进行相关的响应。抽象而言就是其他的一个对象或者多个对象依赖于一个类的变化。实现类似于广播机制的功能,不需要知道具体的接听者,只需要广发,系统中感兴趣的类会自动接受该广播信息;
优点:符合依赖倒置原则,观察者与被观察者之间是松耦合的,分离观察者与被观察者,使得一个数据的变化能够被多个观察者响应;
缺点:如果观察者数量过多,事件通知会耗时较长,如果其中一个观察者卡壳,会影响后续观察者的接受,如果观察者与被观察者之间存在循环依赖,那么系统将会崩溃。
(25)访问者模式,被称为最复杂的设计模式,使用频率不高,目的是将数据结构和数据操作分离,由抽象访问者、具体访问者、抽象元素、具体元素以及结构对象五大部分组成,具体的UML类图如下:
在这里插入图片描述
应用场景:数据结构稳定,但是对数据的操作会经常发生变化;需要数据结构和数据操作分离的场景;需要对不同的数据类型进行操作但不能使用分支判断具体类型。
优点:符合单一职责原则,解耦了数据结构和数据操作,使得操作集合可以独立变化,可以扩展访问者,实现对数据集的不同操作;
缺点:难以改变被操作的数据结构,如果有新的数据结构被加进来,那么需要增加新的操作,违背了开闭原则;违背依赖倒置原则,访问者依赖的是具体的元素类型,而不是抽象。

6.容易混淆的设计模式之间的区别

    虽然前面提到了少量的设计模式的区别,但主要是为了理解这些设计模式而稍加区别,不是很全面。当对设计模式有了较为深入的理解的时候,这里的深入主要是说能够明白设计模式的设计意图基本的UML结构应用场景以及优缺点,虽然上面有一些文字说明,但是可能由于部分文字比较抽象,还需要参考一下其他大佬的文章,加深自己的理解。当你觉得对这些设计模式有深入的理解之后,可以阅读一下下面关于设计模式的区别,方便自己之后更好的使用设计模式。

6.1创建型设计模式

(1)工厂模式与抽象工厂模式
    虽然都会提供对外的获取对象的方法,但抽象工厂模式的抽象产品可以使多维的,而工厂模式中的抽象产品是单维的。一个抽象工厂可以创建同一个产品族下的多个产品,但对于工厂模式,抽象产品则由其实现类创建。
(2)简单工厂与建造者模式
    虽然都是为了将创建产品的细节封装起来,建造者模式比简单工厂模式多一个建造者类,用户在调用建造者模式里面的build方法之前都是对产品的预设,而在简单工厂模式下,没有预设参数的动作,直接调用创建产品的方法获取类的实例;

6.2结构型设计模式

(1)装饰器模式与代理模式
    虽然二者都是为了达到增强功能的目的,但装饰器模式会设计一个抽象的组件,不管是装饰器还是具体的组件,都是抽象组件的实现类,属于同一继承体系,功能扩展是在具体的装饰器里面实现的,但是代理模式简单粗暴,通过编码直接实现;
(2)装饰器模式与门面模式
    站在单一职责原则的角度上,门面模式的门面类更像是一个万能类,看上去覆盖了全部子系统的功能。装饰器模式主要是为了同一多个子系统的访问入口,承担一定的静态代理功能,但是门面模式是站在子系统的角度(偏向于业务),将子系统隔离;
(3)装饰器模式与适配器模式
    装饰器模式包装的是兄弟类,同根同源,而适配器模式则是将非本族的对象伪装起来;装饰器是为了增强对象的功能,而适配器模式更多的偏向于提高接口的复用性;
(4)适配器模式与代理模式
    核心目的不一样,虽然二者都能够实现对目标对象的保护和隐藏,但是适配器模式主要解决兼容性问题,代理模式主要是为了功能增强,目标类的方法不会直接提供给用户调用,而是调用代理类的方法获取增强之后的结果。

6.3行为型设计模式

(1)策略模式与模板方法模式
    虽然都可以用来分离高层的算法和低层的具体实现细节,允许高层独立于它的具体实现细节重用,但是在策略模式中,策略类是实现策略抽象接口的全部方法,在模板方法中,具体的实现类只实现模板中的模板类中的部分方法,模板类通常是抽象类而不是接口;
(2)策略模式与命令模式
    虽然都需要提供一个功能清单给用户选择,但站在业务的角度上来看,命令模式的请求和处理的代码是写在一起的,通常用于解耦请求和处理,策略模式则关注的是算法,提供固定选项,用户参与到具体的业务执行中,不同策略会得到同一类型结果,每一种策略都是可以相互替换,命令模式每一条命令是不能相互替换的;
(3)策略模式和委派模式
    策略模式关注的是策略是否能够相互替代,委派模式更加关注分发和调度的过程;策略模式里面的上下文容器只是算法策略的选择切换所在,不需要实现策略接口,但是委派模式中委派者和被委派者实现同一个接口;
(4)桥接模式与适配器模式
    虽然桥接模式和适配器模式都是间接引用对象,但还是那句话,适配器关注兼容性问题,只要相关对象能与系统定义的接口一起协同工作即可;适配器模式常用在与第三方产品的功能集成上,但桥接模式的接口是稳定的,用户可以扩展和修改桥接中的类,但是不能修改里面的类。桥接模式不使用继承,适配器模式使用;
(5)桥接模式与组合模式
    虽然二者都是为了将两个继承体系建立并联系满足个性化的需求,但是组合模式更偏向于统一行动,统一成一套api便于整体操作。

6.4跨类对比

(1)享元模式与单例模式
    虽然二者都是为了节省内存开销,但是享元模式可以再次创建对象,也可以使用缓存对象,单例模式则不同,只能有一个实例对象;
(2)建造者模式与装饰器模式
    虽然二者都是为了扩展装饰,但建造者模式针对的是构建过程不稳定的对象(相对复杂),但是装饰器模式要求构建过程比较稳定。
(3)策略模式与简单工厂模式
    简单工厂模式直接创建具体对象并利用被创建的对象去执行相应操作,但是策略模式设计了一个上下文,讲操作交给上下文,策略类内部并没有创建具体的对象。
(4)策略模式与适配器模式
    虽然二者都是通过已经存在的,运行良好的类来实现接口的,但是策略模式是将一系列算法封装起来,提供一个统一的接口给客户,这些算法是可以相互替换的,适配器模式则是将类的接口转化成客户希望的接口,从而使原本接口不兼容的类能够一起工作;策略模式需要将全部方法暴露给用户,适配器模式则是直接定义好实现方式以及内部需要引入的类,客户端直接调用这个类就行;
(5)中介者模式与适配器模式
    虽然二者都是为了在一个类中调用另外一个类同时减低耦合性,但是中介者模式主要是为了协调资源,适配器为了兼容;适配器模式可以使用继承也可以使用组合来实现,但是中介者模式只能是以组合的方式实现;
(6)中介者模式与代理模式
    虽然二者都具有保护目标对象的特性,但是如果代理被称为“媒婆”,那么中介者模式则是“不负责任的媒婆”,代理模式将目标对象和代理对象联系起来,并且参与二者的信息交流中,中介者只负责牵线搭桥,建立联系,不参与具体的过程;
(7)中介者模式与桥接模式
    虽然二者都能够将两个对象联系起来,但是桥接只适用于将两个维度的对象联系起来,中介者模式则能够将多个维度建立连接,更能够适用于复杂的“桥接”;
(8)命令模式与桥接模式
    虽然二者都能够达到解耦的目的,桥接模式需要一个中间实现类,但是命令模式需要一个抽象的中间类,桥接模式通过抽象对象构建两个不同维度之间的联系,但是命令模式则是通过封装命令对象来建立调用者和被调用者之间的联系,二者适用于不同的业务场景;
(9)委派模式与门面模式
    委派模式和门面模式比较类似,委派模式针对行为上的统一调度和分发,而门面模式针对的是组织结构上的统一入口,在结构上委派模式比门面模式多一个公共接口;
(10)委派模式与代理模式
    虽然二者都具有保护目标对象的特性,但是委派模式是一种全权的静态代理模式,对目标类的功能不做任何代码增强,而代理模式能够对目标类的功能进行增强;

7.声明

    本篇文章来自于对《设计模式就该这样学》——谭勇德的总结,如需使用文章内容,请附上这本书以及作者,尊重原创。

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

凌空暗羽

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值