1.
所谓OCP即指对扩展开放(当新需求出现的时候,可以通过扩展现有模型达到目的),对修改关闭(对已有的二进制代码,不允许进行修改)。
实现OCP原则的关键是抽象与封装。利用抽象封装完成对需求可能发生变更的部分进行处理,具体处理手段如下:
1.使用多态的方式
做一个继承树。此方案针对会发生修改但不是很严重的地方,让需求扩展的实体继承已经存在的实体。
2.使用继承与组合联合的方式
例如: 装饰者模式、策略模式、状态模式、桥模式
3.延迟绑定:
运行时注册:
使用Event style或Observer pattern实现运行时注册的方式,当需要进行扩展时,就让扩展的方法监听某个事件,事件发生时这个方式就会被调用(service lookup)
配置文件:
使用配置文件进行启动时绑定,将需要修改、扩展的信息写在配置文件中,通过解析配置文件来决定做什么事情(DataDriven)
继承多态方式(LSP)
构件更替:
如果需要修改、扩展,则在加载模块的时候使用修改/扩展的模块,实现加载绑定。一般是将变化的部分写在一个.dll 文件中,变化的时候直接更新.dll 文件。(ReflectiveorMeta-LevelDesign)
预定义协议:
在两个之间预定义协议,然后各个进程可以独立进行变化,只要通信协议不变。
例如TCP/IP等协议(Uniform Access)
4.信息隐藏
下文有详细讲述
5.泛化模块:Interpreter-Driven
6.限制交互路径
迪米特法则:一个对象应该对其他对象有尽可能少的了解
1. 每个模块有一个基本的secret——外部的行为和内部隐藏
每个模块隐藏重要的设计决策的实现,只有模块的组成可以了解细节。所有的设计决策都独立于彼此。即一个模块的接口功能与模块内部程序细节的分离。给出功能接口,隐藏功能实现程序的细节方法:外观模式Facade pattern——信息隐藏:用户和部件的子系统之间解耦合,降低耦合Controller2. 模块可能有附加的secrets——改变
预期的变更:把变更从模块中分离开来,安排到新的类,方法或者设计单元中.然后封装隔离所有的secret这样其一旦变更,变更不会影响其他程序模块。将要发生变化的程序部分需要进行一个决策。给出需要修改部分的接口,隐藏待修改部分的实现程序细节方法:策略模式:将算法从包含其的对象中抽离出来,封装该算法(策略)为一个对象装饰模式:装饰在不改变类的代码的情况下扩展了一个类的实例的功能适配器模式:将类的接口转换成另一个client期望的接口,让接口不兼容的类能一起工作 如果上下文也有可变性:用桥接模式1. 策略模式:将要变更的算法独立出来,按照OCP的方法将其封装起来成为一个对象,同时给这个算法建立一个继承树(为其设置一个接口,让所有这个算法的可能变更的版本实现这个接口)变化来源于内部,即自己做完某件事情后把自己的状态改掉。
3. 桥接模式:在 抽象和实现需要独立变化的情况下,将抽象和实现做成两个不同的继承树。抽象接口和实现接口是两个完全不同的接口,实现接口一般包含很基本、原始的方法,而 抽象接口包含的是基于原始方法实现的更加高级的方法。在抽象的接口中包含一个对实现对象的引用,抽象对象的方法中需要调用实现对象的方法(抽象接口的方法 基于实现接口的方法)多态(继承)与聚合
其中多态(继承)适合于1 of N的情况,父类中封装共性部分,子类中封装可变性部分
聚合适合于M of N的情况,whole角色类封装共性部分,part角色类封装可变性部分
1、如果一个对象集之间除共性外,有超过2个的差异行为,如何处理?
做多个策略树
2、如果一个对象集的部分行为组存在差异性,如何处理?
部分行为绑定在一棵策略树
3、如果一个对象集的部分属性(以及依赖于这些属性的方法)存在差异性,如何处理?
把属性和方法做成策略树
4、如果一个对象集的一个行为需要协作对象来完成,但是它们的协作对象存在差异性,如
何处理?
将“调用协作对象”这一过程置于Strategy中,Command Pattern的变体
5、如果一个对象集的行为因为属性的取值而存在差异性,如何处理?
State Pattern
1)避免重复——只做一次:重复往往代表着耦合,修改一部分重复代码表示要修改其他的
2)DIP:Dependency Inversion Principle,依赖倒置原则,即细节应当依赖于抽象,抽象不应当依赖于细节;在要被其他模块implement的模块中定义接口,这是一种去除依赖减少耦合的基本方式
3)继承:共性和差异性
4)设计模式
中介模式
定义封装一组对象交互方式的对象;中介通过避免对象互相显式引用提升了松散耦合;让你独立的差异交互;集中控制
桥接模式
成果:解耦了接口和实现——消除了编译时依赖,改善了可扩展性,对client隐藏了实现细节
1)依赖倒置:如果抽象实体需要依赖于具体实体的话,那么为具体实体做一个接口,抽象实体可以依赖于这个接口,具体实体则实现这个接口
2) 外观模式:Faade是用户和子系统之间的一层间接,它封装子系统的内部实现并对用户提供访问接口,将这二者解耦
3) 代理模式:Proxy是用户和实际实体之间的一层间接,它负责转发用户的请求到实际实体,对实际实体进行访问控制,将这二者解耦
4) 适配器模式:应用中使用了一个接口Target,这个Target的一个实现需 要使用到一个其他实现,但是那个实现与当前的接口不一致。让Target的实现实体聚合一个Adaptee的对象,做成对象Adapter,当用户对 Adapter有请求的时候,Adapter便将请求转发给Adaptee。同时,Adapter还需要实现用户要求的但是在Adaptee没有实现的职 责,不仅仅是转发请求。(client->Adapter->Adaptee)
5) Event-Style事件风格:使用一个事件处理机制,其他模块可以向处理对象提供事件,也可以将自己的方法注册到某个事件,当这个事件发生之后,处理对象会调用注册到这个事件的所有方法,实现事件源和注册方法的解耦
1、 Observer Pattern
意图:定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于
它的对象都得到通知并被自动更新
适用场景:
(1)当对一个对象的改变需要同时改变其它对象,而不知道具体有多少对象有待改变。
(2)当一个对象必须通知其他对象,而他又不能假定其他对象是谁。换言之,你不希望这
些对象四紧密耦合的。
(3)当一个模型有两个方面,其中一个方面依赖于另一个方面。将这二者封装在独立的对
象中以使它们可以各自独立的改变和复用。
优点:
灵活性、可变性、复用性得到保证。目标与观察者之间耦合程度降低,实现抽象耦合,允许
独立的改变目标和观察者,可以复用目标对象而无需同时复用其观察者。
支持广播通信,目标者不必知道观察者是谁
缺点:
增加系统复杂程度,对于系统的理解和测试更加困难。
一个观察者的无意的更新可能引起其他观察者的意外的更新,也容易引起更更新错误,而这
种错误难以捕捉。
2、Event Style
相比Observer Pattern,可以实现对多个事件的监听。即实现对象间多对多的依赖关系。
其他特点同Observer Pattern
3、Command Pattern
意图:将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化;请求排
队或记录请求日志,以及支持可撤销的操作。
适用场景:
(1)抽象出待执行的操作以参数化某对象。
(2)在不同的时候制定、排列和执行请求。
(3)通过在实施操作之前将状态存储起来以支持撤销操作。
(4)支持修改日志,这样当系统崩溃时,这些修改可以被重做一遍。
优点:
将调用操作的对象与知道如何实现该操作的对象解耦。
可以将命令存储在栈或队列中以支持请求排队,命令处理模式维护一个历史信息。
可以容易的支持undo和redo操作,但是必须存储额外的状态信息以避免滞后影响问题。
扩展Command对象很容易。
1、简单场景:
Creator创建者:对于A、B两个对象,在以下情况下A创建B对象:A聚合了B的对象;A包含了B的对象;A记录了B对象的引用;A使用了B的对象;在A中包含初始化B对象的数据
Coupling低耦合:在A聚合、包含、记录、使用B的情况下,如果将创建B对象的职责赋予其他对象,则A需要与其他对象产生多余的耦合
Cohesion高内聚:在A中包含初始化B对象的数据的情况下,则A为信息专家,根据高内聚原则,A应当承担B对象创建的职责
2、复杂场景:
(1)场景一:仅仅一个实例允许被创建
使用SingletonPattern,首先将Constructor私有化;声明一个static private的类实例;创建一个publicstatic的getInstance方法使得外部类可以通过此方法获得此类实例。并且此方法如果用于多线程,要注意声明为protected/synchronize以保证线程安全
(2)场景二:实例个数有限制
以singleton为基础进行改进
(3)场景三:一个类不知道它所必须创建的对象的类;一个类希望有他的子类来制定它所创建的对象;类将创建对象这一职责委托给多个帮助子类中的一个,并且希望将哪一个帮助子类作为代理者这一信息局部化
Factory Method
(4)场景四:控制子类拓展,子类与父类的算法框架相同,但局部实现方法不同
Template MethodPattern
(5)场景五:多个需创建的类实例之间存在类型依赖关系
Abstract FactoryMethod
(6)场景六:实例的创建和初始化很复杂,例如运行时刻制定要实例化的类;初始化时变量值发生变更
Prototype Pattern