个人在工作一直是使用C语言从事驱动设计开发,有几年的经验,但设计上一直没有套路,希望在架构设计上更进一步。个人对面向对象的理解 目前只存在基础概念阶段,java c++也只熟悉一些最基本的语法,没有面向对象语言项目开发经验。
学习目标:1、设计模式基础概念;2、常用的几种模式;3、如何学习;4、什么场景使用;
软件设计复杂的根本原因:变化。客户需求,技术平台,开发团队,市场环境。。。
复杂性常见解决办法:1、分治:分解复杂问题到多个简单问题;2、抽象:从更高层次寻找解决核心问题的通用技术。分层分模块,模块高内聚低耦合 是软件设计 过程中解决复杂场景 的 永恒策略。
变化场景软件的设计目标:易维护、可复用、可扩展、灵活性。
就是在特定场景下解决一般设计问题的解决方案描述。
(1)依赖倒转(倒置)原则:高层模块(稳定)不应该依赖底层模块(变化),两个都应该依赖抽象(稳定),也就是依赖接口。抽象不:该依赖细节,细节应该依赖抽象。
(2)开放封闭原则:于扩展是开放的,对于修改是封闭的。通过新增补充功能,而非修改改变旧功能。
(3)单一职责原则:对于一个类而言,应该仅有一个引起他变化的原因。承担的职责越多 耦合也就越多。
(4)里氏替换原则:子类型必定能够替换他的父类,只有这样 父类才能真正被复用。
(5)最小知识原则(迪米特法则):每个类都应该尽可能降低内部成员的访问权限,并且如果两个类不必直接通信,那就避免二者通信产生耦合关系。
(6)优先使用组合而非继承:继承是白箱复用,组合是黑箱复用;某种程度破坏了封装,子类父类耦合度高,组合只要求对象具备良好的定义接口。
(7)封装变化点:使用封装来创建对象之间的分界层,让设计者可以在一侧修改而不会对另一侧产生不良影响,从而实现层次之间松耦合。
(8)针对接口编程,而不是实现编程:在设计业务的时 不将变量类型声明为特定类,而是声明接口,客户无需获知对象具体类型,只需要直到对象接口,减少依赖。
面向对象的核心机制:(1)封装:隐藏内部细节(2)继承:复用现有代码(3)多态:改写对象行为。
通过 对象创建 模式绕开new,来避免兑现创建new过程种所导致的紧耦合(依赖具体类),从而支持对象创建的稳定。将对象的部分创建工作延迟到子类或者其他对象,从而应对需求变化为对象创建时具体类型实现引来的冲击。
定义一个用于创建对象的接口,让子类决定实例化哪一类。Factory Method使一个类的实例化延迟到子类。
适用:用于隔离类对象的使用者和具体类型之间的 耦合关系。面对一个经常变化的具体类型,紧耦合关系(new)会导 致软件的脆弱。
实现:定义工厂创造对象接口product,适用ConcreteProduct为其实现接口。定义一个Creater工厂方法,可以返还一个Product对象。再定义一个ConcreteCreater来实现工厂,返回ConcreteProduct实例。
提供一个创建一系列相关或相互依赖对象的接口,而无需指定他们的具体类。
适用:当一个系统需要某种产品系列中一个来配置,同一个系列产品之间存在关联。比如数据库(包含一些列操作),当数据库从mysql变化为oracle,其一些列操作都需要变化。
实现:声明AbstractFactory创建抽象产品对象的操作接口(包含CreateProductA、CreateProductB等系列产品操作),继承产生ConcreteFactory为实现具体产品对象的操作。声明AbstractProductA\AbstractProductB为抽象的系类产品A,B产品。ConcreteProduct定义为实际具体创建的产品对象。
用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。从一个对象创建另外一个可定制的对象,而且不需要知道任何创建细节。
适用:当我们需要的对象内部状态非常复杂,而每次需要的时候 如果从头创建 这些状态不符合预期需要大量操作,而如果通过一个方法能返回一个接近我们需求对象的自身产生一个clone副本,我们可以用这个副本进行更快速操作。(相当于 通过 对指定对象进行 复制 加少量调整 得到目标对象)
实现:声明一个克隆自身的接口Prototype(包含一个克隆方法Clone可一返回自身的拷贝),ConcretePrototype实现一个可克隆的类。
将一个复杂对象的构建与他的表示分离,是得同样的构建过程(稳定)可以创建不同的表示(变化)。
适用:当创建复杂对象的算法必须独立于对象的组成部分及他们的装配方式时,封装分步创建过程细节。
实现:声明一个创建抽象接口Builder,ConcreteBuilder实现内部接口构造,Product被构造的复杂对象。
保证一个类仅有一个实例,并且提供访问他的全局访问点。
适用:某个类只能有一个实例,并且客户可以从一个众所周知的访问点访问;(或者某些类不必要引入多实例,性能浪费),比如 全局线程池 等。
实现:将构造函数定义为私有(不可被外部直接调用)、并包含一个自身对象的静态指针(全局一个唯一化),包含一个返回其实例指针的get_instance方法提供外部访问(注意)。
在软件组件的设计中,如果责任划分的不清晰,使用继承得到的结果往往是随着需求的变化,子类急剧膨胀,同时充斥着重复代码,这时候的关键是划清责任。通过类继承或者对象组合获得更灵活的结构,从而应对需求变化为对象的结构带来的冲击。
动态的给对象添加一些额外的职责,就新增功能来收,Decorator模式比生成子类更为灵活。(在职责划分不清晰的时候,过度的是使用继承来扩展对象会使子类数量急剧膨胀,引入大量重复代码)
适用:有时我们希望给某个对象而不是整个类添加一些功能;当添加的功能需要可以撤销;或者 需要给大量类同时添加这种 功能(如压缩、缓存、或者装饰组件),如果采用继承会产生大量子类。通过运行时组合一个类 替代 编译时 定义一个类。
实现:比如要给A类 B类 C类 (三者都父类都为X)某个方法func前后添加相同的附加功能next,定义一个nextDecroator类 继承 至 X,同时其内部包含一个X类指针成员作为属性,重写其func变为先调用原有base.func 然后执行新的next,其构造方法参数为X类。那么我们可以在运行时通过nextDecroator类使用A、B、C来构造出 都存在附加功能next的A B C实例。并且 我们可以持续的继续累加新的xxxDecroaror叠加更多的附加功能。
将抽象部分(业务部分)与他实现部分(平台实现)分离,使他们都可以独立的变化。
适用:现实的系统可能有多个维度分类,每个维度都可能发生变化,那么就可以把这种多角度单独分离出来,让他们独立变化,减少他们之间的耦合。
(举个例子,手机可以分为A品牌、B品牌、C品牌,不同品牌功能特性可能不同,从另一个维度 手机也可以分为 各种软件,通讯录 短信 打电话 QQ 等各种应用,不同手机的组件可能一样 也可能有差异。如果给每个手机品牌定义类,然后在给每个手机品牌应用单独定义类数量就非常多。我们可以把 手机品牌 和 应用软件 单独抽象 分开变化)
实现:定义一个Abstract抽象接口(如手机),包含一个Implementor实施者(如通用应用)指针属性。对Abstract进行继承扩充功能RefinedAbstract(如品牌A、品牌B),对Implement实现 进行继承差异实现ConcreteImplementor如a版本平台实现,b版本平台实现。我们实例化各种扩充功能RefinedAbstract(手机A,手机B)通过传入 指定的ConcreteImplementor平台实现,就可以进行组合出具备各自扩展功能,在不同平台实现的各种手机品牌。
运用共享技术有效的支持大量细粒度的对象。
适用:避免大量非常相似类的开销,通过把差异部分移出以传入方式来 达成大量类共享,减少实例数量。
实现:构建一个FlyweightFactory享元对象工厂,包含一个GetFlyweight方法(不一定时新增,也可能时现有实例的返回)提供用户获取 享元。声明一个Flyweight享元接口,继承实现ConcreteFlyweight/UnshareConcreteFlyweight;
为子系统中的一组接口提供一个一致的界面,Facade模式定义了一组高层接口,使得这个接子系统更容易适用。
适用:用稳定的接口隔离变化体(内部相互耦合比较大的一些列组件)。比如为各种不同的操作系统封装一个平台接口隔离起来,让用户可以通过平台接口访问不同的系统。而不必要和底层各种系统接口耦合起来。
实现:通过添加一个隔离层,用户不直接和复杂的子系统产生分离的耦合,而是通过更一致的隔离层良好的封装接口。
由于某种原因(开销大/权限/复杂访问/跨进程不在一个地址空间)引起不能直接访问对象,为其他对象提供一种代理以控制这个对象的访问。
适用:对应解决 开销大/权限/复杂访问/跨进程不在一个地址空间 不能直接访问,通过代理解决。
实现:声明一个subject公共接口(包含行为request),继承实现RealSubject和Proxy;
将一个类的接口转化为客户希望的另外一个接口,Adapter模式使得原本不能一起工作的类可以一起工作;
适用:系统的数据和行为都正确,但接口不符合客户需求,我们应该考虑适配器,目的是控制范围外一个原有对象与某个接口适配。但接口又与复用环境要求不一致。(双方都不太容易修改的时候再适用适配器模式)
实现:存在一个Target客户指定特定接口(包含方法request),存在一个Adaptee现存接口,继承Target得到Adapter(对Adaptee的接口与Target接口适配)
现代软件专业分工之后的第一个结果是“框架与应用程序的划分”,“组件协作”模式通过晚期绑定,来实现框架与应用程序之间的松耦合,是二者之间协作时常用的模式。通过类继承或者对象组合来划分类与对象间的职责,从而应对需求变化为多个交互的对象带来的冲击。
定义一个操作算法的骨架,将一些步骤推迟到子类实现,由子类来重写算法的特定步骤。
适用:一次性实现算法不变的部分(核心流程),将变可变的部分留给子类;使得子类可以不改变算法结构 即 可重定义特定步骤。
实现:C++中 稳定的代码写非虚函数,变化的代码 写为(protect)虚函数 提供 多态调用。
定义一系列算法单独封装,使得他们可以互换,每个算法都可以独立与他客户程序(稳定)而方便的扩展和调整(变化)。
适用:策略模式提供了条件判断语句以外的另一种选择,消除判断语句就是在解耦。
实现:定义支持所有算法的公共接口interface,Context通过适用这个接口来调用某个ConcreteStrategy定义的算法。
定义对象间的一种一(目标)对多(观察者)的依赖关系(稳定-针对关系),当一个对象状态发生变化(变化),所有依赖与他的对象都得到通知并自动更新(变化);
适用:当一个对象的改变需要同时改变其他对象,并且不知道具体有多少对象需要改变 或者 不知道需要改变对象者是谁(松耦合)。
实现:
(1)、定义一个subject类 包含一个 observer链表 属性,AttachObserver(添加观察者)/DettachObserver(删除观察者)/Notify(通报-遍历observer链表成员的Update方法)方法。observer类 包含 Update方法。
(2)、很多时候观察者之间可以差异较大,为避免继承 observer类引入观察者之间设计上耦合过多,可以把observer类 调整为委托的方式,通过构造一个事件处理 EventHandler类(类似包含一个函数指针),各种不同类型 观察者 的实例 通过 EventHandle类 把 自身方法 注册到 subject的EventHandler 列表中。对应的subject在Notify中 遍历全部 EventHandler中Update方法。
使用一个中介对象来封装一系列对象交互,中介者是对象不需要显示相互调用,从而使其耦合松散,而且可以独立的改变他们之间的交互。
适用:当一个系统中包含的大量对象之间相互连接激增,是得一个对象不可能再没有其他对象的支持下工作,对象间的交互降低了可复用性,对系统进行任何较大的改动十分困难。(误区:当系统中出现复杂的多对多交互,先分析其是否合理,不要盲目适用中介者,中介者控制了集中化,又是就把交互复杂性变为了中介者复杂性,中介者可能比任何一个类都复杂)
实现:定义一个Mediator类接口用于和Colleage对象通信,ConcreteMediator具体实现。ConcreteColleage表示同事具体体现。
允许一个对象在其内部状态改变时 改变他的行为。对象看起来像修改了他的类。
适用:当控制一个对象状态转换条件表达式过于复杂的情况,把状态的判断逻辑转移到表示不同状态的一系列类当中,可以把复杂的判断逻辑简化。好处 将状态相关的行为放入对象,由于所有与状态相关的代码都在其ConcreteState中,通过定义新的子类可以很容易添加新的状态和转换。
实现:上下文Context包含一个State类的实例指针 表示当前状态,State定义一个状态接口,ConcreteStateXXXX表示具体状态实现;每次通过State实例指针 去执行 行为(传入Context 可以访问 并修改其State)
在不破环封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样之后就可以将该对象恢复到原先保存的状态。
适用:功能比较复杂,但需要维护或者记录历史的类,或者需要保存属性只是众多属性的一小部分时,originator可以根据保存的Memento信息还原到前一状态。
实现:Originator原发器用于创建一个备忘录记录状态,适用备忘录恢复状态。Mementor类用于存储原发器状态。Caretaker负责人负责保存好备忘录,不能操作或者检查。
将对象组合层树形结构以表示“部分-整体”的层次结构,composite使得用户可以对单个对下对象和组合对象适用具有一致性。
适用:需求中是体现部分与整体层次的结构时,当用户希望可以忽略组合对象与单个对象的不同,统一的适用组合结构中所有对象时。用户不用关心处理时一个叶节点还是组合组件。
实现:Component为组合中对象声明接口,声明一个接口可以用于访问和管理子组件。继承产生Leaf在组合中表示叶子节点,继承产生Composite定义子部件的行为,存储子部件行为和子部件。
提供一种方法顺序访问一个聚合对象中各个元素,而又不需要暴露该对象的内部表示。
适用:当需要访问一个聚集对象,而且不管对象是什么都需要遍历时。
实现:声明一个Iterator定义访问和遍历元素的接口(count/add/remove/next/first),ConcreteIterator具体是是实现的迭代器。Aggregate聚合接口。ConcreteAggregate具体实现的聚合接口。
是多个对象都有机会处理请求,从而编码请求发送者和接受者耦合,将这些对象连成一条链,并沿着传递请求,直到有一个对象处理为止。
实现:Handler定义一个处理请求接口,ConcreteHandler处理它负责的请求,可以访问后继者(如果没有处理)。
将一个请求封装为类对象,从而使不同请求对客户进行参数化,对请求进行排队 或 记录日志 以及支持可撤销。
实现:command声明执行操作接口,ConcreteCommand实现命令(绑定recever和execute动作),invoker要求该命令执行某个请求,recever直到如何实施请求相关操作。
表示一个作用与某对象结构中的元素的操作,他使你可以在不改变各元素类定义前提下定义作用与这些元素的新操作
给定一个语言,定义他的文法的一种表示,并定义一个解释器,这个解释器可以适用该表示来解释语言中的句子。
设计模式很多都是利用 类中包含 XXX基类 的 实例指针,通过入参的方式 传入XXX基类继承衍生的类的实例,通过子类实例可以替换父类行为 从而实现运行时多态,实现松耦合。
1、分析问题 要全面的理解景上下文:场景上下文非常关键,设计的精髓在于“在多个相互矛盾的目标中找到权衡”,关键处上下文变一点点,都有可能造成设计方案完全不同。
2、主动寻找变化点,审视依赖关系:识别每个模式 哪些部分时稳定的,哪里时变化的。分析一个问题场景也要把他放到时间轴上去看,后面可能会引入什么变化。
3、不要刻意去套设计模式,拿着锤子找钉子 :思考当前场景违背哪些设计原则,存在哪些问题,可以通过哪些设计模式解决问题。客观的比较引入之后带来的好处和额外的复杂性哪个更大。
4、培养用工程方法分析问题,分清哪些是框架部分,哪些是应用部分,如何分解隔离。不陷入一个具体的问题解决方案,不关注太多细节,找到其核心的问题和述求。
5、良好的设计是演化的结果,重构中落地设计模式,重构的关键技法:(1)静态绑定 到 动态绑定;(2)早绑定到晚绑定;(3)继承 到 组合;(4)编译时依赖 到 运行时依赖;(5)紧耦合 到 松耦合;
设计模式价值是 通过管理变化 实现软件的可复用;(工程思想)
(1)代码可读性差 (2)需求理解浅显 (3)变化点不明确 (4)不是系统关键依赖点 (5)项目没有复用价值 (6)项目即将发布