@IT·互联网设计模式

设计模式:可复用面向对象软件的基础 分析阅读

2019-07-10  本文已影响0人  卡斯特梅的雨伞

设计模式:可复用面向对象软件的基础 分析阅读

设计模式代码参考

一句话描述设计模式(名字和意图)


img img

一些导致重新设计的一般原因,以及解决这些问题的设计模式


  1. 通过显示地指定一个类来创建对象 。

创建对象时指定类名将使我们受实现的约束,应该面向接口编程,这种时候应该间接地创建对象。

解决模式:抽象工厂、工厂方法、原型

  1. 对特殊操作的依赖。

当我们为请求指定一个特殊的操作时(炸酱面多加葱蒜不要香菜,特麻特辣),完成该请求的方式就固定下来了,这不利于代码复用,且代码死板。为避免吧请求代码写死,可以在编译或者运行时刻很方便地改变相应请求的方法。

解决模式:责任链、命令

  1. 对硬件和软件平台的依赖。(待了解)

外部的操作系统接口和应用编程接口(API)在不同的软硬件平台上是不同的。依赖于特定平台的软件将很难移植到其他平台上,这时候就需要对变化作出封装,设计系统时限制其平台相关性。

解决模式:抽象工厂、桥接

  1. 对对象表示或实现的依赖。

知道对象怎样表示、保存、定位或实现的客户在对象发生变化时可能也需要变化,对客户隐藏这些信息可以阻止连锁变化。最小知道原则。

解决模式:抽象工厂、桥接、备忘录、代理

  1. 算法依赖

算法在开发和复用时常常被扩展、优化和替代。依赖于某个特定算法的对象在算法发生变化时不得不变化。因此对于可能发生变化的算法应该被孤立起来。

解决模式:建造者、迭代器、策略、模板方法、访问者

  1. 紧耦合

紧耦合的类很难独立地被复用,因为强依赖关系。要改变或删掉一个紧耦合的类,需要理解和改变其他许多类,这样的系统难以学习、移植和维护。

松散耦合提高了一个类本身被复用的可能性,设计模式使用抽象耦合和分层技术来提高系统的松散耦合性。

解决模式:抽象工厂、命令、外观、中介者、观察者、责任链

  1. 通过生成子类来扩充功能。

通过生成子类来扩充功能会导致类爆炸;通常很难通过定义子类来定制对象,定义子类需要对父类有深入的了解(里式替换)。一般通过对象组合技术和具体的委托技术,这是继承职位组合对象的另一种灵活方法。

小细节待实践发现:过多使用对象组合会使设计难于理解。可以定义一个子类,且将它的实例和已存在实例进行组合来引入定制的功能。

解决模式:桥接、责任链、组合、装饰器、观察者、策略

  1. 不能方便地对类进行修改

当不得不改变一个难以修改的类时,如源代码类,或者该类的改变会改变许多已存在的其他子类。

解决模式:适配器、装饰器、访问者


其他:


英语技术官网及其文档、英语技术书的学习和使用、查看浏览关键点技巧等需要去下功夫学习一下,可以去知乎上看看别人的建议。

主要是两个障碍,一个的技术单词含义不懂的障碍,还有就是新知识不理解,这两个一叠加我就根本不想看了。所以一方面慢慢积累技术单词量,理解一般计算用语中单词的含义;另一方面就是加强基础,加强技术的理解和基础知识水平,方便理解新知识。

小说家和剧作家很少从头开始设计剧情,他们总是沿袭一些已经存在的模式。如悲剧性英雄模式——麦克白、哈姆雷特。

每一个模式都描述了一个在我们周围不断重复发生的问题,以及该问题的解决方案的核心。——Alexander

城市和建造模式。


待了解


第一章属于理论部分,得回过头来重新看看。

设计模式的理解将使我们拥有非同寻常的思考面向对象设计能力,帮助我们设计出更加灵活、模块化、可复用、易理解的软件。

一个模式有4个基本要素:

  1. 模式名称 。 描述了模式的问题、解决方案和效果。
  2. 问题 。 描述了在何时使用模式。
  3. 解决方案 。描述了设计的组成成分,他们之间的相互关系及各自的职责和协作方式。
  4. 效果 。描述了模式应用的效果及使用模式应权衡的问题。

设计模式用来在特定场景下解决一般设计问题的类和相互通信的对象的描述。

模式分类

模式依据目的可以分为创建型、结构型、行为型三种。创建型模式与对象的创建有关;结构型模式处理类或对象的组合;行为型模式对类或对象怎样交互和怎样分辨职责进行描述。

模式依据范围准则指定模式主要用于类还是用于对象。

面向对象设计最困难的部分是将系统分解成对象集合。需要考虑的因素:封装、粒度、依赖关系、灵活性、性能、演化、复用等。

设计中的抽象对于产生灵活的设计是至关重要的。

针对接口编程,而不是针对实现编程。

当我们不得不在系统的某个地方实例化具体的类时,用创建型模式(6种)来实例化可以确保系统是裁员针对接口的方式编程的。

面向对象系统中功能复用的最常见两种技术是类继承和对象组合。

优先使用对象组合,少用类继承。

因为继承对子类揭示了父类的实现细节,所以继承常被认为破坏了封装性,且父类实现中的任何变化必然会导致子类发生变化。继承如果不适用则在重写后会导致继承关系限制了灵活性并最终限制了复用性。比较好的解决方法是只继承抽象类,因为抽象类通常提供较少的实现。

怎么选择设计模式

  1. 考虑设计模式是怎样解决设计问题的
  2. 浏览模式的意图部分
  3. 研究模式怎样互相关联
  4. 研究目的相似的模式
  5. 检查重新设计的原因
  6. 考虑设计中哪些是可变的。这与关注引起重新设计的原因刚好相反,这类要考虑的是我们想要什么变化却又不好引起重新设计,是封装变化的概念,下来列出设计模式允许我们使用时可以独立了变化的方面,改变它们不会导致重新设计。
img

怎样使用设计模式

  1. 大致浏览一遍模式。注意适用性和效果部分,确定它适合你的问题。
  2. 研究结构、参与者和协作部分。确保能理解这个模式的类和对象以及他们是怎么样关联的。
  3. 看代码示例。
  4. 选择模式参与者的名字,使他们在应用上下文中有意义。即使用了什么模式的类就用模式名命名该类。(###Factory、###Strategy
  5. 定义类。申明接口,建立继承关系。
  6. 定义模式中专用于应用的操作名称。
  7. 实现执行模式中责任和协作的操作。

总结

设计模式的使用限制要特别注意:设计模式不能够随意使用。

通常在通过引入额外的间接层次获得灵活性和可变性的同时,也使设计变得更负责并/或牺牲了一定的性能。

一个设计模式只有当它提供的灵活性是真正需要的时候,才有必要使用。

衡量一个模式的得失时,通过他的效果部分是最能提供帮助的。

UML学习

抽象类的主要目的是为它的子类定义公共接口,非抽象类称为具体类。

img img img img

第二章实践需重新看一遍

创建型模式

创建型模式抽象了实例化过程。

创建迷宫

抽象工厂


意图:

提供一个创建一系列相关或相互依赖对象的接口,而无需指定他们具体的类。

适用性:

效果

优点:

结构:

img

相关模式:

抽象工厂类通常用工厂方法实现,也可以用原型实现。

一个具体的工厂通常是一个单例。

建造者


意图:

将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。

适用性:

效果

优点:

结构:

img img

相关模式:

抽象工厂和建造者相似,都可以创建复杂的对象。区别是创建者模式着重于一步步构造一个复杂对象,而抽象工厂着重于多个系列的产品对象。建造者在最后一步返回产品,而抽象工厂的产品是立即返回的。

组合模式通常是用建造者模式生成的。(疑问?)

工厂方法


意图:

定义一个用于创建对象的接口,让子类决定实例化哪一个类。工厂方法使一个类实例化延迟到其子类。

适用性:

效果

优点:

结构:

img

相关模式:

抽象工厂经常用工厂方法实现。

工厂方法通常在模板方法中被调用。

工厂方法与原型的区别:原型不需要创建工厂Creator的子类,但原型通常要求一个针对Product类的初始化操作。原型Creator使用Initialize来初始化对象,而工厂方法不需要这样的操作。

原型(最简单的就是要被复杂创建的类加个Clone方法)


意图:

用原型实例指定创建对象的种类,并通过拷贝这些原型创建新的对象。

适用性:

先前提:当一个系统应该独立于它的产品创建、构成和表示时,使用原型模式;且满足以下条件之一:

效果

优点:

浅拷贝和深拷贝问题:

结构:

img

相关模式:

//原型的使用 org.springframework.beans.BeanUtils
BeanUtils.copyProperties(repayOverview,re);

单例


意图:

保证一个类仅有一个实例,并提供一个访问它的全局访问点。

适用性:

效果

优点:

结构:

img

相关模式:

很多。

创建型模式讨论

一般一开始会使用工厂方法模式,如果需要更复杂或者对应的变化时再改用其他创建型模式。

结构型模式

结构型模式涉及到如何组合类和对象以获得更大的结构。结构型类模式采用继承机制来组合接口或实现。

适配器


意图:

将一个类的接口装换成客户希望的另外一个接口。适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。

适用性:

效果

对象适配器优点:

结构:

img

相关模式:

桥接的结构与对象适配器类似,但桥接模式的出发点不同:桥接的目的是将揭开部分和实现部分分离,从而对它们较为容易也相对独立的加以改变,而适配器则是为了适配去改变一个已有对象的接口。

装饰器模式增强了装饰对象的功能且不改变装饰对象的接口。因此装饰器对应用程序的透明性比适配器要好。装饰器支持递归组合,而适配器不支持实现这一点。

代理模式在不改变他的接口的条件下,为另一个对象定义了一个代理,与适配器的意图不同。

桥接


意图:

将抽象部分与他的实现部分分离,使他们都可以独立得变化。

适用性:

效果

优点:

结构:

img

相关模式:

抽象工厂模式可以用来创建和配置一个特定的桥接模式。

桥接与适配器的区别:适配器模式用来帮助需要适配的类协同工作,它通常在系统设计完成后不得已为了适配等才会被用到,而桥接则在系统开始时就被使用,它使得抽象接口和实现部分可以独立进行改变。

组合


意图:

将对象组合成树形结构以表示“部分--整体”的层次结构。组合使得用户对单个对象和组合对象的使用具有一致性。

组合模式描述了如何使用递归组合,使得用户不必对组合和个体的类进行区别对待。

视图组件、财经应用中的资产组合中经常使用。

适用性:

效果

优点:

结构:

img

相关模式:

通常部件-父部件连接用于责任链模式。(疑问?)

装饰器模式经常与组合模式一起使用。当装饰和组合一起使用时,他们通常有一个公共的父类。因此装饰必须支持具有Add / Remove / GetChild 操作的Component 接口。

享元让你共享组件,但不再能引用他们的父部件。(疑问?)

迭代器可以用来遍历组合。

访问者模式将本来应该分布在组合和叶节点的操作和行为局部化。(局部化什么意思?)

装饰器


意图:

动态地给一个对象添加一些额外的职责。就增加功能来说,装饰器模式相比生成子类更为灵活。

适用性:

效果

优点:

使用注意

结构:

img

相关模式:

装饰器与适配器的区别:装饰器增加对象的职责而不改变他的接口;适配器将给对象一个全新的接口。

策略和装饰器的区别:装饰器可以改变对象的外壳;而策略使你可以改变对象的内核,这是改变对象的两种途径。

外观


意图:

为子系统中的一组接口提供一个一致的界面,外观模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。

适用性:

效果

优点:

结构:

img

相关模式:

抽象工厂模式可以与外观模式一起使用以提供一个接口,这一接口可用来以一种子系统独立的方式创建子系统对象。抽象工厂也可以代替外观模式隐藏那些与平台先关的类。

中介者与外观的相似处:他们都抽象了一些已有的类的功能。区别:中介者的目的是对同事之间的任意通讯进行抽象,通常集中不属于任何单个对象的功能。中介者的同事对象知道中介者并与它通信,而不是直接与其它同类对象通信。而外观模式仅对子系统对象的接口进行抽象,从而使他们更容易使用,它并不定义新功能,子系统也不知道外观的存在。

外观对象通常仅需要一个,一般用单例构造。

享元


意图:

运用共享技术有效地支持大量细粒度的对象。

例子:文档编辑器中字母的对象共享。

适用性:

注意:

效果

优点:

结构:

img img

相关模式:

享元模式经常与组合模式结合起来。

最好用享元实现状态模式和策略模式对象。

代理


意图:

为其他对象提供一种代理以控制对这个对象的访问。

适用性:

效果

优点:

结构:

img img

相关模式:

适配器与代理的区别:市北区是为它所适配的对象提供一个不同的接口;而代理提供的是与它的实体相同的接口。

装饰器与代理的区别:虽然两者实现结构相似,但两者目的不同。装饰器是为对象添加一个或多个功能,而代理则控制对对象的访问。更具体一点如远程代理并不包含对实体的直接引用,而是间接的,跟装饰器包含被装饰对象的引用不同,而虚拟代理则一开始并没有引用,而是在被调用时才创建真实的引用。

结构型模式讨论

适配器和桥接的区别:相同点:他们都给另一对象提供了一定程度上的间接性,因而有利于系统的灵活性。

不同点:它们的用途不一样。适配器注意解决两个已有接口之间不匹配的问题。桥接则对抽象接口与它的实现部分进行桥接。因此它们两常用于软件生命周期的不同阶段。适配器在开发后;桥接在设计类之前实施。它们两针对了不同的问题,没有好差之分。

外观与适配器的区别:外观定义了一个新的接口,而适配器则复用一个原来的接口,适配器使两个已有的接口协同工作,而不是定义一个全新的接口。

装饰和组合的区别:装饰旨在使你能够不需要生成子类即可给对象添加职责。而组合旨在构造类,使多个相关的对象能够以统一的方式处理,多重对象可以被当做一个对象来处理,它的重点不在于修饰,而在于表示。

装饰与代理的区别:两种都描述了怎样为对象提供一定程度上的间接引用。但目的不同,代理是用于当直接访问一个实体不方便或不符合需要时,为这个实体提供一个代替这,而装饰器是为了提供功能,装饰器适用于编译时不能(至少不方便)确定对象的全部功能的情况下使用。这种开放性使得递归组合成为装饰器模式中一个必不可少的部分;而代理模式不一样,他强调的是一种关系,即代理与他的实体之间的关系,这种关系可以静态的表达。

行为型模式

职责链


意图:

使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。

例子:在线帮助系统;许多类库使用职责链模式处理用户事件。

适用性:

效果

优点:

结构:

img

相关模式:

职责链常与组合一起使用。这种情况下,一个构件的父构件可作为它的后继。

命令

意图:

将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可撤销的操作。

适用性:

效果

优点:

结构:

img

相关模式:

组合模式可被用来实现宏命令。

备忘录模式可以用来保持某个状态,命令用这一状态来取消他的效果。如删除命令执行后的取消操作,需要复原被删除的对象信息。

解释器


意图:

给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子。

适用性:

效果

优点:

结构:

img

相关模式:

组合模式:抽象语法树是一个复合模式的实例。

享元模式:说明了如何在抽象语法树中共享终结符。

迭代器模式:解释器可用一个迭代器遍历该结构。

访问者模式:可用来在一个类中维护抽象语法树中的各节点的行为。

迭代器


意图:

提供一种方法顺序访问一个聚合对象中的各个元素,而又无需暴露该对象的内部表示。

适用性:

效果

优点:

当客户来控制迭代时,该迭代器称为一个外部迭代器,而当由迭代器控制跌打时,该迭代器称为一个内部迭代器。外部迭代器比内部迭代器更灵活。如比较两集合是否相等时。

一个健壮的迭代器保证插入和删除操作不会干扰遍历,且不需要拷贝该聚合。做法是:大多数需要想这个聚合注册该迭代器。?当插入或删除元素时,该聚合要么调整迭代器的内部状态,要么在内部维护额外的信息以保证正确的遍历。

空迭代器(NullIterator)使得我们更容易遍历属性结构的聚合。当遇到聚合元素时将返回一个具体的迭代器,而叶节点元素返回NullIterator实例,使得我们可以用一种统一的方式实现在整个树形结构上的遍历。

结构:

img

相关模式:

组合模式:迭代器常被应用到像复合这样的递归结构上。

工厂方法:多态迭代器靠工厂方法来实例化适当的迭代器子类。

备忘录:常与迭代器一起使用。迭代器可使用一个备忘录来捕获一个迭代的状态。迭代器在其内部存储备忘录。

中介者


意图:

用一个中介对象来封装一系列的对象交互。中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。

适用性:

效果

优点:

实现:

1.可以通过使用观察者模式,将中介者实现为一个观察者,各个Colleague 作为目标对象,一旦目标对象状态改变就发送通知给中介者,中介者做出相应将状态改变的结构传播给其他的Colleague。

2.另一个方法是在中介者中定义一个特殊的通知接口,各个Colleague在通信时直接调用该接口。

结构:

img

相关模式:

外观模式与中介者的不同之处在于它是对一个对象子系统进行抽象,从而提供了一个更为方便的接口。他的协议是单向的,即外观对象对这个子系统类提出请求,反之则不行。而中介者提供了各个Colleague对象不支持或不能支持的协作行为,而且协议是多向的。

Colleague 可使用观察者模式与中介者通信。

备忘录


意图:

在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到原先保存的状态。

适用性:

效果

优点:

结构:

img

相关模式:

命令模式可以用备忘录来为可撤销的操作维护状态。

备忘录可用于迭代。待理解

观察者


意图:

定义对象间的一种一对多的依赖关系,当一个对象的状态发送改变时,所以依赖于它的对象都得到通知并被自动更新。

适用性:

效果

优点:

使用时注意:

  1. 创建目标到其观察者之间的映射。一个目标对象跟踪它应通知的观察者的最简单的方法是显示地在目标中保存对它们的引用。但是当目标很多而观察者很少时,存储代价太高,可以改为用时间换空间的方法,即用一个管理查找机制,例如hash表来维护目标到观察者的映射。这样没有观察者的目标就不产生存储开销。但缺点是这方法增加了访问观察者的开销。
  2. 观察多个目标。如一个表格对象可能依赖于多个数据源。这是必须扩展update接口来使观察者知道是哪一目标送来的通知。如可以将目标对象自己作为参数传给update接口。
  3. 谁触发更新。有两种,一种是有目标对象的状态设定操作在改变目标对象的状态后自动调用Notify,优点是不需要客户端调用Notify,但如果有多个设置状态操作会产生多次更新,效率较低。第二种是让客户自己在适当的时候调用Notify,这样就避免了不必要的中间更新,但缺点是增加了客户端的触发更新责任,如果忘记容易出错。
  4. 避免特定于观察者的更新协议--推拉模型。a)目标想观察者发送关于改变的详细信息,而不管它们需要与否,这种为推模型。b)目标除最小通知外饰面也不送出,而在此之后由观察者显示地想目标询问细节,这种为拉模型。拉模型强调的是目标不知道他的观察者,而推模型假定目标制定一些观察者的需要的信息。推模型可能使得观察者相对难以复用,因为目标对观察者的假定可能并不总是正确的。(除非确定就是这种类型信息)》》》拉模型的缺点是效率较差,因为观察者需要在没有目标对象帮助的情况下确定是吗改变了。

结构:

img

相关模式:

中介者:通过封装复杂的更新语义,ChangeManager充当目标和观察者之间的中介者。

单例:ChangeManager可使用单例模式来保证它是唯一的并且是可全局访问的。

状态


意图:

允许一个对象在其内部状态改变时改变它的行为。对象看起来似乎修改了它的类。

例子:画图编辑器,通过使用状态模式来根据当前的工具改变编辑器的行为。

适用性:

效果

优点:

结构:

img

例子图表:

img

相关模式:

享元模式解释了何时以及怎样共享状态对象。

状态对象通常是单例的。

策略


意图:

定义一系列的算法,把它们一个个封装起来,并且使它们可互相替换。本模式使得算法可独立于使用它的客户而变化。

适用性:

效果

优点:

结构:

img

相关模式:

享元:策略对象经常是很好的轻量级对象。

模板方法


意图:

定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。

适用性:

效果

优点:

钩子操作:它提供了缺省的行为,子类可以在必要时进行扩展,一个钩子操作在缺省操作中通常是一个空操作。

模板方法应该指明哪些操作是钩子操作(可以被重定义)以及哪些是抽象操作(必须被重定义)。

要有效地重用一个抽象类,子类编写者必须明确了解哪些操作是设计为有待重定义的。一般就是抽象方法。子类可以通过重定义父类的操作来扩展该操作的行为,扩展行为时还可显式地调用父类操作

注意模板方法命名最好加个约定,如Do-

结构:

img

相关模式:

工厂方法:常被模板方法调用。

策略:模板方法使用继承来改变算法的一部分。策略使用委托来改变整个算法

访问者


意图:

表示一个作用于某对象结构中的各元素的操作。它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作。

实现目标:可以独立地增加新的操作,并且是这些结点类(元素)独立于作用于其上的操作两个目标。

注意:使用访问者模式,必须定义两个类层次:一个对应于接收操作的元素(Node层次),另一个对应于定义对元素的操作的访问者(Node Visitor层次)。这样给访问者类层次增加一个新的子类就可创建一个新的操作。特别要注意的是Node层次是不变的,即不需要增加性的Node子类的前提下使用访问者。

适用性:

效果

优点:

双分派:

如果一个语言支持双分派或多分派,如CLOS,那么就不需要访问者模式了。

单分派语言通过访问者模式实现双分派技术。技术核心是通过我们的实现一个请求操作取决于两个方面:该请求的名和接收者的类型。双分派意味着得到执行的操作决定于请求的种类和两个接收者的类型。如Element中的Accept方法就是一个双分派操作。它的含义决定于两个类型:Visitor 的类型和Element的类型。双分派可以对每一类元素请求不同的操作。

访问者模式的关键在于:得到执行的操作不仅决定于访问者的类型还决定于它访问的元素的类型。

结构:

img

相关模式:

组合:访问者可以用于对一个由组合模式定义的对象结构进行操作。

解释器:访问者可以用于解释。

行为模式讨论

总结

设计模式是一套通用的设计词汇,方便技术人员交流沟通使用。

更好的理解现有的面向对象系统,因为里面用到了多种设计模式。

更好地运用设计模式重构。

建筑模式语言

Christopher Alexander :以一种松散的方式把一些模式串接在一起来建造建造是可能的。这样的建造仅仅是一些模式的对其,而不紧凑。这不够深刻。然而另外一种组合模式的方式,许多模式重叠在同一个物理空间里:这样的建造非常紧凑,在一小块空间里继承了许多内涵;由于这种紧凑,它变得深刻。

编程就是用最紧凑的代码,最极限的方式,最 大限度地实现功能。


其他知识补充:

多态时,编译看左边,运行看右边。看下面例子解释:

class Animal;
class Cat;
Animal a = new Cat();
//假设 Animal 和 Cat 都有 clone()方法 、name 实例变量;Cat 自己有miaomiao();方法  
//则运行时 a.clone()调用的是Cat的方法(子类多态)而a.name 调用的是Animal的实例变量,因为实例变量不具备多态,如果想要拿到子类Cat的实例变量,则可以用强转方式把(Cat)a.name来获得子类的实例变量。
//如果调用a.miaomiao()会报错,因为Animal没有该方法,不能实现多态,会报编译失败。

上一篇 下一篇

猜你喜欢

热点阅读