设计模式
本文章参考自headfirst设计模式,如有侵权请联系作者
看完这本书心得:该书主要就是教如何设计出更有弹性的系统。没想到花了十天才把这本书看完。这本书我觉得只要有面向对象基础都可以尝试看看。可以长长见识,耗时不多。
最大作用:帮助理解源码的含义,以及为何这么用。
设计模式
定义
模式实在某情境下,针对某问题的某种解决方案。
(模式必须应用一个重复出现的问题,即可以多次使用,像什么破窗找东西就不实际)
反模式
定义:反模式告诉你如何采用一个不好的解决方案解决一个问题。
作用:1. 反模式告诉你为何这个解决方案从长远看会造成不好的影响。
2. 除了告诉你什么解决方案不好之外,也会向你建议一些会引向好的解决方案的可能性。
阅读前须知
开发的目标就是简单(简单的方式行得通,满足需求,就别用模式),过度使用设计模式可能导致代码被过度工程化,模式会带来复杂性(产生过多的类和对象),模式只是指导方针,让设计变得更简单和系统更具备弹性(杀鸡焉用宰牛刀),
所有的设计都应该尽量保持简单,只有在需要实践扩展的地方,才值得使用复杂性模式
总结:总是使用满足需要的最简单解决方案,不管它用不用模式,让设计模式自然而然地出现在你的设计中,而不是为了使用而是用。并且牢记:你所遇到大多数的模式都是现有模式的变体,而非新模式。
好处
好处:
1.松耦合,建立弹性系统-》加快开发速度(因为松耦合,前后端可以同时进行)->分工明确,前端只负责前端,后端只负责后端。
2.共享词汇(一听就明白)
我建立的这个广播类,他会持续地追踪所有倾听他的对象,只要有数据进来,就会把消息发送给每一个倾听者。最棒的地方在于倾听者可以在任何时候加入这个广播类,也可以在任何时候从广播中删除。而这个广播类本身并不用知道这些倾听者的确切类,只要实现了正确的接口,就可以当倾听者。 —》只要说观察者就行了,简单清晰精确。
设计模式分类:
分类意义
怎么分类不重要,重要的是了解这些模式和它们之间的关系。分类,然后好比较可让你对模式有清晰的概念
就比如汽车也分为:跑车,经济车,卡车……,一旦有了分类或类目,你就可以方便地这么说:“如果你想从硅谷开车到圣克鲁斯,那么跑车将会是最好的选择”。或者“因为石油的市场状况日益恶化,所以应该购买经济车,比较省油。
通过分类,我们可以将一组模式视为一个群体。当我们需要一个创建型模式,但又不知掉确切是哪一个的时候,就可以用创建型模式这个词来统称它。
而且分类也有助于我们比较相同类目内的其他成员。比方说:“迷你车是最具有风格的小型车”。或者帮助我们缩小缩小范围,”我需要一部省油的车子“。
再者说,对于改变对象接口来说,适配器模是最好的结构型模式。
并且,类目还可以开发新的领域,比方说”我们真的想要开发一部跑车,具有法拉利的性能和Miata的价格“.(寿命短之类的缺陷)
所以类目可以让我们思考模式群组之间的关系,以及同一组模式内模式之间的关系,还可以让我们找出新的模式。但是为什么只是用三个类目呢?
(就像夜晚天空中的星星一样,你可以看见许多类目。“三”是一个适当的数目,并且是有许多人所决定出来的数目,他有助于更好地进行模式分类,但是的确有人建议用四个,五个或更多)
分类
结构型:通过用接口将实现与抽象联系起来的方式把已有对象组合起来进行建模(把类或对象组合到更大的结构中,[以获取新的结构或功能])。(外观与适配器、代理模式、装饰器模式、桥接模式、组合模式、享元模式)
行为型:(涉及类和对象如何交互以及分配职责。[对象之间的沟通与互连])通过对变化进行封装使得所建立的模型可以提供灵活的行为方式。(策略与观察者、模板方法模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式)
创建型:将对象实例化,这类模式都提供一个方法,将客户从所需要实例化的对象解耦(单例模式、抽象工厂与工厂方法、建造者模式、原型模式)
常见疑问:
问:为何装饰者模式被归类为结构类目中?不应该是行为类目吗?
答:是的,许多人都这么认为,四人组(设计成这样的四个人)的想法:结构型模式用来描述类和对象如何被组合以建立新的结构或新的功能。装饰者模式允许你通过“将某对象包装进另一个对象的方式",来组合对象以提供新的功能.所以焦点是在于如何动态地组合对象以获取功能,而不是行为型模式的目的——对象之间的沟通与互连。请牢记,这几个模式的意图并不相同,而这通常是了解某个模式属于哪个类目的关键。
其实还有两类:并发型模式和线程池模式,用一个图片来整体描述一下:
转至:
设计模式的三大分类
六大原则总结:
别人总结:
单一职责
开闭原则
里氏替换原则:
- 定义与目的:所有基类在的地方,都可以换成子类,程序还可以正常运行。这个原则是与面向对象语言的继承特性密切相关的。
- 作用方向(实现方式):对**继承(重用,扩展,但是耦合-》)**进行了规则上的约束(保留扩展,对重用进行限制-》降低耦合[替换时能正常运行])
- 子类必须实现父类的抽象方法,不能重写父类的非抽象方法。
- 可以重载(非覆盖),将方法前置条件(形参)设置比父类方法的输入参数更宽松,这样根据匹配原则,会优先匹配最接近的。
- 实现抽象方法时,方法的后置条件要比父类严格。(即不能返回比父类还大的范围)
- 子类可以有自己特有的方法
迪米特法则(即下面的最少知识原则):
只与你的直接朋友交谈,不跟“陌生人”说话
其含义是:如果两个软件实体无须直接通信,那么就不应当发生直接的相互调用,可以通过第三方转发该调用。其目的是降低类之间的耦合度,提高模块的相对独立性。
迪米特法则要求限制软件实体之间通信的宽度和深度,正确使用迪米特法则将有以下两个优点。
- 降低了类之间的耦合度,提高了模块的相对独立性。
- 由于亲合度降低,从而提高了类的可复用率和系统的扩展性。
缺点:
过度使用迪米特法则会使系统产生大量的中介类,从而增加系统的复杂性,使模块之间的通信效率降低。所以,在釆用迪米特法则时需要反复权衡,确保高内聚和低耦合的同时,保证系统的结构清晰。
从迪米特法则的定义和特点可知,它强调以下两点:
- 从依赖者的角度来说,只依赖应该依赖的对象。
- 从被依赖者的角度说,只暴露应该暴露的方法。
接口隔离原则:
定义:
1、客户端不应该依赖它不需要的接口。
2、类间的依赖关系应该建立在最小的接口上。
以上两个定义的含义是:要为各个类建立它们需要的专用接口,而不要试图去建立一个很庞大的接口供所有依赖它的类去调用。
接口隔离原则和单一职责的区别:
接口隔离原则和单一职责都是为了提高类的内聚性、降低它们之间的耦合性,体现了封装的思想,但两者是不同的:
- 单一职责原则注重的是职责,而接口隔离原则注重的是对接口依赖的隔离。
- 单一职责原则主要是约束类,它针对的是程序中的实现和细节;接口隔离原则主要约束接口,主要针对抽象和程序整体框架的构建。
https://www.jianshu.com/p/3232c9891403
我的总结:
抽象倒置原则
自己总结:
所有的原则都应该在有帮助的时候才遵守。所有的设计都不免需要折衷(在抽象和速度之间取舍,在空间和时间之间平衡……)。虽然原则提供了方针,但在采用原则之前,必须全盘考虑所有的因素。
松耦合(面向接口,增加复用潜力,依赖于抽象而不依赖于具体,降低对象之间的依赖,一个类应当只对应一个职能,),来建立弹性的OO系统,提高系统的可维护性。 松耦合-》易复用,易扩展,还可以加快效率(易多人开发)
易扩展:在不修改现有代码的情况下,可搭配新的行为。
常用到的设计原则:
- 面向接口编程,而非面向实现编程(多态)Animal a = new Dog();
//在设计模式中,所谓“实现一个接口”并“不一定”表示“写一个类,并利用implement关键词来实现某个java接口”。”实现一个接口“泛指“实现某个超类型(可以是类或者接口)的某个方法”。
-
多用组合,少用继承 (java不支持多重继承。使用类继承难松耦合);
-
封装变化。(状态,策略)
-
开放-关闭原则(对扩展开放,对修改关闭,结合上面的原则)(装饰者)
-
单一责任:一个类应该只有一个引起变化的原因。
(内聚:一个类或一个模块被设计成只支持一组相关的功能)
当我们允许一个类不但要完成自己的事情(管理某种内聚),还同时要担负更多的责任(例如遍历)时,我们就给了这个类两个变化的原因。即当这个集合改变的话这个类也必须改变,遍历的方式改变的话,这个类也必须改变。
-
最少知识原则(得墨忒耳法则):只和你的密友谈话。(只和朋友交谈) 外观模式跟最少知识原则的关系
解释:当你正在设计一个系统,不管是任何对象,你都要注意它所交互的类有哪些,并注意它和这些类是如何交互的。
这个原则希望我们在设计中,不要让太多的类耦合在一起,免得修改系统中一部分,会影响到其他部分。
-
好莱坞原则:别调用(打电话给)我们,我们会调用(打电话给)你。
作用:**防止“依赖腐败”**的方法。如:当高层组件依赖低层组件,而低层组件又依赖高层组件,而高层组件又依赖边侧组件,而边侧组件又依赖低层组件时,依赖腐败就发生了。这样的情况,是无法分清系统是如何设计的。
-
要依赖抽象,不要依赖具体类。(依赖倒置原则)(抽象工厂)
-
为交互对象之间的松耦合设计而努力。(观察者)
单例模式,策略模式
单例懒汉式代码
** DCL(double check lock)模式为什么要使用volatile,防止半初始化。 **
如T t = new T() ; 在汇编中有如下几步:
- 0 new #2 ;先进行new操作,创建一个空间并赋默认值0.
- 3 dup ;在操作数栈上把这个引用的地址复制一份
- 4 invokespecial #3<T.> ;在根据引用的地址调用它的构造方法
- 7 astore_1 把整个引用的值赋值给t。
- 8 return
当jvm进行优化发生指令重排序(对于单线程不影响最后结果,代码执行顺序会发生改变),即第3个指令会与第4个交换,多线程中会出现半初始化问题,一般情况在百万数据情况下不会出现,以上就可能出现。
单件模式的定义:
确保类只有一个实例,并提供一个全局访问点。
场景
注册表等只需要创建一个类的地方。
为什么要使用单件模式:
为什么要只创建一个对象:
避免程序的行为异常。比如注册表,多了会导致配置紊乱。再者printer spooler,计算机可以使用多个打印机,但当存在两个printer spooler就会出现打印错误,比如同时使用一个打印机则打印出错。
为什么使用单件模式不使用全局变量:
节省资源,延迟实例化,全局变量在一开始就要求实例化。
再者无法确保只创建一个实例 如:慎用全局变量,再则全局变量也会变相鼓励开发人员,用许多全局变量指向许多小对象来造成命名空间的污染。单件不鼓励这样的现象,但单件仍然可能被滥用
。
单例模式常见问题
策略模式(代码复用)
描述了怎样按需要在一组可替换的算法中选用算法,几把所定义的一些算法各自封装起来,可根据客户的需要分别使用它们。该模式可使算法独立变化而不影响他的客户。该模式既注重算法使用的灵活性,又注重对需求的变化性。
场景
需要扩展类但又不想全部继承其方法如鸭子类,不同类型鸭子有不同的quack和fly方式。
策略模式用到的原则:(为什么)
- 封装变化
- 针对接口(超类型)编程,而不是针对实现编程。
- 多用组合,少用继承 (将变化的分离出来用接口实现,作为组合)
(解决了继承复用代码功能,但不需要全部继承,就是因为针对变化的部分)
策略回顾遇到的问题
1.怎么实现的?(将经常变化的地方封装[拿出来作为接口],在让其他类实现[方便扩展],然后再在Context类中组合进去【即添加变化类的接口】,在调用即可)
//比如duck类中的fly经常需要修改或者扩展。,就把fly封装起来,让接口去实现。 flyBehavior
//Context类
public abstract class Duck{
FlyBehavior flyBehavior;
public Duck(){
}
public void peformFly(){
flyBehavior.fly();//fly行为由被封装为flyBehavior
}
public void setFlyBehavior(FlyBehavior flyBehavior){ this.flyBehavior = flyBehavior;} //也可以放到构造器中,不过就不够动态
}
//ContextConcrete类
public class ModelDuck extends Duck{
FlyBehavior flyBehavior;
public ModelDUck(){
flyBehavior = new FlyNoWay();// fly:I can't fly;
}
}
//封装变化为接口
public interface FlyBehavior{
void fly();
}
//接口实现
public class FlyNoWay implements FlyBehavior {
public void fly(){
System.out.println("I can't fly");
}
}
public class FLyRocketPowered implements FLyBehavor{
public void fly(){
System.out.println("I'm flying with a rocket");
}
}
//Test
public class Test{
public static void main(String args[]){
Duck model = new ModelDuck();
model.performFly(); //i can't fly.
model.setFlyBehavior(new FlyRocketPowered());
model.performFly(); // i'm flying with a rocket.
}
}
观察者模式:
定义(发布与订阅)
用于定义对象间的一对多的依赖关系,当一个对象发生变化时,所有依赖他的对象都将得到通知并自动更新。
以下是两种形式:建议使用第一种方式(subject)。
第二种方式(Observable)违背了针对接口编程以及面向组合而非继承的原则。但java内置支持。
场景
出版(subject(接口)/observable(类))与订阅(observer)
报纸出版者和订阅者的关系。
原则:(为什么)
为了交互对象之间的松耦合设计而努力。
回顾时存在的疑问
怎么拉而不是自动强行推送??
在update方法的形参改为Subject/Observable,将Subject的变量用get公开获取。(这也是拉的缺点,数据被公开获取)
对比推/拉代码即可观察出,
推送是将所有参数发过去,update中接受所有参数。
拉是传递一个Subject/Observable对象,并且要让其参数变量可以公开获取
拉:
class CurrentCondition implements Observer,DisplayElement{
void update(Subject subject,Object args){
//WeatherDate entends Subject
if(sub instanceOf WeatherData){
WeatherData weatherData = (WeatherData) sub;
//通过get获取相关参数
this.temperature = weatherData.getTemperature()j;
//...
this.humidity = weatherData.getHumidity();
display();//就是一个输出获取参数值的作用
}
}}
推:
class CurrentCondition implements Observer,DisplayElement{
void update(double temperature,double humidity,double pressure){
//通过推送过来的值进行赋值
this.temperature = weatherData.getTemperature();
//...
this.humidity = weatherData.getHumidity();
display();//就是一个输出获取参数值的作用
}
}}
class WeatherData implements Subject{
private ArrayList observers;
private double temperature;
//...//...others needs parameters
pubic WeatherData(){observers = new ArrayList();}
void notifyObservers(){
//可以加些限制条件 改变值大于多少才会通知,在setMeasurements方法中设置即可。
for (Observer obs:observers){
obs = observers.get(i);
obs.update(temperature,...) //推送直接将所有参数发送过去
}
}
}
装饰者模式
定义
装饰者模式动态地将责任附加到对象上。若要扩展功能,装饰者提供了比继承更有弹性的替代方案。
// Beverage beverage = new Milk(new Milk(new Espresso())); 可以用无数个装饰者类包装组件
具体实现:
public abstract class Beverage {
String description = "Unknwon Beverage";
public String getDescription(){
return description;
}
public abstract double cost();
}
类Espresso: extends Beverage
public Espresso() {
description = "Espresso";
}
类Milk: extends
public Milk(Beverage beverage){
this.beverage = beverage;
}
public String getDescription() {
return beverage.getDescription()+",Milk";
}
Test:
public static void main(String[] args) {
Beverage beverage = new Espresso();
beverage = new Milk(beverage);
beverage = new Milk(beverage);
// Beverage beverage = new Milk(new Milk(new Espresso()));
System.out.println(beverage.getDescription() + " $"+beverage.cost());
//输出Espresso,Milk,Milk $1.38 很神奇的通过递归叠加名称和金额
}
场景
星巴克Beverage(饮料)进行扩展。 (类数量太多,价格变动)
原则
-
类应该对扩展开放,对修改关闭。(开放-关闭原则)
缺点:引入新的抽象层次,增加代码的复杂度,。
需注意的地方:
1.难以理解 2.类型问题 3.因为组合原因会增加大量小对象导致程序很复杂。 以及包装过多。
工厂模式
定义
工厂方法模式定义了一个创建对象的接口,但由子类决定实例化的类是哪一个。工厂方法让类把实例化推迟到子类。
(将实例化放到一个抽象方法中,当需要创建时,才调用create,子类实现create)(原本是直接根据参数进行if判断)(但其实感觉差不多,就是添加种类时,可以到具体子类中修改)
理解:工厂方法让子类决定要实例化的类是哪一个。容易理解错误-》所谓的“决定”,并不是指模式允许子类本身在运行时做决定,而是指在编写创建者类时,不需要知道实际创建的产品时哪一个。选择了使用哪个子类,自然就决定了实际创建的产品时什么。
(比如:pizzastore时抽象类,不知道是选择CaliforniaPizzaStore子类还是NewYorkPizzaStore子类中的那种类型pizza(cheese,pepperoni)。
将创建对象代码集中起来,方便维护,将客户代码和真实的实现解耦。(针对接口实现)
场景
2.简单工厂不是一种设计模式,更像一种编程习惯。(经常被用到) (就是将需要new的地方法全部放在SimpleFactory种,让它来create()创建。
1.需要new的地方,将其用放到抽象方法中让子类去实现,不同子类实现不同特点的new 产品(加盟店)。
为什么
为什么在工厂模式代码中仍要new?对象创建是现实的,如果不创建任何对象,就无法创建java程序。
1.将创建对象代码集中起来,方便维护,将客户代码和真实的实现解耦。(针对接口实现)(通过将创建代码放入抽象方法中,让子类实现create)
常见疑惑
抽象工厂模式
定义
抽象工厂模式提供一个接口,用于创建相关或依赖对象的家族,而不需要明确指定具体类。
场景
披萨配料工厂。
原则
要依赖抽象,不要依赖具体类。(依赖倒置原则)
要满足该原则应尽量(有足够理由时,可以违反。比如一个不太会改变的类,直接实例化具体类也没什么大碍,比如String)以下三点:
1.变量不可以持有具体类的引用(使用工厂)
2.不要让类派生自具体类 (派生具体类,就会依赖于具体类。尽量派生自一个抽象[接口或抽象类])
3.不要覆盖基类中已实现的方法。 (如果覆盖基类已实现的方法,那么你的基类就不是一个真正适合被继承的抽象。基类中已实现的方法,应该由所有子类共享)
不使用工厂模式时,pizzastore依赖于任何一个具体的pizza类,每新增一个种类,就多一个依赖。
工厂方法与抽象工厂的联系和区别:
联系:
抽象工厂的方法经常以工厂方法的方式实现。
抽象工厂的任务是定义一个负责创建一组产品的接口。这个接口内的每个方法都创建一个具体产品,同时我们利用实现抽象工厂的子类来提供这些具体的做法。所以,在抽象工厂中利用工厂方法实现生产方法是相当自然的做法。
简单工厂,虽然不是真正的设计模式,但仍不失为一个简单的方法,可以将客户程序从具体类解耦。
作用:
这两种模式以及简单工厂都可以将对象的创建封装起来,从而减少应用程序和具体类之间的依赖,以便于得到更松耦合、更有弹性的设计。
区别
工厂方法使用继承:把对象的创建委托给子类,子类实现工厂方法来创建对象。
抽象工厂使用对象组合:对象的创建被是现在工厂接口所暴露出来的方法中。
内容为按照headfirst 设计模式.顺序学习(除非特意学习某个模式)
命令模式
定义
命令模式将“请求"封装成对象,以便使用不同的请求、队列或者日志来参数化其他对象。命令模式也支持可撤销的操作。
场景(为什么)
当需要“发出请求的对象”和“接受与执行这些请求的对象”解耦时使用命令模式
1.接待员(接受订单)与厨师:接待员无需知道订单内容的含义
2.遥控器与电灯打开(或者其他功能):遥控器无需知道按钮的实现(意义)。直接按就行。(将遥控器和电灯对象解耦)
从而不用不用写出这样的代码:
if(slot1 = Light) then light.on(),
else if(slot1 ==Hottub) then hottob.jetsOn(),
else if… //这样的代码每次添加新的物品都必需修改代码,这会造成潜在的错误,而且工作没完没了。(为什么)
3.日志恢复(通过命令模式记录保存之前的操作,在电脑死机时可以成批依次地调用命令进行恢复)以及事务系统(原子性,全做或全不做)
实现解耦(帮助理解)
遥控器和电灯(功能对象)完全解耦,即使再新的厂商对象也无需在RemoteControl中修改代码(NullCommand可以用作空对象[不实现功能的按钮],用于做扩展)
常见疑问
1.如何实现多次撤销?
使用栈保存之前的命令。每次撤销都是弹出最上面的命令(最后的一次操作)。
适配器与外观模式
适配器,装饰者,外观的作用(区别
适配器将一个对象包装起来以改变接口;
装饰者将一个对象包装起来以增加新的行为和责任;
外观将一群对象”包装“起来以简化其接口。
定义
适配器模式将一个类的接口,转换成客户期望的另一个接口。适配器让原本接口不兼容的类可以合作无间
场景
TurkeyAdapter:让火鸡能使用鸭子的方法。
插座适配,将两口和适配器配合变成三口(类似转接器的作用)
还可以用于兼容适配,比如 之前用枚举器,现在要用新的迭代器,但是也希望以前的代码也使用迭代器这时候就可以使用适配器适配兼容。
常见疑问
-
一个适配器需要做多少“适配”的工作?如果我需要实现一个很大的目标接口,似乎有“很多”工作要做?
的确如此。实现一个适配器所需要进行的工作,的确和目标接口的大小成正比。如果不用适配器,就必须改写客户端的代码来调用这个新的接口,将会花费许多力气来做大量的调查工作和代码改写工作。 -
一个适配器只能够封装一个类?
适配器模式的工作是将一个接口转换成另一个。虽然大多数的适配器模式所采取的例子都是让一个适配器包装一个被适配者。但我们知道这个世界其实复杂多了,所以你可能遇到一些状况,需要让一个适配器包装多个被适配者。这涉及另一个模式——外观模式。人们常常将外观模式和适配器模式混为一谈。 -
万一我的系统中新旧并存,那应该保存哪个?还是不用适配器更好?
可以创建一个双向适配器,支持两边的接口。 (实现涉及的两个接口即可)
对象适配器和类适配器
装饰者和适配器
外观模式
定义
外观模式提供了一个统一的接口,用来访问子系统中的一群接口。外观定义了一个高层接口,让子系统更容易使用。
原则
最少知识原则(得墨忒耳法则):只和你的密友谈话。
解释:当你正在设计一个系统,不管是任何对象,你都要注意它所交互的类有哪些,并注意它和这些类是如何交互的。
这个原则希望我们在设计中,不要让太多的类耦合在一起,免得修改系统中一部分,会影响到其他部分。
如何遵循此原则?
只应该调用属于以下范围的方法:
- 该对象本身
- 被当作方法的参数而传递进来的对象
- 此方法所创建或实例化的任何对象
- 对象的任何组件。
采用最少知识原则有什么缺点吗?
是的,虽然这个原则减少了对象之间的依赖,研究显示这会减少软件的维护成本;但是采用这个原则也会导致更多的”包装“类被制造出来,以处理和其他组件的沟通,这可能会导致复杂度和开发时间的增加,并降低运行时的性能。
常见问题
-
如果外观封装了子系统的类,那么需要底层功能的客户如何接触这些类?
外观没有”封装“子系统的类,只是提供简化的接口。所以客户如果觉得有必要,依然可以直接使用子系统的类。即外观模式可以在提供简化接口的同时,依然将系统完整的功能暴露出来,以供有需要的人使用。
-
一个子系统可以有多个外观。
-
除了能够提供一个比较简单的接口之外,外观模式还有其他优点吗?
外观模式允许你将客户实现从任何子系统中解耦。比方说:你得到了大笔加薪,所以想要升级你的家庭影院,采用全新的和以前不一样接口的组件。如果当初你的客户代码是针对外观而不是针对子系统编写的,现在就不需要改变客户代码,只需要修改外观代码(而且有可能厂商会提供新版的外观代码)。
-
我可不可以这样说,适配器模式和外观模式的差异在于:适配器包装一个类,而外观可以代表许多类?
不对!提醒你,适配器模式将一个或多个类接口变成客户所期望的一个接口。虽然大多数教科书所采用的例子中适配器只适配一个类,但是你可以适配许多类来提供一个接口让客户编码。类似地,一个外观也可以只针对一个拥有复杂接口地类提供简化的接口。两种模式的差异,不在于他们”包装"了及各类,而在于他们的意图。适配器模式的意图是,”改变“接口来符合客户的期望;而外观模式的意图是,提供子系统的一个简化接口。
外观模式回顾存在疑问
1.外观模式是怎么只提供一个接口的?最少知识原则又跟它有什么关系?
模板方法模式(代码复用)
定义
模板方法模式在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。
(即定义一个算法步骤,让子类为一个或多个步骤提供实现)
场景
咖啡和茶的制作方式相似,则可以将相同地方提取出来,不同的地方抽象,泛化名称,放到咖啡因饮料(基类)的一个制作方法中。
数组的sort方法也是模板方法。 需要子类实现Comparable中的CompareTo
为什么说数组的sort也算是一个模板方法?
这个模式的重点在于提供一个算法,并让子类实现某些步骤而数组的排序做法很明显地并非如此!但是我们都知道,荒野中地模式并非总是如同教科书例子一般地中规中矩,为了符合当前地环境和实现地约束,他们总是要被适当地修改。这个Array类sort()方法的设计者受到一些约束。通常我们无法设计一个类继承Java数组,而sort()方法下能够适用于所有的数组(每个数组都是不同的类).所以他们定义了一个静态方法,而由被排序的对象内的每个元素自行提供比较大小的算法部分。所以,这虽然不是教科书上的模板方法,但它的实现仍然符合模板方法的精神。再者,由于不需要继承数组就可以使用这个算法,这样使得排序变得更有弹性、更有用。
策略模式和模板方法
排序实现实际上看起来更像是策略模式,而不是模板方法模式,为什么归为模板方法模式?
你之所以会这么认位,可能是因为策略模式使用对象组合,在某种程度上,你是对的——我们使用数组对象排序我们的数组,这部分和策略模式非常相似。但是请记住,在策略模式中你所组合的类实现了整个算法。数组所实现的排序算法并不完整,它需要一个类填补compareTo()方法的实现。因此我们认为这更像模板方法。
两者对话:
模板方法 | 策略 |
---|---|
我定义一个算法家族,并让这些算法可以互换。正因为每一个算法都被封装起来了,所以客户可以轻易使用地不同的算法。 | |
嘿!听起来好像是我在做的事情。但是我的意图和你不一样:我的工作是要定义一个算法的大纲,而由我的子类定义其中某些步骤的内容。这么一来,我在算法中的个别步骤可以由不同的实现,但是算法的结构维持不变。而你似乎必须要放弃对算法的控制。 | |
我不确定话可以这么说……更何况,我并不是使用继承进行算法的实现,我是通过对象组合的方式,让客户可以选择算法实现。 | |
这我记得。但是我对算法有更多的控制权,而且不会重复代码,事实上,除了极少一部分之外,我的算法的每一个部都是相同的,所以我的类比你的有效率得多。会重复使用到的代码,都被我放进了超类中,好让所有的子类共享。 | |
你或许更有效率一点(只是一点点),也的确需要更少的对象。和我所采用的委托模型比起来,你也没有那么复杂。但是因为我使用对象组合,所以我更有弹性。利用我,客户就可以在运行时改变他们的算法,而客户所需要做的,只是改用不同的策略对象罢了。拜托作者选择把我摆在第一章,这不是没有道理的! | |
好吧,我真替你感到高兴,但是你别忘了,环顾四周,我可是最常被使用的模式。为什么呢?因为我在超类中提供了一个基础的方法,达到代码的复用,并允许子类指定行为。我相信你会看到这一点在创建框架时是非常棒的! | |
也许呢……但是,别忘了依赖!你的依赖程度比我高。 | |
这话怎么说?我的超类时抽象的。 | |
但是你必须依赖超类中的方法的实现,因为这是你算法中的一部分。但我就不同了,我不依赖任何人;整个算法我自己搞定吧! |
其他例子:java.io的InputStream类有一个read()方法,是由子类实现的,而这个方法又会被read(byte b[],int off,int len)模板方法使用。
原则
好莱坞原则:别调用(打电话给)我们,我们会调用(打电话给)你。
作用:**防止“依赖腐败”**的方法。如:当高层组件依赖低层组件,而低层组件又依赖高层组件,而高层组件又依赖边侧组件,而边侧组件又依赖低层组件时,依赖腐败就发生了。这样的情况,是无法分清系统是如何设计的。
做法:低层组件可以参与计算,低层组件不可以直接调用高层组件,高层组件控制何时以及如何让低层组件参与。
好莱坞原则和依赖倒置原则之间的关系如何?
依赖倒置原则教我们尽量避免使用具体类,而多使用抽象。而好莱坞原则是用在创建框架或组件上的一种技巧,好让低层组件能够被挂钩进计算中,而且又不会让高层组件依赖低层组件。**两者的目标都在于解耦,但是依赖倒置原则更加注意如何在设计中避免依赖,好莱坞原则则教我们一个技巧,创建一个有弹性的设计,允许低层结构能够互相操作,而又防止其他类太过依赖他们。
低层组件不可以调用高层组件的方法吗?
并不尽然。事实上,低层组件在结束时,常常会调用从超类中继承来的方法。我们所要做的是,避免让高层和低层组件之间有明显的环状依赖
常见疑问
-
什么时候使用抽象方法,什么时候使用钩子(hook)?
当子类必须提供某个方法或步骤的实现时,使用抽象方法。如果算法中的某个部分是可选的,那么这个部分可以使用钩子,子类可以实现这个钩子,也可以选择不实现。
-
使用钩子的真正目的?
1)让子类实现算法中可选的部分。(或者在钩子对于子类的实现并不重要的时候)
2)让子类能够有机会对模板方法中即将发生的(或刚刚发生的)步骤作出反应。比如说,名为justReOrderedList()的钩子方法允许子类在内部列表重新组织后执行某些动作(例如在屏幕上重新显示数据)。钩子也可以让子类有能力为其抽象类做一些决定。
-
子类必须实现抽象类中所有的方法。
-
似乎我应该保持抽象方法的数目越少越好,否则,在子类中实现这些方法将会很麻烦?
可以让算法内的步骤不要切割的太细。但是如果步骤太少的话,会比较没有弹性,要自己判断折衷,也请记住,某些步骤是可选的,所有你可以将这些步骤是现成钩子,而不是实现成抽象方法,这样就可以让抽象类的子类减轻负担。
迭代器与组合模式
定义
描述:提供一个方式来遍历集合,而无需暴露集合的实现
迭代器模式提供一种方法顺序访问一个聚合对象中的各个元素,但又不暴露其内部的表示。
(将调用者和集合操作【遍历】解耦)
常见疑问
1.让方法不生效:throw new lang.UnsupportedOperationException。
2.在多线程的情况下,可能会由多个迭代器引用同一个对象集合。remove()会造成怎样的影响?
后果并没有指明,所以很难预料。当你的程序在多线程的代码中使用到迭代器时,必须特别小心。
场景
在使用ArrayList和数组的情况下的集合代码都可以通过迭代器来循环不需要写两遍循环代码,且实现其接口还可以隐藏内部表示。
常见疑问
-
我听说过“**内部的”迭代器和“外部的”的迭代器。**这是什么?我们在前面例子中实现的是哪一种?
我们实现的是外部的迭代器,也就是说,客户通过调用next()取得下一个元素。而内部的迭代器则是由迭代器自己控制。你必须将操作传入给迭代器比外部迭代器更没有弹性。然而,某些人可能认为内部的迭代器比较容易使用,因为只需将操作告诉它,它就会帮你做完所有事情。
-
对于散列表这样的集合,元素之间并没有明显的次序关系,我们该怎么办?
迭代器意味着没有次序(java.util.Enumeration是一个有次序的迭代器实现)。只是取出所有的元素,并不表示取出元素的先后就代表元素的大小次序。对于迭代器来说,数据结构可以是有次序的,或者是没有次序的,甚至数据可以是重复的。除非某个集合的文件有特别说明,否则不可以对迭代器所取出的元素大小顺序做出假设。
组合模式
定义
描述:客户可以将对象和集合以及个别对象一视同仁
组合模式允许你将对象组合成树形结构来表现“整体/部分”层次结构。组合能让客户以一致的方式处理个别对象以及对象组合。
场景
在使用ArrayList和数组的情况下的集合代码都可以通过迭代器来循环不需要写两遍循环代码,且实现其接口还可以隐藏内部表示。在这样的组合下,再在这数组或ArrayList中添加子数组或者子ArrayList。
常见疑问
-
组件、组合、树?我被搞混了?。
组合包含组件。组件有两种:组合和叶节点元素。听起来像递归是不是?组合有一群孩子,这些孩子可以时别的组合或者叶节点元素。当你用这种方式组织数据的时候,最终会得到树形结构(正确的说法是由上而下的树形结构),根部是一个组合,而组合的分支逐渐往下延伸,直到叶节点为止。
-
组合跟迭代器又有什么关系?
合作无间。
代码举例:
import java.util.ArrayList;
import java.util.Iterator;
abstract class MenuComponent{
public void add(MenuComponent menuComponent){
throw new UnsupportedOperationException();
}
public void remove(MenuComponent menuComponent){
throw new UnsupportedOperationException();
}
public MenuComponent getChild(int i){
throw new UnsupportedOperationException();
}
public String getName(){
throw new UnsupportedOperationException();
}
public String getDescription(){
throw new UnsupportedOperationException();
}
public boolean isVegetarian(){
throw new UnsupportedOperationException();
}
public double getPrice(){
throw new UnsupportedOperationException();
}
public void print(){
throw new UnsupportedOperationException();
}
}
class MenuItem extends MenuComponent{
String name;
String description;
boolean vegetarian;
double price;
public MenuItem(String name,String description,boolean vegetarian,double price){
this.name =name;
this.description = description;
this.vegetarian = vegetarian;
this.price =price;
}
public String getName(){
return name;
}
public String getDescription(){
return description;
}
public double getPrice(){
return price;
}
public boolean isVegetarian(){
return vegetarian;
}
public void print(){
System.out.print(" "+getName());
if(isVegetarian()){
System.out.print("(v)");}
System.out.println(", "+getPrice());
System.out.println(" -- "+getDescription());
}
}
class Menu extends MenuComponent{
ArrayList menuComponents = new ArrayList();
String name;
String description;
public Menu(String name,String description){
this.name = name ;
this.description =description;
}
public String getName(){
return name;
}
public String getDescription(){
return description;
}
public void add(MenuComponent menuComponent){
menuComponents.add(menuComponent);
}
public void remove(MenuComponent menuComponent){
menuComponents.remove(menuComponent);
}
public MenuComponent getChild(int i){
return (MenuComponent)menuComponents.get(i);
}
public void print(){
System.out.print("\n"+getName());
System.out.println(", "+getDescription());
System.out.println("-------------------");
Iterator iterator = menuComponents.iterator();
while (iterator.hasNext()){ //使用组合解决循环复用,使用迭代器配合组合 遍历所有项目
MenuComponent menuComponent = (MenuComponent)iterator.next();
menuComponent.print();
}
}
}
class Waitress{
MenuComponent allMenus;
public Waitress(MenuComponent allMenus){
this.allMenus = allMenus;
}
public void printMenu(){
allMenus.print();
}
}
public class MenuTestDrive {
public static void main(String[] args) {
MenuComponent pancakeHouseMenu = new Menu("PANCAKE HOUSE MENU","Breakfast");
MenuComponent dinnerMenu = new Menu("DINER MENU","Lunch");
MenuComponent dessertMenu = new Menu("DESSERT MENU","Dessert of course");
MenuComponent cafeMenu = new Menu("CAFE MENU","Dinner");
MenuComponent allMenus = new Menu("ALL MENUS","All menus combined");
allMenus.add(pancakeHouseMenu);
allMenus.add(dinnerMenu);
allMenus.add(cafeMenu);
dinnerMenu.add(new MenuItem("Pasta","Spaghetti with Marinara Sauce,and a slice of sourdough bread",true,3.89));
dinnerMenu.add(dessertMenu);
dessertMenu.add(new MenuItem("Apple Pie",
"Apple pie with flakey crust ,topped with vanilla ice cream", true,1.59));
Waitress waitress = new Waitress(allMenus);
waitress.printMenu();
}
}
我:到底怎么回事?首先说“一个类一个责任”,现在却给我们一个让一个类有两个责任的模式。组合模式不但要管理层次结构,而且还要执行菜单的操作。
设计者:你的观察有几分真实性。我们可以这么说,组合模式以单一责任设计原则换取透明性。什么是透明性?通过让组件的接口同时包含一些管理子节点和叶节点的操作,客户就可以将组合和叶节点一视同仁。(无法统一处理菜单和菜单项就会失去透明性)即看不见-》不可见。
也就是说,一个元素究竟是组合还是叶节点,对客户是透明的。 现在我们在MenuComponent类中同时具有两种类型的操作(例如试图把菜单添加到菜单项),所以我们失去了一些“安全性"这是设计上的抉择;我们当然也可以采用另一种方向的设计,**将责任区分开来放在不同的接口中。**这么一来,设计上就比较安全,但我们也因此失去了透明性,客户的代码将必须用条件语句和instanceof操作符处理不同类型的节点。 所以,回到你的问题,这是一个很典型的折衷案例。==尽管我们受到设计原则的指导,但是,我们总是需要观察某原则对我们的设计所造成的影响。有时候,我们会故意做一些看似违反原则的事情。==然而,在某些例子中,这是观点的问题;比方说,让管理孩子的操作(例如add(),remove(),getChild())出现在叶节点中,似乎很不恰当,但是换个角度看,你可以把叶节点视为没有孩子的节点。
帮助理解对答:
状态模式
定义
定义:状态模式允许对象在内部状态改变时改变它的行为,对象看起来好像修改了他的类.
解释:==状态模式将状态封装成为独立的类,并将动作委托到代表当前状态的对象,我们知道行为会随着内部状态而改变。比如糖果机,在NoQuarterState或HasQuarterState两种状态时,投入25分钱会有不同的行为。==一个对象“看起来好像修改了它的类”是什么意思呢?从客户视角来看:如果说你使用的对象能够完全改变它的行为,那么你会觉得这个对象实际上是从别的类实例化而来的。然而,实际上,你知道我们是在使用组合通过简单引用不同的状态对象来造成类改变的假象。
描述:封装基于状态的行为,并将行为委托到当前状态(对象)。
封装:将每个状态封装进一个类-》改变局部化(不用到大量if中改变)
场景
糖果机根据相应行为进入相应的状态。
不使用状态模式:(要扩展时混乱):
使用状态模式:
解决的问题:
- 将每个状态的行为局部化到自己的类中。
- 将容易产生问题的if语句删除,以方便日后的维护。
- 让每一个状态“对修改关闭”,让糖果机“对扩展开放”,因为可以加入新的状态类。
- 创建一个新的代码基和类结构,这更能映射万能糖果公司的图,而且更容易阅读和理解。
策略模式和状态模式
基本常识:策略模式和状态模式是双胞胎,在出生时才分开。你已经知道了,策略模式是围绕可以互换的算法来创建业务的。状态走的是更崇高的路,状态模式通过改变对象内部的状态来帮助对象控制自己的行为。
策略模式和状态模式有相同的类图,但他们的意图不同。
状态:
策略:
区别:
策略模式通常会用行为或算法来配置Context类。状态模式允许Context随着状态改变而改变行为。
策略:有一个可以实例化的类,而且通常给它一个实现某些行为的策略对象。
状态: Context对象会随着时间而改变状态,而任何的状态改变都是定义好的。
常见疑问
-
在GumballMachine中,状态决定了下一个状态应该是什么。ConcreteState总是决定接下来的状态是什么吗?
不,并非总是如此,Context也可以决定状态转换的流向。一般来说,当状态转换是固定的时候,就适合放在Context;然而,当转换是更动态的时候,通常会放在状态类中(例如,在GumballMachine中,由运行时糖果的数目决定状态要转换倒NoQuarter还是SoldOut)。将状态转换放在状态类中的缺点是:状态类之间产生了依赖。在我们的GumballMachine实现中,我们试图通过使用Context上的getter方法把依赖减到最小,而不是显示硬编码具体状态类。请注意,在做这个决策的同时,也等于是在为另一件事情做决策:当系统进化时,究竟哪个类是对修改封闭(Context还是状态类)的。
-
客户会直接和状态交互吗?
不会。状态是用在Context中来代表它的内部状态以及行为的,所以只有Context才会对状态提出请求。客户不会直接改变Context的状态。全盘了解状态是Context的工作,客户根本不了解,所以不会直接和状态联系。
-
如果在我的程序中Context有许多实例,这些实例之间可以共享状态对象吗?
是的,绝对可以,事实上这是很常见的做法。但唯一的前提是,你的状态对象不能持有它们自己的内部状态;否则就不能共享。想要共享状态,你需要把每个状态都指定到静态的实例变量中。如果你的状态需要利用到Context中的的方法或者实例变量,你还必须在每个handler()方法内传入一个context的引用。
-
使用状态模式似乎总是增加我们设计中类的数目。请看GumbleMachine的例子,新版本比旧版本多出了许多类!
没错。**在个别的状态类中封装状态行为,结果总是增加这个设计中类的数目。这就是为了要获取弹性而付出的代价。**除非你的代码是一次性的,可以用完就扔掉(是呀!才怪!),那么其实状态模式的设计是绝对值得的。其实真正重要的是你暴露给客户的类数目,而且我们有办法将这些额外的状态类全都隐藏起来。
让我们看一下另一种做法:如果你有一个应用,他有很多状态,但是你决定不将这些状态封装在不同的对象中,那么你就会得到巨大的、整块的条件语句。这会让你的代码不容易维护和理解。通过使用许多对象,你可以让状态变得很干净,在以后理解和维护它们时,就可以省下很多的工夫。
-
状态模式类图显示State是一个抽象类,但你不是使用接口实现糖果机状态的吗?
是的。如果我们没有共同的功能可以放进抽象类中,就会使用接口。在你实现状态模式时,很可能想使用抽象类。这么一来,当你以后需要在抽象类中加入新的方法时就很容易,不需要打破具体状态的实现。
-
我们为什么需要WinnerState?为什么不直接在SoldState中发放两颗糖果?
这个一个好问题。这两个状态几乎一样,唯一的差别在于,WinnerState状态会放两颗糖果。你当然可以将发放两颗糖果的代码在SoldState中,当然这么做有缺点,因为你等于是将两个状态用一个状态类来代表。这样做你牺牲了状态类的清晰易懂来减少一些冗余代码。你也应该考虑到在前面的章节中所学到原则:一个类,一个责任。将WinnerState状态的责任放进SoldState状态中,你等于是让SoldState状态具有两个责任。那么促销结束之后或者赢家的几率改变之后,你又该怎么办呢?所以,这必须用你的智慧来做折衷。
代理模式
定义
代理模式为另一个对象提供一个替身或占位符以控制对这个对象的访问。
描述:包装另一个对象,并控制对它的访问。
相关类图:
RMI (远程代理)
步骤:
1.制作一个远程接口Remote(MyRemote implements Remote),再添加客户需要调用的方法
2.制作远程接口的实现。(MyRemoteImpl Extends UnicastRemoteObject implements MyRemote)
然后再通过注册服务{
try{ MyRemote service = new MyRemoteImpl();
Naming.rebind(“RemoteHello”,service);} catch(Exception ex){…}
3.产生Stub类和Skeleton类。 cmd: %rmic MyRemoteImpl
4.执行remiregistry。 开启注册表用于给客户端寻找相应的stub类,然后stub类将请求发送给Skeleton,然后Skeleton打包(序列化)将相应方法的返回值返回,stub在解包(反序列化)将结果返回给客户端。
ps:对于RMI,程序员最常犯的三个错误:
1)忘了启动远程服务之前先启动rmiregistry(要用Naming.rebind()注册服务,rmiregistry必须是运行的)
2)忘了让变量和返回值的类型成为可序列化的类型(这种错误无法在编译器发现,只会在运行时发现)。
3)忘了给客户提供stub类。
ps:关于客户如何取得stub类?
我听说,在Java 5 ,甚至连stub都不需要产生了,这是真的吗?
是真的。Java 5的RMI和动态代理搭配使用,动态代理动态产生stub,远程对象的stub是java.lang.reflect.Proxy实例(连同一个调用处理器),它是自动产生的,来处理所有把客户的本地调用变成远程调用的细节。所以,你不再需要使用rmic,客户和远程对象沟通的一切都在幕后处理掉了。
5.启动服务 %java MyRemoteImpl
从哪里启动?可能是从你的远程实现类中的main()方法,也可能是从一个独立的启动类。在这个简单的例子中,我们是从实现类中的main()方法启动的,先实例化一个服务对象,然后到RMI registry中注册(Naming.rebind())。
虚拟代理
虚拟代理控制访问创建开销大的资源。
大概步骤:当对象在创建前和创建中时,由虚拟代理来扮演对象的替身。对象创建后,代理就会将请求直接委托给对象。(可以防止程序被挂起)
相关场景:设置CD封面的虚拟代理
核心代码:
常见疑问:
-
看起来,远程服务器和虚拟服务器差异非常大,它们真的是一个模式吗?
在真实的世界里,代理模式有许多变体,这些变体都有共通点:都会将客户对主题施加的方法调用拦截下来。
-
代理将客户从ImageIcon解耦了,如果它们之间没有解耦,客户就必须等到每幅图像都被取回,然后才能把它绘制在界面上。代理控制ImageIcon的访问,以便在图像完全创建之前提供屏幕上的代表。一旦ImageIcon被创建,代理就允许访问ImageIcon。
-
我们要如何让客户使用代理,而不是真正的对象?
好问题。一个常用的技巧是提供一个工厂,实例化并返回主题。因为这是在工厂方法内发生的,我们可以用代理包装主题在返回,而客户不知道也不在乎他使用的代理还是真东西。
-
我已经知道代理和装饰者的关系了,但是适配器?代理和适配器也很类似。
代理和适配器都是挡在其他对象的前面,并负责将请求转发给他们。适配器会改变对象适配的接口,而代理则实现相同的接口。有一个额外相似性牵涉到保护代理。保护代理可以根据客户的角色来决定是否允许客户访问特定的方法。所以保护代理可能只提供给客户部分接口,这就和适配器很相像了(适配器是只能访问共同接口的方法)。
代理和装饰者的围炉夜话:
保护代理
相关类图:
相关步骤:
核心代码:
创建InvocationHandler,invoke的实现代码:
常见疑问
-
到底“动态代理”动态在哪里?是不是指在运行时才将它实例化并和handler联系起来?
不是的。动态代理之所以被称为动态,是因为运行时才将它的类创建出来。代码开始执行时,还没有proxy类,它是根据需要从你传入的接口集创建的。
-
InvocationHandler看起来像一个很奇怪的proxy。它实现所代理的类的任何方法。?
这是因为InvocationHandler根本就不是proxy,它只是一个帮助proxy的类,proxy本身是利用静态的Proxy.newProxyInstance()方法在运行时动态地创建地。
-
可以通过isProxyClass()的返回值true判断一个类是动态代理类。
-
为什么使用skeleton?我以为我们早在java1.2就已经摆脱了skeleton了。
确实,我们不需要真的产生skeleton,因为java1.2RMI利用reflectionAPI直接将客户调用分派给原称为服务。尽管如此,我们还是希望呈现skeleton,因为这可以帮助你从概念上理解内部的机制。
其他类型代理
防火墙代理(Firewall Proxy):控制网络资源的访问,保护主题免于“坏客户”的侵害。
智能引用代理(Smart Reference Proxy):当主题被引用时,进行额外的动作,例如计算一个对象被引用的次数。
缓存代理(Caching Proxy):为开销大的运算结果提供暂时存储:它也允许多个客户共享结果,以减少计算或网络延迟。(Web服务器代理,内容管理与出版系统)
同步代理(Synchronization Proxy):在多线程的情况下为主题提供安全的访问。(出现在JavaSpaces,为分散式环境内的潜在对象集合提供同步访问控制)
复杂隐藏代理(Complexity Hiding Proxy):用来隐藏一个类的复杂集合复杂度,并进行访问控制。有时候也称为外观代理。复杂隐藏代理和外观模式是不一样的,因为代理控制访问,而外观模式只提供另一组接口。
写入时复制代理(CopyOnWrite Proxy):用来控制对象的复制,方法是延迟对象的复制,知道客户真的需要为止。这是虚拟代理的变体。(CopyOnWriteArrayList)
如有侵权请联系本网站!
复合模式
定义
复合模式结合两个或以上的模式,组成一个解决方案,解决一再发生的一般性问题。
(一般是多个模式解决一个问题,而不是一个模式解决一个问题然后加起来。)
一群模式携手合作(不是复合)
鸭子 → 鹅 \xrightarrow{鹅} 鹅适配器 → 计 算 呱 呱 叫 的 次 数 , 不 修 改 原 有 代 码 \xrightarrow{计算呱呱叫的次数,不修改原有代码} 计算呱呱叫的次数,不修改原有代码装饰者 → 每 次 创 建 都 要 包 装 \xrightarrow{每次创建都要包装} 每次创建都要包装 使用抽象工厂将创建和包装放在一个方法中 → 管 理 一 群 对 象 \xrightarrow{管理一群对象} 管理一群对象迭代器和组合模式 → 持 续 观 察 某 些 对 象 \xrightarrow{持续观察某些对象} 持续观察某些对象观察者模式
常见疑问:
–相关代码定义:
//Qauckable接口只负责quack(呱呱叫即可)
public interface Quackable{
public void quack();
}
//绿头鸭
public class MallardDuck implements Quackable{
public void quack(){
System.out.println("quack!");
}
}
//红头鸭
public class ReaheadDuck implements Quackable{
public void quack(){
System.out.println("quack!");
}
}
//鸭鸣器
public class DuckCall implements Quackable{
public void quack(){
System.out.println("Kuaw!");
}
}
//橡皮鸭
public class DuckCall implements Quackable{
public void quack(){
System.out.println("Squeak!");
}
}
适配器:(适配鹅)
public class Goose{
public void hook(){
System.out.println("Honk");//咯咯叫
}
}
public class GooseApater implements Quackable{
Goose goose;
public GooseAdapter(Goose goose){
this.goose = goose;
}
public void quack(){
goose.honk();
}
}
实现:new GooseAdapter(new Goose());
装饰者:(动态添加新的行为——添加呱呱叫计数器,而不需要修改源代码)
public class QuackCount implements Quackable{
Quackable duck;
static int numberOfQuacks;
public QuackCounter(Quackable duck){
this.duck = duck;
}
public void quack(){
duck.quack;numberOfQuacks++;
}
public static int getQuacks(){
return numberOfQuacks;
}
}
实现:new QuackCounter(new MallardDuck());
抽象工厂:将创建和装饰部分包装起来集中管理
public abstract class AbstractDuck Factory{
public abstract Quackable createMallardDuck();
public abstract Quackable createRedHeadDuck();
public abstract Quackable createrDuckCall();
public abstract Quackable createRubberDuck();
}
//创建没有装饰者的鸭子的工厂
public class DuckFactory extends AbstractDuckFactory{
public Quackable createMallardDuck(){
return new MallradDuck();
}
public Quackable createRedHeadDuck(){
return new RedHeadDuck();
}
public Quackable createDuckCall(){
return new DuckCall();
}
public Quackable createRubberDuck(){
return new RubberDuck();
}
}
//创建装饰过的鸭子的工厂
public class CountingDuckFactory extends AbstractDuckFactory{
public Quackable createMallardDuck(){
return new QuackCounter(MallradDuck());
}
public Quackable createRedHeadDuck(){
return new QuackCounter(RedHeadDuck());
}
public Quackable createDuckCall(){
return new QuackCounter(DuckCall());
}
public Quackable createRubberDuck(){
return new QuackCounter(RubberDuck());
}
}
实现:
AbstractDuckFactory duckFactory = new CountingDuckFactory();
duckFactory.createMallardDuck();...
其中比较难想到的方式代码:
迭代器和组合模式:以一致的方式来对待鸭群和单只小鸭
public class Flock implements Quackable{
//Flock就是鸭群,处理它也可以像单只鸭子一样调用quack方法。
ArrayList quackers = new ArrayList(); //叶节点就是Quackable。
public void add(Quackable quacker){
quackers.add(quacker);
}
public void quack()//flock也是Quackable,所以也要具备quack方法
{
Iterator iterator = quackers.iterator();
while(iterator.haxNext())
{
Quackable quacker = (Quackable)iterator.next();
quacker.quack();
}
}
}
关于组合模式中的安全性和透明性:
观察者模式:(追踪某只鸭子什么时候叫)
MVC
MVC与Web
其他模式
桥接
生成器
责任链
蝇量
解释器
中介者
备忘录
原型
访问者
完结!恭喜你阅读完常用基础的设计模式。