面向对象技术
刘竹松 liuzs@gdut.edu.cn\r广东工业计算机学院\r(PPT根据网络资源修改,在此表示感谢)\r
面向对象的设计模式Object-Oriented Design Patterns
-3-
Review: Object-Oriented Design Principles
LSP:Liskov替换原则\rThe Liskov Substitution Principle\rOCP:开放-封闭原则\rThe Open-Close Principle\rSRP:单一职责原则\rThe Single Responsibility Principle\rISP:接口隔离原则\rThe Interface Segregation Principle \rDIP:依赖倒置原则\rThe Dependency Inversion Principle\r……
-4-
学习线路图
-5-
References
[GoF94], Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides, Design Patterns: Elements of Reuseable Object-Oriented Software(李英军等译,设计模式—可复用面向对象软件的基础,机械工业出版社,2000年)\r[Mart02], Robert C. Martin, Agile Software Development-Principles, Patterns, and Practices(邓辉译,敏捷软件开发-原则、模式与实践,清华大学出版社,2003年)\r[Gold01], Brandon Goldfedder, The Joy of Patterns: Using Patterns for Enterprise Development(熊节译,模式的乐趣,清华大学出版社,2003年) \r[Larm01], Craig Larman, Applying UML and Patterns, 2e(姚淑珍、李虎等译,UML和模式应用-面向对象分析与设计导论,机械工业出版社,2002年)
-6-
内容安排
从原则到模式\r设计模式\rGoF设计模式及应用\rGRASP职责分配模式\r模式与编程语言\r模式与重构
-7-
内容安排
从原则到模式\r设计模式\rGoF设计模式及应用\rGRASP职责分配模式\r模式与编程语言\r模式与重构
-8-
模式?
如何在已排序的值列表中查找一个数组?
1. 将列表一分为二。将要查找的值与中间元素的值相比较。如果相等,就找到我们要查找的值。如过要查找的值小于中间元素的值,将中间点设置为列表的新的顶点(并再次将列表一分为二)。如果要查找的值大于中间元素的值,将中间点设置为列表的新的尾点。然后再将列表一分为二。继续这种分割过程,直到列表不能再分为止。此时,如果要查找的值不再最后两个元素中,它就不在这个列表中。
2. 使用二分查找
-9-
设计?
Maslov:如果你唯一的工具是锤子,你就会把所有的东西都当作钉子!
1.绝大多数开发人员和设计人员都是程序员出身,他们习惯于最熟悉的工具—即编写代码的文本编辑器—来思考!\r2.设计主要是一个交流的活动,而程序员的交流能力通常非常糟糕!
Build for Today, Design for Tomorrow!
-10-
设计模式?
-11-
模式
Pattern\rA decorative design, as for wallpaper, made up of elements in a regular arrangement\rA recognizable combination of action, qualities, etc., characteristic of a particular person or population\r通用问题的解决方案\r来自建筑行业\rChristopher Alexander\r1977年《A Pattern Language》
-12-
Concept of Patterns
Each pattern describes a problem which occurs over and over again in our environments, and then describes the core of the solution to that problem, in such a way that you can use this solution a million times over, without ever doing it the same way twice.\r -- Christopher Alexander, \r A Pattern Language, 1977
-13-
模式的形式
模式的一个主要目标就是以一种别人容易接受的方式,捕捉那些重复出现的问题的解决方案\r模式的形式:\r名称\r意图\r动机\r适用性\r结构\r参与者\r协作\r效果\r实现
example
-14-
模式简史-1
1964 Christopher Alexander出版了Notes on the Synthesis of Form,该书尝试从一个不同的角度来看待建筑的过程\r1977 Christopher Alexander出版了A Pattern Language一书\r1987 Ward Cunningham和Kent Beck开始将一些建筑学概念应用到使用SmallTalk语言的软件开发中\r1992 Jim Coplien出版了Advanced C :Programming Style and Idioms一书\r1992 Peter Coad在ACM上发表了他在分析模式方面的研究成果\r1993 Erich Gamma的博士论文以及JohnVlissides、Ralph Johnson和Richard Helm的其它研究成果在ECOOP 93上发表\r1993 Kent Beck、Grady Booch、Jim Coplien以及其他人组成了Hillside小组,提供一个讨论模式的论坛
-15-
模式简史-2
1994 第一次编程模式语言(Pattern Language of Programming, PLoP)大会举行\r1994 Design Patterns: Elements of Reusable Object-Oriented Software出版\r1996 Frank Buschmann与其他人合作出版了Pattern-Oriented Software Architecture: A System of Patterns一书\r1997 Martin Flower出版了Analysis Patterns: Reusable Object Models一书\r1999 Martin Flower与其他人合作出版了Refactoring:Improving the Design of Existing Code一处\r……
-16-
内容安排
从原则到模式\r设计模式\rGoF设计模式及应用\rGRASP职责分配模式\r模式与编程语言\r模式与重构
-17-
设计模式
什么是设计模式?\r设计就是解决方案—对某个问题的解决\r如果某个解决方案对某类问题都很有用\r这时就把它总结出来\r这就产生了设计模式
-18-
设计模式
是:\r优秀的设计范例\r从优秀设计方案中发现和总结出来的经验\r在实践中反复出现的设计问题的优秀解决方案\r设计者相互交流的基本术语:设计语言\r培养优秀设计师的一条捷径\r不是:\r面向对象设计的框架\r可供简单组合的设计元件\r发明创造出来的创新思路\r解决面向对象设计问题的完整方案
-19-
设计模式的基本要素
1. 名称:用于助记,形象表示这个模式\r2. 问题:这个模式可以解决什么问题\r3. 解决方案:这个模式怎样解决这个问题的步骤与方法\r4. 效果:使用这个模式与不使用这个模式有什么区别,它有什么优点和缺点
一个问题可以有多种解法,好的解法都可以找到很多种,每种都有优缺点;所以编程时不要死记方法,应该活学活用
-20-
设计模式的基本思想-1
软件是在不断进化的\r需求在不断改变,所以软件应该适应变化\r设计模式是为了让软件更加适应变化,有更多的可复用性;就是有变化时你不用从头重写一次这个软件\r如何适应变化?\r就应该封装变化,让变化的影响最小\r封装复杂性,提供简单的接口
-21-
设计模式的基本思想-2
1. 松耦合\r2. 针对接口编程,而不是针对实现编程\r3. 继承、组合、委托、多态、参数化
-22-
内容安排
原则与模式\r设计模式\rGoF设计模式及应用\rGRASP职责分配模式\r模式与编程语言\r模式与重构
-23-
经典设计模式:GoF
GoF(Gang of Four) 23种经典设计模式
-24-
GoF模式分类-1
根据模式的目的(用来完成什么工作的) \r创建型模式\r结构型模式\r行为型模式\r根据模式的作用范围(是处理类还是处理对象的):\r类模式\r对象模式
-25-
GoF模式分类-2
创建型模式\r创建型类模式将对象的部分创建工作延迟到子类\r创建型对象模式将它延迟到另一个对象中\r结构型模式\r结构型类模式使用继承机制来组合类\r结构型对象模式描述了对象间的组装方式\r行为型模式\r行为型类模式使用继承描述算法与控制法\r行为型对象模式则描述一组对象怎样协作完成单个对象无法完成的工作
-26-
GoF设计模式
GoF 23种经典设计模式
-27-
GoF应用-State模式-1
//修改LegoSystem源代码\rcase BLUE:\r BlueProcess();\r break;
LegoSystem::ProcessColor(){\r switch (color) {\r case RED:\r RedProcess();\r break;\r case GREEN:\r GreenProcess();\r break;\r case YELLOW:\r YellowProcess();\r break;\r }\r};
-28-
State模式-2
目的:允许一个对象在其内部状态改变时改变其行为\r结构\r\r\r\r适用性:\r一个对象的行为取决于它的状态, 并且它必须在运行时刻根据状态改变它的行为\r一个操作中含有庞大的多分支的条件语句,且这些分支依赖于该对象的状态。这个状态通常用一个或多个枚举常量表示\rState模式将每一个条件分支放入一个独立的类中;这使得可以根据对象自身的情况将对象的状态作为一个对象,这一对象可以不依赖于其他对象而独立变化
-29-
State模式-3
-30-
State模式-4
LegoSystem::ProcessColor(){\r Color->Process();\r};\rCColor{\rpublic:\r virtual void Process();\r}\rvoid CRed::Process(){\r RedProcess();\r};\rvoid CGreen::Process(){\r RedProcess();\r};\rvoid CYellow::Process(){\r RedProcess();\r} ;
//仅需要增加新的类,\r//原有代码不需要任何变动\rvoid CBlue::Process(){\r BlueProcess();\r};
State模式满足OCP
-31-
汉堡店-1
增加奶酪?
加入洋葱?
-32-
汉堡店-2
加入洋葱?
加入西红柿?
-33-
汉堡店-3
加入西红柿?
-34-
汉堡店-4:问题
类爆炸性增长(指数级别:2n)\r再增加一种调料(如生菜),则多出8个类\r继续增加一种调料(如番茄酱),则多出16个类\r破坏单点维护能力\r如添加一个简单的getPrice方法,将会受到严重的影响?\r实际情况呢?\r我们可以把每层调料都看作是在普通汉堡或三明治上附加的装饰物
-35-
GoF: Decorator模式
目的:动态地给一个对象添加一些额外的职责(就增加功能来说,Decorator 模式相比生成子类更为灵活)\r结构:\r适用性:\r在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责。 \r处理那些可以撤消的职责。 \r当不能采用生成子类的方法进行扩充时\r可能有大量独立的扩展,为支持每一种组合将产生大量的子类,使得子类数目呈爆炸性增长\r可能是因为类定义被隐藏,或类定义不能用于生成子类
-36-
汉堡店:Decorator模式
-37-
应用Decorator模式
-38-
模式思维
如何应用设计模式\r“任何问题都可以通过增加一个间接层次来解决。”(抽象层)\r这话不是严格的正确,但是经常是解决问题的有效指导思想\r设计模式的核心思维\r多态包容是诸多设计模式中都反复出现的经典技术。掌握了多态包容,就能够在实践中自然而然地运用多种设计模式\r多态包容:一个宿主对象中包含基类型(或接口)引用,并将一部分行为委托给这个引用所指向的实际对象,使自己的行为可以根据该实际对象的不同而有所不同
-39-
可复用的Button
Button key9 = new Button(“9”);
Button keyDial = \r dialKeySingleton();
Button keyPower = \r powerKeySingleton();
-40-
“拨电话” 的协作图
-41-
观察静态结构(类图)
-42-
根据类图试写Button代码
public class Button { private Dialer myDialer; public Button(Dialer dialer) { myDialer = dialer; } public buttonPressed(String token) throws NumberFormatException { int digit = Integer.parseInt(token); dialer.digit(digit); }}
Button与Dialer之间出现耦合,从而破坏了Button的可复用性我们考虑增加一个间接层来解决?
-43-
添加一个间接层?
添加一个怎样的类?\r两个关系是怎样的?
X
?
?
-44-
改进设计—Active Server模式
面向对象设计对付类耦合的基本对策是:将两个类之间的关联转变为一个类与一个接口之间的关联,或者两个接口之间的关联
-45-
新的问题?
interface ButtonListener { public void buttonPressed(int token)}public class Dialer implements ButtonListener { public void buttonPressed(int token) … }
问题在于:Dialer为什么一定要能够处理buttonPressed事件?为什么一定要求Dialer能够接受int型的数值作为输入?如果Dialer是已经做好的配件,只能接受一个字符串表示的电话号码,又该怎么办?这种设计限制了Dialer的可复用性
-46-
再增加一个间接层
-47-
GoF:Adapter模式
目的:将一个类的接口转换成客户希望的另外一个接口;Adapter 模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作 \r结构:\r适用性:\r使用一个已经存在的类,而它的接口不符合需求\r创建一个可以复用的类,该类可以与其它不相关的类或不可预见的类(即那些接口不一定兼容的类)协同工作\r使用一些已经存在的子类,但是不可能对每一个都进行子类化以匹配它们的接口;对象Adpater可以适配它的父类接口
-48-
代码实现
interface ButtonListener { void buttonPressed(int digit);}public class Dialer { public void dial(String pno) { radio.connect(pno);}}public class ButtonDialerAdapter implements ButtonListener { StringBuffer myPho = “”; Dialer myDialer; … void buttonPressed(int digit) { if (digit == DIAL_SIGNAL) myDialer.dial(myPho); else myPho.append(digit); }}
-49-
引发潜在问题
既然Dialer只在拨号时反应,那么按下数字键时,如何让Speaker发音?
-50-
如法炮制
现在的关键问题是,Button必须可以在被按下的时候,通知数量未知的监听者,或者说“观察者(observer)”
-51-
GoF:Observer模式
目的:定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时, 所有依赖于它的对象都得到通知并被自动更新\r结构:\r适用性:\r当一个抽象模型有两个方面, 其中一个方面依赖于另一方面。将这二者封装在独立的对象中以使它们可以各自独立地改变和复用\r当对一个对象的改变需要同时改变其它对象, 而不知道具体有多少对象有待改变\r当一个对象必须通知其它对象,而它又不能假定其它对象是谁。换言之, 你不希望这些对象是紧密耦合的
-52-
实现Observer
public class Button { private int myDigit = INVALID_DIGIT; private LinkedList observers = null; boolean addObserver(ButtonListener bl) { return observers.add(bl); } void pressed() { ListIterator li = observers.listIterator(); while (li.hasNext()) { ButtonListener bl = (ButtonListener)li.next(); bl.buttonPressed(myDigit); } }\r} // 忽略了对类型不匹配的检查
-53-
GoF:Command模式
目的:将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可撤消的操作\r结构:\r适用性:\r抽象出待执行的动作以参数化某对象\r在不同的时刻指定、排列和执行请求\r支持取消操作\r支持修改日志,这样当系统崩溃时,这些修改可以被重做一遍\r用构建在原语操作上的高层操作构造一个系统\r
-54-
运用Command模式的Button
interface Command { public void do(int signal); }\r\rpublic class Button { Command cmd = null; int digit = 0; public Button(Command cmd) { this.cmd = cmd; } public Command setCommand(Command cmd) { Command old = this.cmd; this.cmd = cmd; return old; } public final void pressed() { cmd.do(digit); }}
-55-
更多的模式…
Null Object\rAbstract Server\rActive Object\r……
-56-
NULL Object模式
Employee e=DB.getEmployee(“Bob”);\rif (e!=null