-
面向接口编程,而不是面向实现编程,这里的意思是说,使用者应该只使用抽象类里面的接口做操作,不应该有抽象类以外的操作。子类只是重写或者实现抽象类。举个例子,如果我们想要创建一个子类的对象,我们不应该直接new一个子类,而是通过创建型模式(工厂模式等)来调用抽象类创建一个对象。
-
继承的优缺点:优点:继承可以比较方便的改变被复用的实现,了解父类内部的实现细节,缺点:无法在运行时改变父类继承的实现,父类发生变化时必然影响子类,破坏了封装性。当我们要继承时,发现继承下来的实现不适合解决新的问题,就必须重写父类或者更改成新的类,这样就限制了灵活性并且最终限制了复用性,所以最好的解决办法是只继承抽象类,因为抽象类通常提供比较少的实现。
-
组合的优缺点:组合要求对象遵守彼此的接口约定,因为对象只能通过接口访问,所以不会破坏封装性,运行时还能用一个对象代替另一个对象。组合还会避免继承的层级过多,成为一个庞然大物。所以优先使用对象组合,尽量不要使用继承。
-
委托(delegation):委托就是代码里面常常见到的,在调用Window->area()的时候,这个函数返回的是rectangle->area(),交由rectangle实现,这种实现方式有点类似于继承中的子类交给父类实现,但是却没有破坏封装性。委托也有缺点:运行比较低效(有函数跳转)
-
参数化类型(模板):可以定义一个类型时不用指定该类型用到的其他所有类型,这些未指定的类型在使用时以参数形式提供,这是一种特殊的组合方式
-
一些导致系统需要重新设计的原因及对应方法:(1)显式的指定一个类来创建对象:这样就会产生依赖,依赖于特定实现而不是特定接口,所以具体做法应该是使用创造型模式。(2)对特殊操作的依赖:当为某个请求指定一个特殊的操作时,完成该请求的方式就写死了,可以在编译时或者运行时改变响应请求的方法,例如责任链模式或者命令模式;(3)对硬件和软件平台的依赖:外部的操作接口在不同软硬件平台上不同,这样可移植性就会降低,使用抽象工厂模式或者桥接模式可以解决这个问题,(4)对对象表示和实现的依赖:知道对象怎样表示、保存、定位或实现的客户在对象变化时可能也需要变化,应该对客户隐藏这些变化。解决方法是使用抽象工厂模式、桥接模式等。(5)算法依赖:算法在开发和复用的时候常常会发生变化,依赖于某个特定算法的对象在算法发生变化时也要跟着变化,因此应该把算法模块孤立起来,使用Builder模式等设计模式(6)紧耦合:紧耦合会让类很难用,因为是相互依赖的,这个时候应该使用抽象工厂模式、命令模式等降低耦合度。(7)通过生成子类来扩充功能:这种方式可能会导致类爆炸,应该通过对象组合来扩充功能,,例如桥接模式,责任链模式等。(8)不能方便的对类进行修改:有时候不得不改变一个难以修改的类,但是没有源代码,或者修改需要牵一发而动全身,那可以使用适配器模式等设计模式。
https://cloud.tencent.com/developer/article/1650116 这个文章讲的很好
- **开闭原则:**对扩展开放,对修改封闭,模块应尽量在不修改原(是"原",指原来的代码)代码的情况下进行扩展。如何实现:(1)参数类型、引用对象尽量使用接口或者抽象类,而不是实现类,保证抽象层稳定。(2)通过接口或者抽象类约束扩展,对扩展做边界界定,不允许出现抽象类中不存在的public方法(3)设计完成后,不允许修改接口或者抽象方法的定义
- **单一职责原则:**一个类或者模块应该只有一个改变的原因,不能让某个类承担过多职责。
- **里氏替换原则:**所有引用基类的地方必须能够替换成其子类(就是子类不能做额外的操作)。这样基类才能真正被复用。
- **依赖倒转原则:**程序要依赖于抽象接口,而不是依赖于具体的实现,这样就需要引用层次高的抽象类。如何实现:(1)每一个具体的类都应该由一个抽象类,(2)任何具体类都不应该派生出子类。(3)尽量不要复写基类中的方法。(4)结合里氏替换原则使用(5)所有类引用的对象应该是抽象类或者接口。抽象不应该依赖于具体,而具体应该依赖于抽象
- **接口隔离原则:**客户端不应该依赖于他不需要的接口,类间的依赖关系应该建立在最小的接口之上,换句话说就是接口应该尽量细化,每一个抽象类的接口数量应该尽可能的少。如何实现:(1)首先要保证职责单一(存在满足了单一职责但是接口还是很大的情况)(2)接口要高内聚,少对外发布public的接口(3)只提供访问者需要的方法
- **合成聚合复用原则:**尽量使用聚合/组合,不要使用继承。因为继承破坏了封装性。
- 迪米特法则(最少知识原则):一个软件实体尽可能少的和其他实体发生相互作用。发生作用的时候可以通过中间类或者抽象类,例如体育老师想要知道全班的人数,可以通过体育课代表的方式。
**描述:**就是把“工厂”给抽象出来,所有的工厂都继承自这个抽象工厂,每一个继承来的工厂拥有创建一系列(或相关)物体的能力,例如一个Bombed factory可以创建bombed room、bombed door 还有bombed wall、另一个 Enchanted Factory可以创建Enchanted room、Enchanted door还有Enchanted wall,每一个xxxwall、xxxroom都是一个product。
优点:(1)可以分离应用创建和使用。因为只需要一个抽象接口就可以操作instance。(2)可以方便的改变产品的系列,我们想要把Bombed maze改成enchated maze,只需要换一个工厂类就可以。(3)有利于产品的一致性,一个子类工厂里面的东西一定是一个系列的
缺点:(1)难以支持新的种类对象,比如说我们想要在room。door和wall之外新加一个floor,那么所有抽象工厂的子类都要创建一个createFloor。(2)参数化抽象工厂可以在一定程度上解决这个问题
什么时候用:(1)一个产品有多种组合、多种系列的组件,且系统需要独立于所有组件的创建、组合和表示(2)一个系统要由多个产品系列中的一个来配置(这一点区别于prototype模式),(3)提供一个产品类库,只需要他们的接口,实现等着以后再做。
实现:因为是创建一系列的东西,所以工厂的名字通常是形容词,例如Boom,heal,同时还要有一个创建抽象工厂的FactoryProducer
**描述:**把对象的创建封装成一个类或者多个工厂函数。由子类决定到底实例化哪一个类。调用者只关注接口,并不关注new了哪一个子类。
优点:(1)为子类提供一个hook接口。(2)连接平行的类层次(3)屏蔽产品的具体实现,调用者只关心接口即可。
缺点:(1)每增加一个产品时,都需要增加一个对应的创建接口,可以用模板,但如果创建接口本身也是另一个factory创建的,就都要改了。
什么时候用:(1)当我们不知道具体实例化哪一个类时。(2)当一个类希望由他的子类指定所创建的类时,(3)当类要把创建对象的操作委托给多个帮助子类中的某一个。
**与抽象工厂的区别:**每一个具体工厂类只能创建一个产品的实例,抽象工厂的一个具体工厂类可以创建多个具体产品类的实例。书中一个RoomFactory应该只有一个MakeRoom,一个DoorFactory 应该只有一个makeDoor,而抽象工厂模式的Bombed factory则可以有MakeRoom、MakeDoor等多个实例。
**描述:**把一个复杂对象(包含很多小对象)的构建过程和他的表示(小对象的组成)分离,使得同样的创建过程可以创建不同的表示
优点:(1)可以灵活的改变一个产品的内部表示(2)可以对构造过程进行灵活控制(3)将构造过程和表示代码分离
缺点:(1)产品得有共同点,适用范围受限,(2)如果内部变化很复杂,会有很多的建造类。
什么时候用:(1)需要生成的对象有很复杂的内部结构,(2)需要把构造对象的过程和该对象的组成分离开时。
**与工厂模式的区别:**更关注零件转配的顺序和过程
实现:builder里面并不提供构造的过程,而是只把具体创建的东西给封装起来。
**描述:**在创建之前先生成一堆子类的实例(prototype),每个子类都需要有一个clone的接口,创建新实例的过程就是调用这个clone接口的过程。例如MakeDoor的实现是prototypeFactory->_door->clone();
优点:(1)可以在运行期建立和删除原型,(2)可以改变prototypeFactory的原型值来创建不同的对象。(3)相比较工厂模式,可以不需要子类Factory,只需要创建不同的Room就可以用原型模式clone新的不同的room,这样就减少了很多类,
缺点:(1)已有的类需要新加一个clone方法,(2)当已有的类有循环引用时会比较麻烦
什么时候用:(1)当一个系统应该独立于他产品创建、组成和表示时。(2)要实例化的类在运行时才知道。(3)为了避免Factory创建一个平行的类。(4)当一个类的实例只有几个不同状态组合中的一种时
实现:需要在一开始就创建所有的prototype
**描述:**保证一个类只有一个实例,并且有一个可以访问这个唯一实例的接口。
优点:(1)减少内存开销。(2)缩小名字空间,对全局变量的改进(3)singleton类可以有子类,子类可以通过注册的方式注册到父类里面(一个名字map到一个实例里面)(4)允许可变数量的实例。(5)比类::操作更加灵活,因为类::操作不能拥有多个实例。(5)延迟创建实例
缺点:(1)没有接口,不能继承
**什么时候用:**唯一的实例的时候(例如内存过多等)
**实现:**C++11以后线程安全的实现就容易了很多
结构性模式的目的是想要把已有的类和对象组合起来以获得更大的结构,是一种连接不同类的模式,例如多重继承方法把两个类组合成一个类。或者适配器模式,把两个不适配的类组合在一起,让他们适配起来
**描述:**把一个类的接口转换成客户希望的另一个接口,使原本不兼容的两个接口可以一起工作
**优点:**可以方便的连接两个不兼容的类,提高了类的复用,灵活性好。
**缺点:**如果过多的使用适配器,会让整个架构非常凌乱,尽量不要使用适配器,而是重构系统
**特别效果:**可以继承两个类(类适配器),或者继承一个类,组合并且private另一个类(对象适配器),实现双向适配器。
什么时候用:(大部分是维护老代码的时候,两大部分代码都不好改)(1)想要使用一个已经存在的类,但是接口不兼容。(2)想要使用很多已经存在的子类,但是不想每一个子类都写一个适配器,这个时候可以适配他们的父类。(3)想要创建一个可以复用的类,这个类可以与其他类不相关或者不可预见。
**实现:**有两种实现,一种是继承自两个类,然后做转发,另一种是继承一个类,然后构造的时候传入另一个类的对象,
**描述:**把抽象接口和实现部分分离,让他们可以独立的变化,一般抽象接口会调用实现的类,例如imp->operation();
**优点:**抽象和实现分离,有很好的扩展能力,实现细节对用户透明
**缺点:**增加系统的复杂度,提升理解成本。
**特殊实现:**一般Implementor接口只提供基本操作,而Abstraction提供了高层次的操作。
**什么时候用:**当一个抽象可以有多种类型的实现时,继承会不太灵活,难以对继承和实现部分独立的修改。(1)想要对客户完全隐藏实现部分,对实现的修改让客户完全不知道(2)抽象有多个角度的分类,并且每一个分类都会发生变化。必须要把抽象分成两个部分。(3)类的抽象以及他的实现都可以通过生成子类的方式加以扩充。(4)共享implementor的实现,用引用计数来共享
其实就是一个抽象的imp,还有一个抽象的接口(例如window),然后不同的应用继承window,而不同的底层实现继承imp,二者通过抽象的Window和imp链接起来,在创建Window的时候,会传入一个WindowImp,例如
IconWindow window(new XWindowImp());
window.DrawIcon();
imp一般只提供基本操作,例如画点,画线,而Window提供更高层的操作,例如画icon等
和Adapter的区别:实现方法相似,但是目的不同。Adapter是在项目后期为了兼容两个类实现的。而Bridge是在项目前期,想要把实现和抽象分离才来做的。而且Adapter定义了新的接口。
**描述:**把很多小模块组合在一起变成一个大模块,并且大小模块对外是一致的,拥有这样一个树形结构来表示“整体-部分”
优点:(1)高层模块不用区分整体和部分,调用简单,(2)节点自由增加。(3更容易增加新类型的组件。
缺点:(1)叶子和树枝的声明都是实现类而不是抽象接口,违反了依赖倒转原则。
实现注意:(1)抽象父节点的GetComposite应该返回nullptr,叶子节点不重写GetComposite,同一层次的组合节点重写GetComposite()并且返回自身,这样就可以识别是否是叶子节点了。(2)抽象父节点的接口应该有尽可能多的公共接口(3)在组合节点中使用高速缓冲来提高(查找等)性能。(4)应该有组合节点负责删除他自己的子节点
抽象父类应该拥有尽可能多的缺省实现,子类需要提供很多实现,不需要对每一个不同的叶子节点创建一个类,可以理解只需要创建一个Node类就行,剩下就是一个树形结构了,只是在使用的时候,可以给不同的node起不同的名字,例如memory,disk等
什么时候用:(1)想要表示对象的整体-部分层次结构(2)想要用户忽略组合对象和单个对象之间的区别。
**描述:**装饰器模式就是拥有一个兄弟类叫做Decorator,这个Decorator类和我们要修饰的类接口完全一致,但是会有一个多出来的接口,在所有调用原始对象的时候,都用这个Decorator的子类替换,调用接口前会先调用Decorator的某个接口。这样就可以动态的给某个类添加新功能
**优点:**动态灵活的给某个对象添加一个新功能,比生成子类更灵活,且修饰器类和被修饰类都可以独立扩展。
**缺点:**多层迭代的装饰会增加系统复杂度,建议重构
**实现:**父类一定要比较简单,(2)当只想添加一个装饰时,建议不要添加抽象类。
其实就是一个composite,只不过在调用某个接口前,先调用Decorator自己的一个接口
什么时候用:(1)当我们在不想增加很多子类的时候扩展某个类(例如有大量独立的扩展,如果扩展的话需要生成大量的子类,或者类定义被隐藏,类定义不能生成子类)。(2)想要增加个可以撤销的职责
**和Composite的区别:**两者的类图都比较像,但是Decorator是为了装饰某个类的,而Composite是为了让各个小组件都相同。两者具有互补性。例如有一个抽象类同时拥有Decorator和Composite子类,从Composite角度看,Decorator是一个leaf,从Decorator角度看,Composite是一个可以装饰的Concrete Component。
**描述:**当我们的子系统拥有非常多的接口(不同层次的接口),使用这个子系统需要调用这些不同层次的接口,这样做很麻烦,Facade模式就把这些接口包装成一个简单的接口。让(多个)用户更容易使用。
优点:(1)解耦客户和实际实现,减少相互依赖,提高了灵活性和安全性。
缺点:(2)不符合开闭原则,如果要改东西很麻烦,继承重写都不合适。
**什么时候用:**为复杂的系统给外界提供一个好用的接口,子系统相对独立,(2)防止低水平编程人员造成错误
**实现:**直接封装成一个接口就行,把中间的过程,以及相互关系屏蔽掉。compiler会封装下面的语法树等会用
**和Adapter和Bridge的区别:**Facade实现了新的接口
**描述:**把很多细粒度的对象共享(用指针共享)起来,降低内存。把一个类拆成内部模式和外部模块,内部模块共享,外部模块会变化,不共享
**优点:**可以降低系统内存(如果外部对象是计算出来的,则更加节省内存,用计算换空间)
**缺点:**使系统变得复杂
**实现:**一般用hashmap存储这些细粒度的对象,然后通过key来访问。例如数据库的数据池
flyweight使用的时候,需要在使用细粒度对象前,通过context等方式改变当前状态,然后才能使用draw等操作,一般glyph这种有自绘制的东西会用。DDGR的字体就用到了这种模式。
**什么时候用:**有大量细粒度的相似对象(注意是对象不是类)。(2)需要有缓冲池的场景
描述:让一个类去代替另一个类做操作,这个代理类可以完全控制被代理的对象。通常是另一个类的创建非常耗时时使用。
**优点:**职责清楚,扩展性好
**缺点:**实现代理模式通常比较复杂,代码编写成本高(2)因为增加了一层,系统的效率可能会受到影响
实现方式:(1)使用一个xxxproxy的类,这个对象放在和原始对象同样的位置(2)C++重载存取运算符->
proxy需要保存创建实际对象所需的所有状态信息,例如filename等,同时还需要有一个private的实体类对象,一般mesh,image这种会用
**什么时候用:**远程代理(对远程对象用)、保护代理(保护原始对象,控制权限),想要对某个操作的类做一下权限控制。smart Reference等。
和Decorator的区别:Proxy构成一个对象并且提供一致的接口,但是不能动态的添加某个功能。Decorator组件只提供一部分功能。
行为型模式描述的是算法和对象间职责的分配,包括他们的通信模式,控制流
**描述:**责任链模式就是把一个操作交给一连串接受者,这一连串接受者从前到后看能不能处理这个操作,不能处理的话就交给下一个接受者。
优点:(1)在发送命令的时候并不需要知道到底谁来处理,只管发送就行。(2)降低发送者和接受者之间的耦合度。(3)可以通过改变链里面的顺序来添加修改责任。(4)添加新的请求处理类很方便
缺点:(1)效率堪忧,而且代码调试会比较麻烦(2)不能保证请求一定会被责任链处理掉。
**实现方式:**如果请求是一个带参数的函数,那么可以通过封装一个Request类来处理请求,把参数封装起来。GTX的Hook其实就是一个责任链模式。如果hook了接口,就处理然后转交给DDK,如果没有Hook,就直接转给DDK。
**什么时候用:**有多个对象可以处理同一个请求,且不希望指定到底是哪个对象处理请求。一般在CS架构中比较常用。
**描述:**主要是把命令发送者和命令接受者解耦开。从命令发送者→命令接收者,变成命令发送者→命令→命令接收者。这样可以实现撤销、重做、命令记录、事务处理等操作
优点:(1)解耦命令发送者和命令接受者的耦合度(2)可以很容易的添加新的命令,因为不需要改变已有的类
缺点:(1)可能会导致有很多具体命令类
**实现方式:**创建一个抽象类,只有一个Execute纯虚函数,每一个命令子类都重写这个Execute,每一个命令子类的构造函数都要传入一个Application的抽象类对象,然后Execute的时候就用这个抽象类对象的多态做操作
**什么时候用:**要对某一个操作做“记录、撤销、重做、事物处理”的时候,紧耦合无法抵御变化。
**描述:**主要是对一个命令或者字符串做解释
**什么时候用:**主要应用在编译器里面,不常用
**描述:**迭代器模式就是把遍历循环给封装起来,隐藏内部遍历的实现(例如一串连续内存的跳转)
优点:(1)这种方式可以隐藏被遍历对象的内部实现,解耦遍历逻辑和内部实现。(2)增加新的迭代器和新的聚合类都比较方便,无需改变原有代码(3)简化了聚合类,在同一个聚合上可以有多个遍历(4)分离数据存储和数据遍历
缺点:(1)增加新的聚合类需要增加新的迭代器类,类的个数成对增加,增加了系统的复杂度
**实现方式:**建议使用多态的迭代器,这样在遍历不同的聚合结构时可以有统一的接口。有外部迭代器和内部迭代器两种方式(根据控制遍历的对象不同)。使用C++模板写一个ListIterator的迭代器,ListIterator有一个List的成员变量,在Iterator初始化的时候传入这个变量。
Iterator的实现比想象中的稍微麻烦一些,主要是接口定义,需要重载挺多操作符的,建议多关注一下
**什么时候用:**当我们想要遍历一个聚合对象的内容,但是不想暴露出这个聚合对象的内部实现时,而且我们可能有多种遍历方式。想要提供一个统一的接口
**描述:**中间者模式封装了多个类之间的交互,类似于一个路由器的功能,把对象之间的交互都通过这个中介类来做。
优点:(1)降低了类与类之间的耦合度,从多对多变成了多对一,符合迪米特法则
缺点:(2)中介者类自己可能会比较庞大,到后面可能会比较难维护。
**实现方式:**有一个中介者类,拥有所有相互交互组件的指针,所有其他组件都只知道中介者对象,而不知道其他对象,中介者对象收到消息以后再决定具体哪个组件做事情。
**什么时候用:**系统中存在多对多的引用关系,使系统变得非常复杂。我们想要一个中间类封装多个类之间的行为时使用。一个对象引用了很多其他对象,导致这个类很难被服用。想要定制一个分布在多个类中的行为,又不想生成太多子类。
**描述:**创建一个Memento对象,这个对象保存了一个对象的所有状态。
**优点:**提供了一个可以恢复的机制,可以让用户方便的回到之前的某个状态,类似于存档一样。(2)实现信息的封装,用户甚至不需要知道我们保存了什么。
缺点:(1)如果类的成员变量过多,导致会占用很多资源(2)需要记得删除Memento,过多的Memento会导致管理Memento有些繁琐,或者占用大量内存。
**实现方式:**新建一个Memento类,这个类在创建的时候直接传入需要被保存的对象的状态,然后保存起来,在恢复对象状态时,只需要传入这个Memento对象即可。还需要一个MementoList来管理所有的状态。为了节约内存,可以使用原型模式加备忘录模式。或者使用存储增量信息。以减小内存占用
**什么时候用:**当我们想要定义存档时,但是命令模式可能在两个存档之间有很多操作。这个时候Memento更好
**描述:**就是定义一个观察者,在某个操作执行的时候,通知所有已经注册的观察者执行操作。在一定程度上类似责任链模式,但是观察者模式需要注册某个操作。是一个发布-订阅的关系。
**优点:**观察者和被观察者是抽象耦合的,观察者模式提供了一个注册-触发的机制。
**缺点:**如果一个被观察者上挂了很多观察者对象,那做一个操作要通知所有的观察者,可能会很耗时。(2)如果观察者和被观察者之间有循环依赖的话,可能会导致他们之间产生循环调用最后程序崩溃。(3)观察者无法知道实现是怎么发生变化的(过程),只是知道发生了变化(结果)
**实现方式:**创建一个observer抽象类,作为观察者。再创建一系列观察者子类。被观察者有观察者List,当他做了操作时,遍历所有的观察者对象,并通知他们。被观察者有一个Attach和Detach接口,用来添加和删除观察者。如果观察者和被观察者之间的依赖关系非常复杂时,可以使用一个ChangeManager来管理所有的更改。这个ChangeManager类似于一个中介者。他一般会有两个子类,一个子类处理更新每个观察者,另一个子类处理有向无环图,解决各个观察者之间的依赖关系(当一个观察者观察多个目标的时候)。观察者只需要多继承一个观察者抽象类即可。
**什么时候用:**一个抽象模型的一个方面依赖于另一个方面。(2)一个对象的改变会导致其他多个对象也发生改变,但是我们不想知道具体有多少对象发生了改变(解耦)(3)系统中有一个触发链,A改变影响B,B改变影响C.......。例如ClockTimer,他变化会影响到很多观察者。
**描述:**状态模式,我觉得把他叫做“状态机模式”更好。他的理念就是定义一个State抽象类,然后每一个子类都是一个状态机。每一个状态机都有相同的接口。
优点:(1)把转换规则封装起来。 (2)可以方便的增加新的状态(3)把多个地方switch等条件转换给干掉了。合并成了一个(4)可以让多个Environment对象共享同一个状态对象,从而减少系统中对象的个数。
缺点:(1)状态模式会增加系统中的类和对象的个数。(2)对“开闭原则”不友好,新增一个状态类,需要修改那个负责状态转换的源代码(3)状态模式使用不当的话会导致程序和代码的混乱。
**实现方式:**创建一个state抽象类,state的所有操作都只提供一个缺省的实现(什么都不做,而不是纯虚方法),把所有的状态都变成state的子类。然后子类实现需要的方法。
什么时候用:
**描述:**策略模式就是封装一系列的算法,并且使他们之间可以相互替换,使算法独立于使用它的客户
优点:(1)算法之间可以自由切换。(2)避免使用多重条件判断。消除了条件判断语句(3)扩展性极好
缺点:(1)策略类会增多(2)所有策略类都需要对外暴露
**实现方式:**添加一个strategy抽象类,他定义了相同行为的算法的接口。或者使用C++模板,来把Strategy作为模板参数使用(这种方式要求strategy在编译的时候就确定了)
什么时候用:如果一个系统里面有很多类,他们的区别只是他们的行为不一样,那就可以把他们封装起来。(2)一个算法经常有很多变体。(3)一个类定义了很多行为,并且这个行为在这个类中经常以多个iflese或者switch形式出现。
**描述:**一个抽象类公开定义了执行它的方法的方式和模板,他的子类可以重写其中的一部分方法,但是整个指令流程仍然在父类中。子类无法改写父类的执行流程。
**优点:**封装不变部分,扩展可变部分。(2)提取公共代码,便于维护(3)行为由父类控制,子类实现
缺点:(1)每一个不同的实现都需要一个子类,可能会使子类数量过多
**实现方式:**实现一个抽象类,对外public的接口只有一个,然后这个接口定义了一系列操作的流程。子类只能重写private的操作。当然也可以显式的调用父类的操作(Hook功能)
什么时候用:
**描述:**访问者模式给我的感觉和抽象工厂模式很像,不过一个是行为型模式,另一个是创建型模式,访问者模式就是定义一个visitor抽象类,这个类的子类是一系列动作列表(而抽象类定义了一系列访问对象的列表)这样就形成了一个动作x对象的矩阵。
**优点:**符合单一职责原则(2)有很优秀的扩展性(3)集中相关的操作,分离无关的操作
缺点:(1)破坏了类的封装,具体的元素对访问者暴露了细节,违反了迪米特法则(2)具体元素变更比较困难,增加一个新的对象需要修改所有的子类(3)违反了依赖导致原则,依赖了具体类,而不是依赖于抽象
**实现方式:**新增加一个visitor抽象类,这个抽象类定义了访问一系列对象的接口,例如VisitElementA、VisitElementB等接口。定义一个Element抽象类,ElementA和ElementB是他的子类。Element抽象类只有一个Accept(Visitor&)接口。如果成员时Composite对象的话,需要遍历所有的子对象
**什么时候用:**当对象对应的类很少发生改变,但是对象经常会有新的操作时。