740 likes | 1.02k Views
第八讲 设计模式. 设计模式概念与特点. 设计模式概念 设计模式是一些面向对象的软件开发经验总结。一个设计模式有系统的命名、解释和评价,是某个重要的可重现的面向对象的设计方案。 设计模式特点 设计模式是被发现而不是被发明出来的。 设计模式是解决一类相关问题的通用技术。. 设计模式组成. 构造式设计模式 结构式设计模式 行为式设计模式. 设计模式描述方法. 广泛采用的设计模式描述是 Erich Gamma 等人采用的方式: • 模式分类和名称:名称及一个简短摘要 • 目的:设计用途、基本原理和目标,针对的特殊问题 • 别名:不同专家给予的命名
E N D
设计模式概念与特点 • 设计模式概念 • 设计模式是一些面向对象的软件开发经验总结。一个设计模式有系统的命名、解释和评价,是某个重要的可重现的面向对象的设计方案。 • 设计模式特点 • 设计模式是被发现而不是被发明出来的。 • 设计模式是解决一类相关问题的通用技术。
设计模式组成 • 构造式设计模式 • 结构式设计模式 • 行为式设计模式
设计模式描述方法 广泛采用的设计模式描述是 Erich Gamma 等人采用的方式: •模式分类和名称:名称及一个简短摘要 •目的:设计用途、基本原理和目标,针对的特殊问题 •别名:不同专家给予的命名 •动机:描述设计问题的方案,其中类与对象的结构 •应用:应用的条件,如何识别这些情况 •结构:用对象模型技术对模式进行结构表示,对象图、合作图等 •成分:组成模式的类和对象,以及它们的职责 •合作:成分之间如何合作实现它们的任务 •效果:使用的利弊权衡,进行必要改造的可能性和途径 •实现:实现过程应注意的问题和技术,与程序语言的相关性 •代码示例:用C++语言实现的代码和说明 •已有的应用:应用实例介绍 •相关的模式:与之相关的其它模式
设计模式的类型 1. 构造式:对象创建的过程处理
3. 行为式:处理对象间的交互方式和任务分布
设计模式的问题和解决方案 • 问题:模式过多! • Pattern Almanac 2000列出了大约500种设计模式,并且又发布了数百种模式。 • 解决方案:找到根本原则 • 大多数设计模式可以被视为少数几个GRASP基本原则的特化。这样除了能够有助于加速对详细设计模式的学习之外,而且对发现其根本的基本主题(防止变异、多态、间接性等)更为有效,它能够帮助我们透过大量细节发现应用设计技术的本质。
适配器(Adapter) GoF • 名称:适配器(Adapter) • 问题:如何解决不相容的接口问题,或者如何为具有不同接口的类似构件提供稳定的接口? • 解决方案(建议):通过中介适配器对象,将构件的原有接口转换为其他接口
适配器-示例 • 例如,POS系统需要支持第三方外部服务,其中包括税金计算器、信用卡授权服务、库存系统和账务系统。它们都具有不同的API,而且无法改变。 • 解决方案之一:增加一层间接性对象,通过这些对象将不同的外部接口调整为在应用程序内使用的一致接口。
适配器-示例 适配器模式
适配器-示例 使用适配器
适配器、外观的区别 • 外观(Façade)是资源适配器使用单一对象封装了对子系统或系统的访问(外观的本质)。而当包装对象是为不同外部接口提供适配时,该对象才被特称为资源适配器
适配器与某些GRASP核心原则的关系 防止变异 低耦合机制 高内聚机制 多态示例 间接性机制 纯虚构 适配器
工厂(Factory) • 该模式也称为简单工厂(Simple Factory)或具体工厂(Concrete Factory)。该模式不是GoF设计模式,它是GoF抽象工厂(Abstract Factory)模式的简化,应用极为广泛。 • 适配器在设计中会引发一个新问题:在前述适配器模式的解决方案中,对外部服务有不同的接口,那么是谁创建了这些适配器?并且如何决定创建哪种类的适配器,例如是创建TaxMasterAdapter还是GoodAsGoldTaxProAdapter?
工厂(Factory) • 如果是某个领域对象来创建这些适配器,那么领域对象的职责会超出了单纯的应用逻辑(例如销售总额的计算),并且会涉及到与外部软件构件连接相关的其他内容。 • 基本设计原则之一(通常被视为构架设计原则):设计要保持关注分离(separation of concern),将不同关注分离或模块化为不同领域,以确保内聚。
工厂(Factory) • 常用的替代方案是使用工厂模式,其中定义纯虚构的“工厂”对象来创建对象。 • 工厂对象有如下一些优势: • 分离复杂创建的职责,并将其分配给内聚的帮助者对象 • 隐藏潜在的复杂创建逻辑 • 允许引入提高性能的内存管理策略,例如对象缓存或再生
工厂(Factory) • 名称:工厂(Factory) • 问题:当有特殊考虑(例如存在复杂创建逻辑、为了改良内聚而分离创建职责等)时,应该由谁来负责创建对象? • 解决方案(建议):创建称为工厂的纯虚构对象来处理这些创建职责
工厂(Factory)-示例 工厂模式
单例(Singleton) GoF • 工厂对象的设计引发了另一个新问题:由谁来创建工厂自身,如何访问工厂? • 首先,注意在该过程中只需要一个工厂实例。其次,因为在代码的不同位置都需要访问适配器以调用外部服务,所以我们很快就会提出要在代码的不同位置调用工厂中的方法。因此,这里也存在可见性问题:如何获得单个ServicesFactory实例的可见性?
单例(GoF) • 名称:单例(Singleton) • 问题:只有唯一实例的类即为“单例类”。对象需要全局可见性和单点访问。 • 解决方案(建议):对类定义静态方法用以返回单例
单例(GoF)-示例 在ServiceFactory类中的单例模式
策略(Strategy)GoF • 问题:如何提供较为复杂的定价逻辑,例如商店在某天的折扣、老年人折扣等等。 • 销售的定价策略(也可以称为规则、政策或算法)具有多样性。在一段时期内,对于所有的销售可能会有10%的折扣,后期可能会对超出200元的销售给予10%的折扣,并且还会存在其他大量变化。我们如何对这些各种各样的定价算法进行设计?
策略(GoF) • 名称:策略(Strategy) • 问题:如何设计变化但相关的算法或政策?如何设计才能是这些算法或政策具有可变更的能力? • 解决方案(建议):在单独的类中分别定义每种算法/政策/策略,并且使其具有共同接口。
策略(GoF) 定价策略类
策略(GoF) 协作中的策略
策略(GoF) 语境对象需要其策略的属性可视性
使用工厂创建策略 创建策略
工厂-总结 • 对于动态变化的定价策略的防止变异可以通过策略和工厂模式实现。建立在多态和接口基础上的策略可以实现具有可插拔的对象设计
组合(Composite)GoF • 问题:我们如何来处理多个互相冲突的定价策略?例如商店在今天(星期一)有效的政策有: • 对老年人有20%的折扣政策 • 对于购物金额满400元的优先客户给予折15%的折扣 • 在星期一,购物金额满500美元享受50元的折扣 • 买一罐咖啡,则所有购物物品都享受15%的折扣 假设一个老年人同时也是优先客户,他买了一罐咖啡和600元的物品。此时应该适用哪个定价政策?
组合( GoF ) • 名称:组合(Composite) • 问题:如何能够像处理非组合(原子)对象一样,(多态地)处理一组对象或具有组合结构的对象呢? • 解决方案(建议):定义组合和原子对象的类,使它们实现相同的接口
组合( GoF )-示例 组合模式
组合( GoF )-示例 与组合的协作
创建多个SalePricingStrategies • 使用组合模式,我们使一组多个(有冲突)的定价策略对于Sale对象来说与单个定价策略一样。包含该组的组合对象也实现了ISalePricingStrategy接口。对于该设计问题更具有挑战性(并且更有趣)的部分是:我们什么时候创建这些策略?
创建多个SalePricingStrategies • 良好的设计应该在开始时为商店创建一个包含当前所有折扣政策(如果没有有效折扣,可以设为0%)的组合,例如某个PercentageDiscountPricingStrategy。然后,如果在该场景的下一步中,发现还需要应用另一个定价策略(例如老年人折扣),此时通过使用继承来的CompositePricingStrategy.add方法,能够轻松地在组合中增加这一策略。 • 在该场景中,可能有三个地方需要在组合中增加策略: • 商店当前定义的折扣,在创建销售时增加 • 顾客类型折扣,在POS获知顾客类型时增加 • 产品类型折扣(如果购买一罐咖啡,则所有购物物品都享受15%的折扣),在向销售输入该产品条目时增加
创建多个SalePricingStrategies 创建组合策略
外观(Façade)GoF 问题: • 假设创建新销售时,可能要识别该销售是否以礼券方式进行支付(这是可能并且普遍的做法)。然后,商店可能会规定如果使用礼券只可以购买一件商品。此时,enterItem在完成第一次操作后应该变为无效。 • 如果销售使用礼券支付,在对该顾客找零时,除了礼券之外所有其他支付类型的找零都应该置为无效。例如,如果收银员请求现金找零或更新顾客在其商店帐户上的积分时,这些请求都应该被判断为无效。 • 假设创建新销售时,可以识别其为慈善捐助(从商店到慈善机构)。商店可能会规定每次输入的条目价值只能低于250元,并且只有当前登录者为经理时才允许对该销售添加条目。
外观(GoF) • 名称:外观(Façade) • 问题:对一组完全不同的实现或接口(例如子系统中的实现和接口)需要公共统一的接口。可能会与子系统内部的大量事物产生耦合,或者子系统的实现可能会改变。怎么办? • 解决方案(建议):对子系统定义唯一的接触点—使用外观对象封装子系统。该外观对象提供了唯一和统一的接口,并负责与子系统构件进行协作
外观(GoF) 具有外观的UML包图
外观-总结 • 外观模式很简单并且应用广泛。它将子系统隐藏在一个对象之后。 • 外观通常通过单例模式进行访问。它们对子系统的实现提供了防止变异,并且通过增加间接性对象有助于低耦合的支持。外部对象只被耦合到子系统中的一个点:即外观对象 • 适配器对象可能用来封装对具有不同接口的外部系统的访问。这就是一种外观,但是其强调的是对不同接口的适配,因此被更具体地称为适配器。
观察者/发布-订阅/委派事件模型(GoF) • 问题:当总额变化后,GUI窗口要能刷新销售总额的显示
观察者(GoF) • 解决方案一:当Sale更新了总额后,Sale对象向窗口发生消息,使其刷新显示。 • 模型-视图分离原则不提倡此类解决方案。它认为”模型“对象(诸如Sale这样的非UI对象)不应该知道像窗口这样的视图或表示对象。它改进了其他层与表示(UI)层对象的低耦合。支持这种低耦合的结果是,它允许替换现有视图或表示层,或者可以使用新的窗口来代替特定窗口,同时不会对非UI对象产生影响。例如,如果模型对象不知道Java Swing对象,那么就可能拔掉Swing接口,或者拔掉特定窗口,然后插入其他什么。 • 因此,模型-视图分离对变化的用户界面提供了防止变异。
观察者(GoF) • 名称:观察者(Observer)(发布-订阅(Publish-Subscribe)) • 问题:不同类型的订阅者对象关注与发布者对象的状态变化或事件,并且想要在发布者产生事件时以自己独特的方式作出反应。此外,发布者想要保持与订阅者的低耦合。如何对此进行设计呢? • 解决方案(建议):定义”订阅者“或”监视器“接口。订阅者实现此接口。发布者可以动态注册关注某事件的订阅者,并在事件发生时通知它们
观察者(GoF)-示例 观察者模式
观察者(GoF)-示例 观察者SaleFrame1向发布者Sale订阅
观察者(GoF)-示例 Sale向其所有订阅者发布特性事件
观察者(GoF)-示例 订阅者SaleFrame1接收已发布事件的通知