大B:“我来讲讲我个人对设计模式的理解吧。”
小A:“呵呵!好啊!”
大B:“也许能让你更好地理解23种设计模式。”
1、Adapter(适配器)模式:旨在提供用户期望的接口,以便利用具有不同接口的类的服务。
(1)个人理解:实际上只是把客户调用,转变为调用已经存在的方法。适配器的作用可以理解为提供一个人人皆知的,顾名思义的新方法名。
(2)提示代码:
publicdoublegetMass(){
returnrocket.getMass(simTime);
}
2、Facade(外观)模式:旨在为子系统提供一个接口,使之更加容易使用。
(1)经典范例:JOptionPane类,JOptionPane.showConfirmDialog(……)
(2)个人理解:构建一个个目的明确的类,比如典型的静态方法的使用。
(3)提示代码:
intoption;
option=JOptionPane.showConfirmDialog(……);//静态方法创建对话框
(4)提示关键字:外观类,工具类,实例类
3、Composite(组合)模式:旨在让用户能够用统一的接口处理单个对象以及对象组合。
(1)经典范例:组合,树,环
(2)个人理解:其他很多模式的基础,群组可以包含群组或者个体,群组和个体有共同的接口。
(3)提示代码:
MachineComponentmc=(MachineComponent)i.next();
count+=mc.getMachineCount();
(4)提示关键字:递归
4、责任型模式Bridge(桥接)模式:旨在将依赖抽象操作的类与这些抽象操作的实现相分离,从而使得抽象类与实现能够独立变化。
(1)经典范例:驱动程序
(2)个人理解:将抽象和方法的具体实现分离,抽象类中包含一个driver对象,driver对象即是对方法的具体实现。
(3)提示关键字:装载
5、ChainofResponsibility(责任链)模式:旨在将一个方法调用请求沿着责任链依次转发给下一个对象,让每个对象都有一次机会决定自己是否处理该请求,从而降低请求的发送者与其接受者之间的耦合程度。
(1)个人理解:寻找责任的请求在链中传递,如果责任人已经找到则终止,否则继续向其他对象转发责任。
(2)提示代码:
publicEngineergetResponsible(VisualizationItemitem){
if(iteminstanceoftool){
Toolt=(Tool)item;
returnt.getToolCart().gerResponsible();
}
if(iteminstanceofToolCart){
ToolCarttc=(ToolCart)item;
returntc.gerResponsible();
}
}
(3)提示关键字:转发
6、Singleton(单例)模式:旨在确保某个类只有一个实例,并且为之提供一个全局访问点。
(1)个人理解:创建一个类的唯一实例,可以作为全局变量。
(2)提示代码:
publicstaticFactorygetFactory(){
if(factory……null)
factory=newFactory();
returnfactory;
}
7、Observer(观察者)模式:旨在在多个对象之间定义一对多的依赖关系,以便当一个对象状态改变时,其他所有依赖这个对象的对象都能够被通知,并自动更新。
(1)经典范例:GUI(MVC中分离M和VC)
(2)个人理解:当一个对象发生改变的时候,其他关心该对象的对象能够得到通知,并且更新自身状态。
(3)提示代码:
publicvoidnotifyObservers(){
observers.update();
}
(4)提示关键字:注册,监听
8、Mediator(中介者)模式:旨在定义一个对象来封装一组对象之间交互的方式,这样可避免对象间的显示引用,而且还可以独立对这些对象的交互进行修改。
(1)经典范例:GUI(特指MVC中的controller)
(2)个人理解:中介者类专门用于处理对象间的交互,与GUI的布局组件分离
(3)提示代码:
publicvoidsetLocation(Machinevalue){
returnmediator.set(this,value);
}
9、Proxy(代理)模式:旨在为某个对象提供一个代理来控制对该对象的访问。
(1)经典范例:图像代理(长时间载入内存前的Loading提示)
(2)个人理解:提供一个代理来承担责任(转发请求),实际操作的对象并不是根本对象,而是一个用户和真正实现之间的中间角色。
(3)提示代码:
setImage(LOADING.getImage());
callbackFrame.repaint();
newThread(this).start();
(4)提示关键字:占位
10、Flyweight(享元)模式:旨在通过共享来为大量的细粒度对象提供有效的支持。
(1)个人理解:很多类具有相同的且不变的属性,可以将这些属性提取出来构成享元,在一个特定的工厂类中作为内部类,具有static的get方法,便于外部类共享。
(2)提示代码:
publicclassChemicalFactory{
privatestaticMaochemicals=newHashMap();
ChemicalImp{
//someattributesandmethods
}
static{
chemicals.put(newChemicalImp());
}
publicstaticChemicalgetChemical(Stringname){
return***;
}
}
(3)提示关键字:共享对象
11、Builder(生成器)模式:旨在把构造对象实例的代码逻辑移到要实例化的类的外部,以便于细化构造过程,或者简化对象。
(1)经典范例:解析文本构造对象
(2)个人理解:用一个builder类收集构造信息,在确定信息足够(或者满足构造的最低要求)的时候,再生成对象。
(3)提示代码:
Stringsample=“*****”;
ReservationBuliderbuilder=newUnforgivingBuilder();
newReservationParser(builder).parse(sample);
Resercationres=builder.build();
(4)提示关键字:逐步构造
12、FactoryMethod(工厂方法)模式:旨在定义一个用于创建对象的接口,同时控制对哪个类进行实例化。
(1)经典范例:迭代器
(2)个人理解:为相关的多个类提供一个共同的接口,客户不需要知道该实例化哪个类,具体实例化的类由服务的提供者决定。
(3)提示代码:
Listlist=Arrays.asList(newString[]{“1”,“2”,“3”});
Iteratoriter=list.iterator();
(4)提示关键字:共同接口
13、AbstractFactory(抽象工厂)模式:旨在创建一系列相互关联或相互依赖的对象。
(1)经典范例:GUI工具包
(2)个人理解:创建一系列相关的对象,也就是把创建一个大对象所需要的子操作聚合起来。
(3)提示代码:
publicJButtoncreateButtonOK(){
JButtonb=super.createButtonOk();
b.setIcon(getIcon(“images/123.gif”));
returnb;
}
(4)提示关键字:外观和感觉
14、Prototype(原型)模式:通过拷贝一个现有对象生成新的对象。
(1)个人理解:通过复制一个已经存在的对象,保存原来对象的状态,在此基础上进行进一步的改动。
(2)提示代码:
publicOzPanelcopy2(){
OzPanelresult=newOzPanel();
result.setBackground(this.getBackground());
//moreresult.set***methods……
returnresult;
}
(3)提示关键字:复制
15、Memento(备忘录)模式:旨在为对象提供状态存储和状态恢复功能。
(1)经典范例:撤销操作
(2)个人理解:使用栈进行撤销和恢复的操作,栈顶部是当前的状态。更多的,可以把相关状态进行持久性存储。
(3)提示代码:
publicvoidundo(){
if(!canUndo())return;
mementos.pop();
}
(4)提示关键字:redo,undo
16、TemplateMethod(模板方法)模式:旨在一个方法中实现一个算法,并遵循算法中某些步骤的定义,从而使得其他类可以重新定义这些新步骤。
(1)经典范例:(根据不同规则)排序
(2)个人理解:在算法的实现中,把一些需要自定义的部分(通常是算法的核心部分),留在外部的类来实现。并可以需要实现的部分设置钩子。
(3)提示代码:
Array.sout(rockets,newApogeeComparator());
publicclassApogeeComparatorimplementsComparator{
//basemethodaboutsort……
}
(4)提示关键字:算法框架+算法步骤
17、State(状态)模式:旨在将与状态有关的处理逻辑分散到代表状态的各个类中。
(1)个人理解:将所有的状态都构建成一个相应的类,它们的超类对外部各个事件提供相应的同意接口,使得调用者无需判断当前状态。
(2)提示代码:
publicclassDoor2extendsObservable{
publicvoidtouch(){
state.touch();
}
}
publicclassDoorOpenextendsDoorState{
publicvoidtouch(){
door.setState(door.STAYOPEN);
}
}
(3)提示关键字:状态处理分散
18、Strategy(策略)模式:旨在把可选的策略或方案封装到不同的类中,并在这些类中实现一个共同的操作。
(1)个人理解:为不同的解决方案建立类,在执行的时候选择一个策略执行。与State模式比较,两者很接近,前者倾向在可选的方案中选择,后者是在不同的状态之间迁移。
(2)提示代码:
privateAdvisorgetAdvisor(){
if(advisor……null){
if(promotionAdvisor.hasItem())
advisor=promotionAdvisor;
//maybemoreelseif
}
returnadvisor;
}
(3)提示关键字:策略选择+策略执行
19、Command(命令)模式:旨在将请求封装为一个对象,并将该请求对象作为参数;客户可以提供不同的请求对象,如队列请求,时间请求或者日志请求;也可以让客户准备调用该请求的特定上下文。
(1)经典范例:菜单命令(actionPerformed())
(2)个人理解:将方法(一般是execute()方法)封装在对象中,使用时直接调用相关mand对象的execute()方法即可。可以作为Template模式的替代模式。
(3)提示代码:
Commanddoze=newCommand(){
publicvoidexecute(){
//dosomething
}
}
publicclassCommandTimer{
Publicstaticlongtime(Commandmand){
mand.execute();
}
}
longactual=CommandTimer.time(doze);
(4)提示关键字:封装对象
20、Interpreter(解释器)模式:旨在使开发者可以根据自己定义的组合规则生成可执行的对象。
(1)个人理解:常与Command和Composite模式配合使用,对命令进行组合使用,有点像编程中使用语句构造功能。
(2)提示代码:
publicclassIfCommandextendsCommand{
protectedTermterm;
protectedCommandbody;
protectedCommandelseBody;
publicIfCommand(Termterm,Commandbody,CommandelseBody){
this.term=term;
this.body=body;
this.elseBody=elseBody;
}
publicvoidexecute(){
if(term.eval()!=null)
body.execyte();
else
elseBody.execute();
}
}
(3)提示关键字:解释器,组合对象
21、Decorator(装饰器)模式:旨在使开发者能够动态地组织对象的行为。
(1)经典范例:流和输出器,函数包装器
(2)个人理解:在运行时动态创建不同的变化
(3)提示代码:
BufferedOutputStreamout=
newBufferedOutputStream(
newGZIPOutputStream(
newFileOutputStream(args[1])));
(4)提示关键字:动态组合
22、Iterator(迭代器)模式:旨在为开发人员提供一种顺序访问集合元素的方法。
(1)个人理解:在新建一个结构的时候,为顺序访问其元素,可以同样新建一个对应的迭代器类。也可以自定义访问元素的其他方式(比如逆序)。
(2)提示代码:
Listemployees;
ListIteratorforward(employees);
ReverseListIteratorbackward(employees);
PrintEmployees(forward);
PrintEmployees(backward);
(3)提示关键字:访问元素
23、Visitor(访问者)模式:旨在让开发者能够在不修改现有类层次结构的前提下扩展该类层次结构的行为。
(1)个人理解:在开发类的时候,留有一个accept()操作,该操作接受一个visitor参数。在需要为类增加新的操作时,无需改变原来的类层次,直接编辑visitor中的visit操作,然后使用accept()方法接受即可。
(2)提示代码:
publicclassFindVisitorimplementsMachineVisitor{
publicMachineComponetfind(MachineComponetmc){
mc.accept(this);
}
publicvoidvisit(MachineCompositemc){
//dosomething
}
}
MachineComponentfactory=OozinozFactory.dublin();
MachineComponentmachine=newFindVisitor().find(factory,3404);
(3)提示关键字:不改变类层次附录:面向对象基础
小A:“为什么要‘面向对象’?”
大B:“面向对象方法使构建系统更容易,因为:解决正确的问题,正常工作,易维护,易扩充,易重用。大家发现面向对象更易理解,实现可以更简单。把数据和功能组合在一起简单而自然,分析和实现之间的概念跨度更小,设计良好的一组对象能弹性地适应重用和变化,可视化模型提供更有效的沟通,建模过程有助于创建通用词汇以及在开发者和用户/客户之间达成共识。非计算机编程人员也能理解对象模型,这些好处可以使用面向对象方法获得,但面向对象方法不能保证这一点。”
小A:“怎样才能变成优秀的面向对象设计者?”
大B:“只有靠经验和聪明的头脑才能做到。”
过程化方法(TheProceduralApproach)
小A:“怎样过程化方法?”
大B:“系统由过程(procedures)组成,过程之间互相发送数据,过程和数据各自独立,集中于数据结构、算法和运算步骤的先后顺序,过程经常难以重用,缺乏具有较强表现力的可视化建模技术,分析与实现之间需要进行概念转换,本质上是机器/汇编语言的抽象,从设计模型到代码实现跨度很大。”
面向对象方法
大B:“系统由对象组成,对象互相发送消息(过程调用)相关的数据和行为紧密地绑定在对象中,把问题领域建模成对象,要解决的问题自然的映射为代码的实现,可视模型表现力强,相对容易理解,集中于实现之前所确定的职责(responsibilities)和接口。强有力的概念:接口,抽象,封装,继承,委托(delegation)和多态。问题的可视模型逐渐进化成解决方案模型,设计模型与代码实现之间跨度较小努力缩减软件的复杂度。”
温度换算
大B:“下面我就以温度换算为例。”
过程/函数化方法
floatc=getTemperature();//假定为摄氏度。
floatf=toFarenheitFromCelcius(c);
floatk=toKelvinFromCelcius(c);
floatx=toKelvinFromFarenheit(f);
floaty=toFarenheitFromKelvin(k);
面向对象方法
Temptemp=getTemperature();
floatc=temp.toCelcius();
floatf=temp.toFarenheit();
floatk=temp.toKelvin();
包含有数据的Temp的内部单元是什么?
建模(Modeling)
小A:“成功的程序能解决真实世界的问题。”
大B:“嗯,是的。它们紧密对应于需要解决的问题。对问题领域和用户活动进行建模。”
小A:“建模促进与用户更好的可视化交流。”
大B:“成功的面向对象设计总是一开始就由领域专家和软件设计者建立一个反映问题领域的可视化的‘对象模型’。”
小A:“嗯。是的。”
大B:“你愿意让承包人在没有设计蓝图的情况下建造你的新房子吗?”
小A:“那当然不行啦。”
对象
大B:“你知道怎样去理解什么是对象吗?”
小A:“对象代表真实或抽象的事物,有一个名字,有明确的职责(well-definedresponsibilities),展示良好的行为(well-definedbehavior),接口清晰,并且尽可能简单、自相容,内聚,完备(self-consistent,coherent,andplete)。”
大B:“嗯,对。(通常)不是很复杂或很大,只需要理解自己和一小部分其他对象的接口,与一小部分其它对象协同工作(teamplayers),尽可能地与其它对象松散耦合(looselycoupled),很好地文档化,以便他人使用或重用,对象是类的实例,每一个对象都有唯一的标识,类定义一组对象的接口和实现,即定义了这些对象的行为,抽象类不能拥有实例,只要有抽象类(如宠物),通常就会有能够实例化的具体类(如猫,狗等),一些面向对象语言(如Smalltalk)支持元类(metaclass)的概念,程序员可以随时(on-the-fly)定义一个类,然后实例化。这种情况下,类也是一个对象,即元类。对象一旦实例化,就不能更改它的类。”
对象的特征
大B:“那你知道对象有什么特征吗?”
小A:“有唯一标识,可以分成许多种类(即类),可以继承或聚合。行为、职责明确,接口与实现分离,隐藏内部结构,有不同的状态,可以提供服务,可以给其它对象发送消息,从其它对象接收消息,并做出相应响应,可以把职责委托给其它对象。”
大B:“对,说得非常全面。”
类
小A:“怎么样才叫类呢?”
大B:“有公共的属性和行为的一组对象可以抽象成为类,对象通常根据你所感兴趣的属性而分类。”
小A:“喔。”
大B:“例如:街道,马路,高速公路……不同的程序对它们分类也不同。交通模拟器程序,单行道,双通道,有分车道的,住宅区的,限制通行的维护调度程序,路面材料,重型卡车运输类本身也可以有属性和行为。例如:养老金管理程序中的‘雇员’类雇员总数,雇员编制多少,不同语言对类的支持略有不同:Smalltalk把类当作对象(很有好处),C++提供最小限度的支持(有时会带来很多烦恼),Java位于上述两者之间,类也是对象,类可以有属性‘雇员’类可以有一个包含其所有实例的列表(list)‘彩票’类可以有一个种子(seed)用于产生随机票号,该种子被所有实例共享,类可以有行为,雇员”类可以有getEmployeeBySerialNum行为。‘彩票’类可以有generateRandomNumber行为。
封装
大B:“只暴露相关的细节,即公有接口(publicinterface)。”
小A:“封装什么?如何封装?”
大B:“隐藏‘齿轮和控制杆’只暴露客户需要的职责,防止对象受到外界干扰,防止其它对象依赖可能变化的细节,信息隐藏有助于对象和模块之间的松散耦合,使得设计更加灵活,更易于重用,减少代码之间的依赖,‘有好篱笆才有好邻居’。例如:汽车的气动踏板。”
小A:“怎样才能更好地实践?”
大B:“最佳实践:对象之间只通过方法(函数)互相访问。切忌直接访问属性。”
classPerson{
publicintage;
}
classBetterPerson{
privateintage;//changetodateOfBirth
publicintgetAge(){returnage;}
}
更完善的Person类可能是:privatedateOfBirth
抽象
小A:“什么是抽象?”
大B:“抽象使得泛化(generalizaions)成为可能,简化问题-忽略复杂的细节,关注共性,并且允许变更,人类经常使用泛化。当你看见约翰和简家里的那头灰德国牧羊犬时,你有没有……想到‘狗’这个词?抽象同样能简化计算机程序。例如,软件中有两个重要抽象:客户端和服务器(clientsandservers)。”
小A:“喔。”
大B:“在图形用户界面中,系统可能会询问用户各种问题:是或不是多选一?输入数字,任意文本问题统一处理这些问题会显得很简单,每一个问题都作为Question类的特例(specialization);程序只需维护这些问题的实例列表,分别调用各自的askTheUser()方法。”
继承
小A:“什么是继承?”
大B:“继承用于描述一个类与其它类的不同之处。例如:类Y像类X,但有下列不同……”
小A:“为什么使用继承?”
大B:“你有两种类型,其中一种是另一种的扩展。有时(不是所有时候)你想忽略对象之间的不同,而只关注它们的共同之处(基类)。这就是泛化。假如某系统需要对不同的形状进行操作(经典例子):有时你并不关心你正在操作的形状的种类(例如,移动形状时)有时你必须知道形状的种类(在显示器上绘制形状)”
小A:“怎样去理解派生类?”
大B:“派生类继承自基类;派生类扩展了基类;派生类是基类的特殊化(specialization)。派生类能够提供额外的状态(数据成员),或额外的行为(成员函数/方法),或覆盖所继承的方法。基类是所有它的派生类的泛化。如:通常所有宠物都有名字。基类(BaseClass)=父类(parentclass)=超类(superclass)派生类(DerivedClass)=子类(childclass)=子类(subclass)”
小A:“喔。”
大B:“继承含有(有些,不是全部)是一个(is-a)或是一种(is-a-kind-of)的关系,正方形是一种矩形(使用继承),Leroy是一种狗(不使用继承),传统的过程分析和设计中不能很好地模拟这种关系。继承是一种强有力的机制,使我们关注共性,而不是特定的细节。使得代码可以重用且富有弹性(能适应变化)。”
小A:怎样去实现继承?
大B:“实现继承(Implementationinheritance):派生类继承基类的属性和行为。”
小A:“又应该怎样去接口继承?”
大B:“接口继承(Interfaceinheritance):类实现抽象接口的方法,保留既定语义(intendedsemantics)C++允许多重实现继承。Java规定派生类只能有一个基类,但可以继承自多个接口。”
多态
小A:“什么是多态?”
大B:“多态是一种允许多个类针对同一消息有不同的反应的能力。对于任何实现了给定接口的对象,在不明确指定类名的情况下,就可以使用。例如:question.askTheUser();当然,这些不同反应都有类似的本质尽可能使用接口继承和动态(运行期)绑定Liskov替换原则:如果Y是X的子类,那么在任何使用X实例的地方都可以用Y的实例来替换。”
演示多态的Java代码
//File:question/QuestionTest.java
//下面的代码将输出什么?
//RefertotheBeginningJavalinkonthecoursewebsite.
packagequestion;
abstractclassQuestion{//Fullclassnameisquestion.QuestionTest
publicQuestion(Stringtext){//Constructor
theText=text;
}
publicabstractvoidaskTheUser();
protectedStringtheText;
}
classYesNoQuestionextendsQuestion{
publicYesNoQuestion(Stringtext){super(text);}
publicvoidaskTheUser(){
System.out.println(theText);
System.out.println(“YESorNO……?”);
}
}
classFreeTextQuestionextendsQuestion{
publicFreeTextQuestion(Stringtext){super(text);}
publicvoidaskTheUser(){
System.out.println(theText);
System.out.println(“Well……?Whatstheanswer……?”);
}
}
publicclassQuestionTest{
publicstaticvoidmain(String[]args){
Question[]questions=getQuestions();
for(inti=0;i