面向对象的设计原则与模式.ppt
第5章,面向对象的设计模式及应用,目标,掌握设计模式的基本概念掌握几个基本的设计模式学会运用常见的设计模式理解面向对象的设计原则,内容大纲,从一个例子开始影片出租店的程序什么是设计模式几个基本的设计模式单例模式、工厂模式、原型模式、组合模式、适配器模式、桥接模式、装饰模式、责任链模式面向对象的设计原则,从一个例子开始影片出租店的程序,这是一个影片出租店的程序,计算每一位顾客的消费金额并打印报表。操作者告诉程序:顾客租了哪些影片、租期多长,程序便根据租赁时间和影片类型算出费用。影片分为三类:普通片、儿童片和新片。除了计算费用还要为常客计算点数;点数会随着“租片种类是否为新片”而有所不同。,影片出租店的程序,初始代码,这个起始程序给你留下什么印象?我会说他设计的不好,而且很明显不符合面向对象的精神。对于这样的一个小程序,这点缺点其实没有什么关系。快速而随性(quick and dirty)地设计一个简单的程序并没有错。但如果这是复杂系统中具有代表性的一段,那么我就真的要对这个程序信息动摇了。Customer里头那个长长的statement()做的事情实在太多了,他做了很多原本应该由其他class完成的事情。,影片出租店的程序,即便如此,这个程序还能继续工作。所以这只是美学意义上的判断,只是对丑陋代码的厌恶,是吗?在我们修改这个系统之前的确如此。编译器才不会在乎代码好不好看呢。但是当我们打算修改系统的时候,就涉及到了人,而人在乎这些。差劲的系统是很难修改的,因为很难找到修改点。如果很难找到修改点,程序员就很有可能犯错,从而引入臭虫(bugs)。,影片出租店的程序,在这个例子里,我们的用户希望对系统做一点修改。首先他们希望以HTML格式打印报表,这样就可以直接在网页上显示,这非常符合潮流。现在请你想一想,这个变化会带来什么影响。看看代码你就会发现,根本不可能在打印HTML报表的函数中复用(reuse)目前statement()的任何行为。你唯一可以做的就是编写一个全新的htmlstatement(),大量重复statement()行为。当然,现在做这个还不太费力,你可以把statement()复制一份然后按需要修改就是。,影片出租店的程序,但如果计费标准发生变化,又会发生什么事?你必须同时修改statement()和htmlstatement(),并确保两处修改的一致性。当你后续还有再修改时,剪贴(copy-paste)问题就浮现出来了。如果你编写的是一个永不需要修改的程序,那么剪剪贴贴就还好,但如果程序要保存很长时间,而且可能需要修改,剪贴行为就会造成潜在的威胁。,影片出租店的程序,现在,第二个变化来了:用户希望改变影片分类规则,但是还没有决定怎么改。他们设想了几种方案,这些方案都会影响顾客消费和常客积点的计算方式。作为一个经验丰富的开发者,你可以肯定:不论用户提出什么方案,你唯一能够获得的保证就是他们一定会在若干个月之内再次修改它。,影片出租店的程序,为了应付分类规则和计费规则的变化,程序必须对statement()作出修改。但如果我们把statement()内的代码拷贝到用以打印HTML报表的函数中,我们就必须确保将来的任何修改在两个地方保持一致。随着各种规则变得愈来愈复杂,适当的修改点愈来愈难找,不犯错的机会也愈来愈少。,影片出租店的程序,你的态度也许倾向尽量少修改程序:不管怎么说,它还运行得很好。你心里头牢牢记着那句古老的工程学格言如果它没坏,就别动它。这个程序也许还没坏掉,但它带来了伤害。它让你的生活比较难过,因为你发现很难完成客户所需的修改。这时候就该重构技术粉墨登场了。,影片出租店的程序,从Customer类中statement方法中分离出amountFor功能,amountFor方法中变量名称的修改,任何一个傻瓜都能写出计算机可以理解的代码。唯有写出人类容易理解的代码,才是优秀的程序员,搬移amountFor方法,变更函数名称,去掉临时变量thisAmount,对frequentRenterPoints进行类似于amountFor方法的处理,去掉临时变量totalAmount和frequentRenterPoints,Movie的继承与多态,影片出租店的程序,我们从中体会到了什么?,我们为什么不能一开始就能设计出最后的程序?,因为我们没有抓住面向对象设计中的一种规律设计模式就是前人为我们总结出的面向对象设计的一种规律,我们所要做的就是吸收消化,并加以运用,设计模式出场啦,设计模式基础什么是设计模式,模式(pattetn)就是做事的一种方法,也即实现某个目标的途径,或者技术。这种捕获有效技术的思想可以应用到许多领域当中,比如烹饪、焰火制品生产、软件开发以及其他一些行业。对于任何一个正迈向成熟的行业,人们总会开始寻找一些通用和有效的方法,来达到目标并解决不同场合中的问题。由某个行业的从业人员组成的社区通常会发明一些专业术语,以便于同行之间进行交流。其中某些专业术语被成为模式,即实现特定目标的公认技术。随着某个行业及其专业术语的发展,一些作者便开始起到重要的作用。他们将某个行业的模式记录归档,参与标准化专业术语,并且公开传播这些有效的技术。,设计模式基础什么是设计模式,在面向对象的软件设计中,总是希望避免重复设计或尽可能少做重复设计。有经验的面向对象设计者的确能做出良好的设计,而新手则面对众多选择无从下手,总是求助于以前使用过的非面向对象技术。有经验的设计者显然知道一些新手所不知道的东西,这又是什么呢?内行的设计者知道:不是解决任何问题都要从头做起。他们更愿意复用以前使用过的解决方案。当找到一个好的解决方案,他们会一遍又一遍地使用。这些经验是他们成为内行的部分原因。它们帮助设计者将新的设计建立在以往工作的基础上,复用以往成功的设计方案。一个熟悉这些模式的设计者不需要再去发现它们,而能够立即将它们应用于设计问题中。,设计模式基础什么是设计模式,设计模式使人们可以更加简单方便地复用成功的设计和体系结构,帮助你做出有利于系统复用的选择,避免设计损害了系统复用性。通过提供一个显式类和对象作用关系以及它们之间潜在联系的说明规范,设计模式甚至能够提高已有系统的文档管理和系统维护的有效性。简而言之,设计模式可以帮助设计者更快更好地完成系统设计。,设计模式基础什么是设计模式,Christopher Alexander是最早将某个行业的最佳实践记录为模式的作者之一。他研究的领域是建筑(而不是软件)的架构。其著作A Pattern Language:Towns,ConstructionAlexander、Ishikouwa 和Silverstein,1977介绍了成功设计房屋和城镇的模式。该著作影响深远,甚至影响到软件开发行业。该书之所以能够影响其他行业,部分原因是因为它给出了一种独特的观察目标的方式。Christopher Alexander 说过:“每一个模式描述了一个在我们周围不断重复发生的问题,以及该问题的解决方案的核心。这样,你就能一次又一次地使用该方案而不必做重复劳动”。尽管Alexander所指的是城市和建筑模式,但他的思想也同样适用于面向对象设计模式,只是在面向对象的解决方案里,我们用对象和接口代替了墙壁和门窗。两类模式的核心都在于提供了相关问题的解决方案。,几个基本的设计模式单例模式,1:环境:几乎在每个应用程序中,都需要有一个从中进行全局访问和维护某种类型数据的区域。在面向对象的(OO)系统中也有这种情况,在此类系统中,在任何给定时间只应运行一个类或某个类的一组预定义数量的实例。例如,当使用某个类来维护增量计数器时,此简单的计数器类需要跟踪在多个应用程序领域中使用的整数值。此类需要能够增加该计数器并返回当前的值。又如建立目录、数据库连接、Windows针对所有用户都应有一个回收站等,都需要这样的单对象的操作。对于这种情况,所需的类行为应该仅使用一个类实例来维护该整数,而不是使用多个类实例来维护该整数。最初,人们可能会试图将计数器类实例只作为静态全局变量来创建。这是一种通用的方法,但实际上只解决一部分问题;它解决了全局可访问性问题,但没有采取任何措施来确保在任何给定的时间只运行一个类实例。应该由类本身来负责只使用一个类实例,而不是由类用户来负责。应该始终不要让类用户来监视和控制运行的类实例的数量。,几个基本的设计模式单例模式,2:问题采用什么方法来控制创建类实例的个数,然后确保在任何给定的时间只创建一个类实例。这会确切地给我们提供所需的行为,并使客户端不必了解任何类细节。3:解决方案,私有的静态属性,类的唯一实例,将构造函数私有化,通过共有的函数返回Instance,几个基本的设计模式单例模式,一般Singleton模式通常有如下两种种形式,第一种叫饿汉式,第二种叫懒汉式。饿汉式在类一实例化的时候就初始化了instance;懒汉式仅在调用时才初始化。,public class Singleton private Singleton()private static Singleton instance=new Singleton();public static Singleton getInstance()return instance;public class Singleton private static Singleton instance=null;public static synchronized Singleton getInstance()if(instance=null)instancenew Singleton();return instance;,几个基本的设计模式单例模式,注意到lazy initialization形式中的synchronized,这个synchronized很重要,如果没有synchronized,那么使用getInstance()是有可能得到多个Singleton实例。,几个基本的设计模式工厂模式,为什么要有工厂模式?就拿我们一开始学到的手机发短信的例子,如果没有“手机工厂”,就等于将手机对象的创建工作放到所有的客户(Client)中完成,没有实现良好的封装和重用,也违背了OCP原则与DIP原则。DIP原则告诉我们:要对接口编程,不要对实现编程,只有这样才能更好地实现OCP。在手机工厂的例子中,工厂就是创建具体手机的接口。,今天我去市场,要决定是买水果等产品,还是选择种水果的产品。具体怎么操作自己选择。来到市场,我发现主要有一些水果:苹果(Apple),葡萄(Grape)和鸭梨(Pear)。到底买什么好呢?我决定买苹果。,几个基本的设计模式工厂模式,三种工厂模式Simple Factory模式 专门定义一个类来负责创建其它类的实例,被创建的实例通常都具有共同的父类。,Factory Method模式 将对象的创建交由父类中定义的一个标准方法来完成,而不是其构造函数,究竟应该创建何种对象由具体的子类负责决定。,Abstract Factory模式 提供一个共同的接口来创建相互关联的多个对象。,几个基本的设计模式工厂模式,Simple Factory模式,几个基本的设计模式工厂模式,Simple Factory模式 我要购买苹果,只需向工厂角色(BuyFruit)请求即可。而工厂角色在接到请求后,会自行判断创建和提供哪一个产品。但是对于工厂角色(BuyFruit)来说,增加新的产品(比如说增加草莓)就是一个痛苦的过程。工厂角色必须知道每一种产品,如何创建它们,以及何时向客户端提供它们。换言之,接纳新的产品意味着修改这个工厂。因此Simple Factory模式的开放性比较差。有什么办法可以解决这个问题吗?那就需要Factory Method模式来为我们服务了。,几个基本的设计模式工厂模式,Factory Method模式,几个基本的设计模式工厂模式,Factory Method模式 工厂方法模式和简单工厂模式在结构上的不同是很明显的。工厂方法模式的核心是一个抽象工厂类,而简单工厂模式把核心放在一个具体类上。工厂方法模式可以允许很多具体工厂类从抽象工厂类中将创建行为继承下来,从而可以成为多个简单工厂模式的综合,进而推广了简单工厂模式。工厂方法模式退化后可以变得很像简单工厂模式。设想如果非常确定一个系统只需要一个具体工厂类,那么就不妨把抽象工厂类合并到具体的工厂类中去。由于反正只有一个具体工厂类,所以不妨将工厂方法改成为静态方法,这时候就得到了简单工厂模式。如果需要加入一个新的水果,那么只需要加入一个新的水果类以及它所对应的工厂类。没有必要修改客户端,也没有必要修改抽象工厂角色或者其他已有的具体工厂角色。对于增加新的水果类而言,这个系统完全支持“开-闭”原则。对Factory Method模式而言,它只是针对一种类别(如本例中的水果类Fruit),但如果我们还想买肉,那就不行了,这是就必须要AbstractFactory Method模式帮忙了。,几个基本的设计模式工厂模式,Abstract Factory Method模式,Apple与CowMeat是一个产品的两个套件,几个基本的设计模式工厂模式,Factory Method模式 抽象工厂模式可以向客户端提供一个接口,使得客户端在不必指定产品的具体类型的情况下,创建多个产品族中的产品对象。这就是抽象工厂模式的用意。抽象工厂模式是所有形态的工厂模式中最为抽象和最具一般性的一种形态。,工厂模式的更具体的阐述见Java与模式一书的1214章,几个基本的设计模式原型模式,Prototype模式 打开文件,几个基本的设计模式组合模式,Composite模式Java与模式P411上图合成模式的一种简化模式去除叶类,当合成模式的三个角色合并时,合成模式可以采用如左图所示的结构。可以通过子节点的值是否为null来判断它是不是叶节点。这种方法的一个很大优势在于,可通过在运行时为叶节点增加子节点而使它成为非叶节点;而缺陷在于不存在抽象层(不管是否应用设计模式,将抽象层提炼出来通常是一个很好的实践,因为能够使用抽象层的更加通用的类来编写客户的代码)。当客户端应用不关心节点是否为叶节点还是枝节点时(即客户端想对叶节点和枝节点一视同仁时),可以采用该种模式。,几个基本的设计模式,适配器模式:Java与模式P359责任链模式:Java与模式P785,面向对象的设计原则,开闭原则里氏替换原则依赖倒转原则接口隔离原则合成聚合复用原则(使用聚合替代继承)迪米特法则,