设计模式介绍
- 什么是设计模式
模式的广义描述:“每一个模式描述了一个不断重复发生的问题,以及该问题的解决方案的核心。这样,就能一次又一次地使用该方案而不必做重复劳动”。
面向对象设计模式的描述:“被用来在特定场景下解决一般设计问题的类和相互通信的对象的描述”。
既适用于面向对象的开发,也适用于面向过程的开发。
基本要素
- 模式名称(pattern name):一个助记名,它用一两个词来描述模式的问题、解决方案和效果。模式名可以帮助我们思考,便于我们与其他人交流设计思想及设计结果。
- 问题(problem):描述了应该在何时使用模式。解释设计问题和问题存在的前因后果,一般会包括使用模式必须满足的一系列先决条件(语境)。
确定一个模式可能应用到的所有场景,不论是一般的还是特殊的,实际上是不可能的。一个更实际的方法是列出特殊模式关注问题可能出现的所有已知场景。这并不保证包括与一个模式相关的所有场景,但至少它给出了有价值的指导。
- 解决方案(solution):描述了设计的组成成分,它们之间的相互关系及各自的职责和协作方式。
因为模式就像一个模板,可应用于多种不同场合,所以解决方案并不描述一个特定而具体的设计或实现,而是提供设计问题的抽象描述和怎样用一个具有一般意义的元素组合(类或对象组合)来解决这个问题。
- 效果(consequences) 描述了模式应用的效果及使用模式应权衡的问题。
模式效果包括它对系统的灵活性、扩充性或可移植性等方面的影响,显式地列出这些效果对理解和评价这些模式很有帮助。
两个概念
组件(component):是软件系统的一个封装部分。组件有一个接口。组件可表示为模块、类、对象或是一组相关函数。
框架( Framework ):是构成一类特定软件可复用设计的一组相互协作的类。框架规定了应用的体系结构。定义了整体结构,类和对象的分割,各部分的主要责任,类和对象怎么协作,以及控制流程,框架更强调设计复用。
推荐书籍:
“Design patterns:Elements of Reusable Object-Oriented Software ”,by Gamma,Helm,Johnson&Vlissides,1994。(设计模式:可复用面向对象软件的基础)
“Pattern-Oriented Software Software Architecture Volume 1:A System of Patterns”by Buscmann,Meunier,Rohnert,Sommerlad,Stal,1996。(面向模式的软件体系结构 卷1:模式系统)
- 设计模式的主要目标
设计模式的主要目标是支持高质量的软件系统开发,所谓“高质量”是指系统既能实现其功能需求又实现其非功能需求,尤其强调非功能需求的重要性。
功能属性(functional property)
用来处理系统功能性的特定方面,并且通常与特定的功能需求相关。功能特性可以通过特定的功能使用户直接可看到应用程序,也可以通过它的实现来描述,例如用来计算功能的算法。
非功能属性(non-functional property)
定义了未被功能属性描述覆盖的系统特征。非功能属性通常解决与一个软件系统的可靠性、兼容性、开销、易用性、维护或者开发有关的方面。开发者过去习惯于专心给软件提供一定的功能属性,而今天,非功能属性变得越来越重要。
易修改性:
- 可维护性。主要体现在问题的修复上:在错误发生后“修复”软件系统。为可维护性做好准备的软件体系结构往往能做局部性的修改并能使对其他组件的负面影响最小化。
- 可扩展性。关注的是使用新特性来扩展软件系统,以及使用改进版本来替换组件并删除不需要或不必要的特性和组件。为了实现可扩展性,软件系统需要松散耦合的组件。其目标是实现一种结构,它能使你在不影响组件客户的情况下替换组件。支持把新组件集成到现有的体系结构中也是必要的。
- 结构重组。这一点处理的是重新组织软件系统的组件及组件间的关系,例如通过将组件移动到一个不同的子系统而改变它的位置。为了支持结构重组,软件系统需要精心设计组件之间的关系。理想情况下,它们允许在不影响实现的主体部分的情况下灵活地配置组件。
• 可移植性。它使软件系统适用于多种硬件平台、用户界面、操作系统、编程语言或编译器。为了实现可移植,需要按照硬件无关的方式组织软件系统,其他软件系统和环境被提取出来放到特定的组件(如系统和用户界面库)中。
注:文档的重要性。通过采用准确的文档,在引入更改时保留体系结构,认真评审和为更改预留空间的方式来防止软件老化所带来的开销。
互操作性:
作为系统组成部分的软件不是独立存在的,它经常与其他系统或自身环境相互作用,为了支持互操作性,软件体系结构必须为外部可视的功能特性和数据结构提供精心设计的软件入口。程序和用其他编程语言编写的软件系统的交互作用就是互操作性的问题,这种互操作性也影响应用的软件体系结构。代理者模式可能是模式解决互操作性问题的最明显的例子。
效率:
效率为软件的执行而处理可获得资源的使用问题,并考虑它是如何影响响应时间、吞吐率和存储开销的。效率不仅仅是使用复杂算法的问题。对组件及其耦合来说,恰当地分配责任是在给定的应用程序中为提高效率而执行的重要的体系结构活动。
可靠性:
可靠性是软件系统在应用或系统错误面前,在意外或错误使用的情况下维持软件系统的功能特性的基本能力。可靠性可以分为两个方面:
- 容错:其目的是在错误发生时确保系统正确的行为,并进行内部“修复”。例如在一个分布式软件系统中失去了一个与远程组件的连接,接下来恢复了连接。在修复这样的错误之后,软件系统可以重新或重复执行进程间的操作直到错误再次发生。
• 健壮性:这里说的是保护应用程序不受错误使用和错误输入的影响,在遇到意外错误事件时确保应用系统处于已经定义好的状态。值得注意的是,和容错相比,健壮性并不是说在错误发生时软件可以继续运行—它只能保证软件按照某种已经定义好的方式终止执行。
可测试性:
软件系统需要从其体系结构上得到支持以减轻对其正确性的评估。支持可测试性的软件结构可以更好地进行错误检测和修复,也可以临时性地集成正在调试的代码和正在调试的组件。
例如,命令处理器模式通过记录日志和重现用户命令对象,促进了用户交互层次的可测试性。
可重用性:
可重用性是目前软件工程领域中讨论最热门的话题之一,能够减少开发软件系统的费用和时间,并且能够开发出更高质量的软件。可重用性有两个主要方面—使用重用进行软件开发和为重用进行软件开发:
- 使用重用进行软件开发:是指重用现有的组件和来自以前项目或商业库、设计分析、设计说明或代码组件的结果。这些可重用的人工制品将稍做修改或不做任何修改集成到正在开发的应用程序中去。
- 为重用进行软件开发的重点集中在产生那些既是目前软件开发的一个组成部分,又有可能在未来项目中重用的组件。这要求软件体系结构允许自包含部件,这样不仅正在开发的应用中可用而且在其他系统内重用时不需要做很大的修改。
尽管模式不能明确地解决可重用性的问题,但是几乎每种支持易修改性的模式也支持可重用性。
非功能属性可能在相互补充的同时又彼此矛盾。当为软件体系结构定义非功能需求时,需要仔细考虑它们之间彼此依赖又相互独立的关系。同时你也需要列出不同非功能需求之间的优先级清单,定义在发生冲突时优先考虑的需求。
非功能属性是难以度量的。估算软件体系结构实现一个给定非功能属性的程度仍然主要依赖于软件工程师的经验。
- 软件设计的几个基本原理(启用技术)
设计模式是明确建立在用来构造定义良好的软件系统的启用技术的基础上。
抽象:
对象的基本特性,这种特性将对象和所有其他类型的对象区分开,并因此提供清晰定义的相对于观众观点的概念性边界。 用“组件”替换“对象”可以获得抽象更普通的定义。包括结构抽象、行为抽象、关联关系抽象。
封装:
封装将构成抽象结构和行为的抽象元素分组,并且把不同的抽象彼此分开。封装提供抽象之间的清晰界限。
信息隐藏:
信息隐藏涉及对客户隐藏组件的实现细节,以便更好地处理系统复杂性并且使组件之间的耦合减少到最小。信息隐藏是软件工程领域内基本和最重要的原理之一。
模块化:
模块化涉及到对软件系统有意义的分解,并且将其分成子系统和组件。模块化的主要目标是通过在程序中引入定义良好的且经过证实的边界来处理系统复杂性。模块化与封装原理密切相关。解决模块性的模式例子包括层模式、管道和过滤器模式以及整体-部分模式。
事务分离:
在软件系统内不同的或无关的责任应该彼此分离。几乎每一个模式都以某种方式应用了这一基本原理。
例:管道和过滤器(Pipes and Filters)、
低耦合度:
耦合度是一个组件与其他组件关联、知道其他组件的信息或者依赖其他组件的强弱程度的度量。强耦合使一个系统错综复杂,因为当一个组件与其他组件紧密相连时它会难以理解、改变或更正。可以通过设计系统组件之间的弱耦合来降低复杂度。
聚合度:
聚合度是对一个类(模块)中的各个职责之间相关程度和集中程度的度量。一个具有高度相关职责的类,并且这个类所能完成的工作量不是特别巨大,那么就具有高聚合度。低聚合度将导致难以理解、难以重用、难以维护。
策略和实现的分离:
软件系统的组件应该处理策略或实现,但并非要求一个组件同时处理这两方面的需求:
• 策略(policy)组件用于处理对语境敏感的决定。
• 实现(implementation)组件用于处理一种说明非常完全的算法的执行,在这个算法中不需要作出对语境敏感的决定。
接口和实现的分离:
任何组件都应该由两部分组成:
• 一是接口部分,它定义了由组件提供的功能特性并说明怎样使用它。
• 二是实现部分,它包括由组件为功能特性提供的实际代码。
这个原理的主要目标是保护使用组件的客户免受实现细节的困扰。接口和实现的分离可使用桥接模式来解决。
分而治之:
把一项任务或者组件分成可被独立设计的更小部分。
- 设计模式分类
第一是目的准则,即模式是用来完成什么工作的。模式依据其目的可分为创建型(Creational)、结构型(Structural) 、或行为型(Behavioral)三种。创建型模式与对象的创建有关;结构型模式处理类或对象的组合;行为型模式对类或对象怎样交互和怎样分配职责进行描述。
第二是范围准则,指定模式主要是用于类还是用于对象。类模式处理类和子类之间的关系,这些关系通过继承建立,是静态的,在编译时刻便确定下来了。对象模式处理对象间的关系,这些关系在运行时刻是可以变化的,更具动态性。
|
|
| 目的 |
|
|
| 创建型 | 结构型 | 行为型 |
范围 | 类 | Factory Method(3.3) | Adapter (类) (4 .1 ) | Interpreter(5.3 ) Template Method(5.10) |
| 对象 | Abstract Factory(3.1) Builder(3.2).Prototype(3.4) Singleton(3.5) | A d a p t e r (对象) ( 4 . 1 ) B r i d g e ( 4 . 2 ) C o m p o s i t e ( 4 . 3 ) D e c o r a t o r ( 4 . 4 ) F a c a d e ( 4 . 5 ) F l y w e i g h t ( 4 . 6 ) P r o x y ( 4 . 7 ) | Chain of Responsibility(5.1) C o m m a n d ( 5 . 2 ) I t e r a t o r ( 5 . 4 ) M e d i a t o r ( 5 . 5 ) M e m e n t o ( 5 . 6 ) O b s e r v e r ( 5 . 7 ) S t a t e ( 5 . 8 ) S t r a t e g y ( 5 . 9 ) Vi s i t o r ( 5 . 1 0 ) |
第三种分类:体系结构模式、设计模式、惯用法。
体系结构模式: 可作为具体软件体系结构的模板。它们规定一个应用的系统范围的结构特性,以及对其子系统的体系结构施加的影响。所以体系结构模式的选择是开发一个软件系统时的基本设计决策。
设计模式(design pattern): 提供一个用于细化软件系统的子系统或组件,或它们之间关系的图式。设计模式是中等规模的模式。它们在规模上比体系结构模式小,但又独立于特定编程语言或编程范例。
惯用法(idiom):是具体针对一种编程语言的低层模式。惯用法描述如何使用给定语言的特征来实现组件的特殊方面或它们之间的关系。
体系结构模式可以用在大粒度设计的开始,设计模式可以用在整个设计阶段,惯用法可以用在实现阶段。
- 几个常用的设计模式
FACTORY METHOD(工厂方法)—对象创建型模式
1. 意图
定义一个用于创建对象的接口,让子类决定实例化哪一个类。Factory Method使一个类的实例化延迟到其子类。
2. 结构
•Product
— 定义工厂方法所创建的对象的接口。
•ConcreteProduct
— 实现Product接口。
• Creator
— 声明工厂方法,该方法返回一个Product类型的对象。Creator也可以定义一个工厂方法的缺省实现,它返回一个缺省的ConcreteProduct对象。
— 可以调用工厂方法以创建一个Product对象。
• ConcreteCreator(MyApplication)
— 重定义工厂方法以返回一个ConcreteProduct实例。
3. 适用性:
• 当一个类不知道它所必须创建的对象的类的时候。
• 当一个类希望由它的子类来指定它所创建的对象的时候。
4. 协作
• Creator依赖于它的子类来定义工厂方法,所以它返回一个适当的ConcreteProduct实例。
5. 效果
工厂方法不再将与特定应用有关的类绑定到你的代码中。代码仅处理Product接口;因此它可以与用户定义的任何ConcreteProduct类一起使用。
工厂方法的一个潜在缺点在于客户可能仅仅为了创建一个特定的ConcreteProduct对象,就不得不创建Creator的子类。
- 实现
1 ) 主要有两种不同的情况- 第一种情况是,Creator类是一个抽象类并且不提供它所声明的工厂方法的实现。
- 第二种情况是,Creator是一个具体的类而且为工厂方法提供一个缺省的实现。
2 ) 参数化工厂方法该模式的另一种情况使得工厂方法可以创建多种产品。工厂方法采用一个参数标识要被创建的对象种类。
一个参数化的工厂方法具有如下的一般形式,此处MyProduct和YourProduct是Product的子类:
重定义一个参数化的工厂方法使你可以简单而有选择性的扩展或改变一个Creator生产的产品。你可以为新产品引入新的标识符,或可以将已有的标识符与不同的产品相关联。例如,子类MyCreator可以交换MyProduct和YourProduct并且支持一个新的子类TheirProduct:
注意这个操作所做的最后一件事是调用父类的Create。这是因为MyCreator::Create仅在对YOURS、MINE和THEIRS的处理上和父类不同。
3 ) 使用模板以避免创建子类,提供Creator的一个模板子类,它使用Prduct类作为模板参数:
使用这个模板,客户仅提供产品类—而不需要创建Creator的子类。
ABSTRACT FACTORY(抽象工厂)—对象创建型模式
1. 意图
提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。
2. 结构
• AbstractFactory
— 声明一个创建抽象产品对象的操作接口。
•ConcreteFactory
— 实现创建具体产品对象的操作。
• AbstractProduct
— 为一类产品对象声明一个接口。
• ConcreteProduct
— 定义一个将被相应的具体工厂创建的产品对象。
— 实现AbstractProduct接口。
• Client
— 仅使用由AbstractFactory和AbstractProduct类声明的接口。
3. 适用性:
• 一个系统要独立于它的产品的创建、组合和表示时。
• 一个系统要由多个产品系列中的一个来配置时。
• 当你要强调一系列相关的产品对象的设计以便进行联合使用时。
• 当你提供一个产品类库,而只想显示它们的接口而不是实现时。
4. 协作
• 通常在运行时刻创建一个ConcreteFactroy类的实例。这一具体的工厂创建具有特定实现的产品对象。为创建不同的产品对象,客户应使用不同的具体工厂。
• AbstractFactory将产品对象的创建延迟到它的ConcreteFactory子类。
- 效果
Abstract Factory模式有下面的一些优点和缺点:
1) 分离了具体的类:一个工厂封装创建产品对象的责任和过程,它将客户与类的实现分离。
2) 使得易于交换产品系列:只需改变具体的工厂即可使用不同的产品配置,这是因为一个抽象工厂创建了一个完整的产品系列。
3) 有利于产品的一致性:当一个系列中的产品对象被设计成一起工作时,一个应用一次只能使用同一个系列中的对象,这一点很重要。
4) 难以支持新种类的产品:难以扩展抽象工厂以生产新种类的产品。这是因为Abstract Factory接口确定了可以被创建的产品集合。支持新种类的产品就需要扩展该工厂接口,这将涉及Abstract Factory类及其所有子类的改变。
6. 实现
1) 将工厂作为单件:一个应用中一般每个产品系列只需一个Concrete Factory的实例。因此工厂通常最好实现为一个Singleton。
2) 创建产品Abstract Factory仅声明一个创建产品的接口,真正创建产品是由Concrete Product子类实现的。最通常的一个办法是为每一个产品定义一个工厂方法。一个具体的工厂将为每个产品重定义该工厂方法以指定产品。虽然这样的实现很简单,但它却要求每个产品系列都要有一个新的具体工厂子类,即使这些产品系列的差别很小。
3) 定义可扩展的工厂:Abstract Factory通常为每一种它可以生产的产品定义一个操作。产品的种类被编码在操作型构中。增加一种新的产品要求改变Abstract Factory的接口以及所有与它相关的类。一个更灵活但不太安全的设计是给创建对象的操作增加一个参数。该参数指定了将被创建的对象的种类。它可以是一个类标识符、一个整数、一个字符串,或其他任何可以标识这种产品的东西。实际上使用这种方法,Abstract Factory只需要一个“Make”操作和一个指示要创建对象的种类的参数。
TEMPLATE METHOD(模板方法)—类行为型模式
1. 意图
定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。Template Method使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
2. 结构
• Abstract Class(抽象类)
— 定义抽象的原语操作(primitive operation),具体的子类将重定义它们以实现一个算法
— 实现一个模板方法,定义一个算法的骨架。该模板方法不仅调用原语操作,也调用定义在Abstract Class或其他对象中的操作。
• Concrete Class(具体类)
- 实现原语操作以完成算法中与特定子类相关的步骤。
3. 适用性:
• 一次性实现一个算法的不变的部分,并将可变的行为留给子类来实现。
• 各子类中公共的行为应被提取出来并集中到一个公共父类中以避免代码重复。
4. 协作
• ConcreteClass靠Abstract Class来实现算法中不变的步骤。
- 效果
模板方法是一种代码复用的基本技术。
- 实现
一个模板方法调用的原语操作可以被定义为保护成员。这保证它们只被模板方法调用。必须重定义的原语操作须定义为纯虚函数。模板方法自身不需被重定义;因此可以将模板方法定义为一个非虚成员函数。
FACADE(外观)—对象结构型模式
- 意图
为子系统中的一组接口提供一个一致的界面,Facade模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。
- 结构
• Facade
— 知道哪些子系统类负责处理请求。
— 将客户的请求代理给适当的子系统对象。
• Subsystem classes
— 实现子系统的功能。
— 处理由Facade对象指派的任务。
— 没有facade的任何相关信息;即没有指向facade的指针。
- 适用性:
• 当你要为一个复杂子系统提供一个简单接口时。Facade可以提供一个简单的缺省视图,这一视图对大多数用户来说已经足够,而那些需要更多的可定制性的用户可以越过facade层。
• 客户程序与抽象类的实现部分之间存在着很大的依赖性。引入facade将这个子系统与客户以及其他的子系统分离,可以提高子系统的独立性和可移植性。
• 当你需要构建一个层次结构的子系统时,使用facade模式定义子系统中每层的入口点。如果子系统之间是相互依赖的,你可以让它们仅通过facade进行通讯,从而简化了它们之间的依赖关系。
- 协作
• 客户程序通过发送请求给Facade的方式与子系统通讯,Facade将这些消息转发给适当的子系统对象。尽管是子系统中的有关对象在做实际工作,但Facade模式本身也必须将它的接口转换成子系统的接口。
• 使用Facade的客户程序不需要直接访问子系统对象。
- 效果
1) 它对客户屏蔽子系统组件,因而减少了客户处理的对象的数目并使得子系统使用起来更加方便。
2) 它实现了子系统与客户之间的松耦合关系,而子系统内部的功能组件往往是紧耦合的。松耦合关系使得子系统的组件变化不会影响到它的客户。
6. 实现
1) 降低客户-子系统之间的耦合度。用抽象类实现Façade,而它的具体子类对应于不同的子系统实现,这可以进一步降低客户与子系统的耦合度。另一种方法是用不同的子系统对象配置Facade对象,为定制facade,仅需对它的子系统对象(一个或多个)进行替换即可。
BRIDGE(桥接)—对象结构型模式
- 意图
将抽象部分与它的实现部分分离,使它们都可以独立地变化。
- 结构
• Abstraction
— 定义抽象类的接口。
— 维护一个指向Implementor类型对象的指针。
• RefinedAbstraction
— 扩充由Abstraction定义的接口。
• Implementor
— 定义实现类的接口,该接口不一定要与Abstraction的接口完全一致;事实上这两个接口可以完全不同。一般来讲,Implementor接口仅提供基本操作,而Abstraction则定义了基于这些基本操作的较高层次的操作。
• ConcreteImplementor
— 实现Implementor接口并定义它的具体实现。
- 适用性:
• 不希望在抽象和它的实现部分之间有一个固定的绑定关系。例如这种情况可能是因为,在程序运行时刻实现部分应可以被选择或者切换。
• 类的抽象以及它的实现都应该可以通过生成子类的方法加以扩充。这时Bridge模式使你可以对不同的抽象接口和实现部分进行组合,并分别对它们进行扩充。
• 对一个抽象的实现部分的修改应对客户不产生影响,即客户的代码不必重新编译。
•有许多类要生成。
- 协作
• Abstraction将client的请求转发给它的Implementor对象。
- 效果
1) 分离接口及其实现部分。一个实现未必不变地绑定在一个接口上,抽象类的实现可以在运行时刻进行配置,一个对象甚至可以在运行时刻改变它的实现。
2) 提高可扩充性。可以独立地对Abstraction和Implementor层次结构进行扩充。
3 ) 实现细节对客户透明。
- 实现
1) 仅有一个Implementor 在仅有一个实现的时候,没有必要创建一个抽象的Implementor类。这是Bridge模式的退化情况.
2) 如果Abstraction知道所有的Concrete Implementor类,它就可以在它的构造器中对其中的一个类进行实例化,它可以通过传递给构造器的参数确定实例化哪一个类。
也可以代理给另一个对象,由它一次决定,例如引入一个factory对象(参见Abstract Factory)。
SINGLETON(单件)—对象创建型模式
- 意图
保证一个类仅有一个实例,并提供一个访问它的全局访问点。
- 结构
• Singleton
— 定义一个Instance操作,允许客户访问它的唯一实例。Instance是一个类操作C++中的一个静态成员函数)。
- 可能负责创建它自己的唯一实例。
- 适用性:
• 当类只能有一个实例而且客户可以从一个众所周知的访问点访问它时。
- 协作
• 客户只能通过Singleton的Instance操作访问一个Singleton的实例。
- 效果
- 对唯一实例的受控访问因为Singleton类封装它的唯一实例,所以它可以严格的控制客户怎样以及何时访问它。
- 是对全局变量的一种改进。它避免了那些存储唯一实例的全局变量污染名空间。
- 实现
1) 保证一个唯一的实例
客户仅通过Instance成员函数访问这个单件。变量_instance初始化为0,而静态成员函数Instance返回该变量值,如果其值为0则用唯一实例初始化它。
STRATEGY(策略)—对象行为型模式
- 意图
定义一系列的算法,把它们一个个封装起来, 并且使它们可相互替换。本模式使得算法可独立于使用它的客户而变化。
- 结构
• Strategy(策略)
— 定义所有支持的算法的公共接口。Context使用这个接口来调用某ConcreteStrategy定义的算法。
•ConcreteStrategy(具体策略)
— 以Strategy接口实现某具体算法。
• Context(上下文)
— 用一个ConcreteStrategy对象来配置。
— 维护一个对Strategy对象的引用。
— 可定义一个接口来让Stategy访问它的数据。
- 适用性:
• 许多相关的类仅仅是行为有异。“策略”提供了一种用多个行为中的一个行为来配置一个类的方法。
• 需要使用一个算法的不同变体。
• 一个类定义了多种行为, 并且这些行为在这个类的操作中以多个条件语句的形式出现。将相关的条件分支移入它们各自的Strategy类中以代替这些条件语句。
- 协作
• Strategy 和Context相互作用以实现选定的算法。当算法被调用时, Context可以将该算法所需要的所有数据都传递给该Stategy。或者,Context可以将自身作为一个参数传递给Strategy操作。这就让Strategy在需要时可以回调Context。
• Context 将它的客户的请求转发给它的Strategy。客户通常创建并传递一个Concrete Strategy 对象给该Context;这样, 客户仅与Context交互。通常有一系列的Concrete Strategy类可供客户从中选择。
- 效果
1 ) 相关算法系列。Strategy类层次为Context定义了一系列的可供重用的算法或行为。继承有助于析取出这些算法中的公共功能。
2) 一个替代继承的方法继承提供了另一种支持多种算法或行为的方法。将算法封装在独立的Strategy类中使得你可以独立于其Context改变它,使它易于切换、易于理解、易于扩展。
3) 消除了一些条件语句Strategy模式提供了用条件语句选择所需的行为以外的另一种选择。
5 ) 客户必须了解不同的Strategy 本模式有一个潜在的缺点,就是一个客户要选择一个合适的Strategy就必须知道这些Strategy到底有何不同。此时可能不得不向客户暴露具体的实现问题。
6 ) 增加了对象的数目Strategy增加了一个应用中的对象的数目。
- 实现
1) 定义Strategy和Context接口,Strategy和Context接口必须使得Concrete Strategy能够有效的访问它所需要的Context中的任何数据. 一种办法是让Context将数据放在参数中传递给Strategy;另一种办法是让Context将自身作为一个参数传递给Strategy, 该Strategy再显式地向该Context请求数据。
- 实例
TravelApp2类图
TravelApp3类图:
TravelApp4类图
TravelAfx类图:
- 其他
- 过分设计
随着模式使用的增加,我们已经看见有人做得太过分了。类不再简单。代码的每“大块”都非常灵活并且能适合许多不同的环境。这样的灵活性,无论如何都是要付出很大代价的。灵活的软件经常会使用间接方式或者不断增加的存储消耗来消耗更多的资源。它也在编码过程中需要更多的思考和更多的工作。好的设计者因此试着提前确定软件中哪些部分需要非常灵活以处理可预知的变化,哪些部分保持相对稳定。如果经过证明,它们是错误的,仍然可以通过仔细地重构系统的某些部分来引入另外的灵活性,或通过使用支持变更设计的模式来完成。这方法相比从一开始就考虑整体易修改性的工程技术更加经济实惠。
- 模式采掘
- 方法学