一、策略模式(让算法与对象独立)
策略模式定义了算法族,分别封装起来,让他们之间可以互相替换,此模式让算法的变化独立于使用算法的客户。
二、观察者模式(让你的对象知悉现状)
定义:观察者模式定义了对象之间的一对多依赖,这样一来,当一个对象状态改变时,它的所有依赖者都会收到通知并自动更新。主题(可观察者)用一个共同的接口来更新观察者。使用此模式时,你可以从被观察者处推(push)或拉(pull)数据(然而,推的方式被认为更"正确")。
观察者模式中用到的设计原则:
1⃣️ 找出程序中会变化的部分,然后将其和固定不变的方面相分离。
在观察者模式中,会变化的是主题的状态,以及观察者的数目和类型。用这个模式,你可以改变依赖于主题状态的对象,却不必改变主题。这就叫提前规则!
2⃣️ 针对接口编程,而不是针对实现编程。
主题与观察者都是用接口:观察者利用主题的接口向主题注册,而主题利用观察者接口通知观察者。这样可以让两者之间运作正常,又同时具有松耦合的优点。
3⃣️ 多用组合,少用继承。
观察者模式利用“组合”将许多观察者组合进主题中。对象之间的这种关系不是通过继承产生的,而是在运行时利用组合的方式产生的。
三、装饰者模式(装饰对象)
定义:装饰者模式动态地将责任附加到对象上。若要扩展功能,装饰者提供了比继承更有弹性的替代方案。
Java中IO很多就是用装饰者来实现的。
四、工厂模式
1、简单工厂其实不是一个设计模式,反而更像是一种编程习惯。
2、工厂方法模式
定义:工厂方法模式定义了一个创建对象的接口,但由子类决定要实例化的类是哪一个。工厂方法让类把实例化推迟到子类。这里所谓的“决定”,并不是模式允许子类本身在运行时决定,而是指在编写创建者类时,不需要知道实际创建的产品是哪一个。选择了哪个子类,自然就决定了实际创建的产品是什么。
工厂方法模式类图如下:
1⃣️ 所有的产品必须实现Product接口,这样一来,使用这些产品的类就可以直接引用这个接口,而不是具体类。
2⃣️ Creator是一个抽象类,它实现了所有操纵产品的方法,但不实现工厂方法。所有的子类都必须实现这个抽象的factoryMethod()方法。
3⃣️ ConcreteCreator实现了factoryMethod(),以实际制造出产品。
简单工厂和工厂方法模式之间的差异:在工厂方法中,返回产品的是子类。简单工厂把所有的事情,在一个地方都处理完了,然而工厂方法却是创建了一个框架,让子类决定去如何实现。简单工厂的做法,可以将对象的创建封装起来,但是简单工厂不具备工厂方法的弹性,因为简单工厂不能变更正在创建的产品。
3、抽象工厂模式
定义:抽象工厂模式提供一个接口,用于创建相关或依赖对象的家族,而不需要明确指定具体类。
抽象工厂允许客户使用抽象的接口来创建一组相关的产品,而不需要知道(或关心)实际产出的具体产品是什么。这样一来,客户就从具体的产品中被解耦。
抽象工厂的方法经常以工厂方法的方式实现。抽象工厂的任务是定义一个负责创建一组产品的接口,这个接口内的每个方法都负责创建一个具体的产品,同时我们利用实现抽象工厂的子类来提供这些具体的做法。所以,在抽象工厂中利用工厂方法实现生产方法是相当自然的事情。
4、工厂方法模式和抽象工厂模式的比较
工厂方法模式:就是通过子类来创建对象。用这种做法,客户只需要知道他们所使用的抽象类型就可以了,而由子类来负责决定具体类型。换句话说,工厂方法只负责将客户从具体类型中解码。
抽象工厂模式:提供了一个用来创建一个产品家族的抽象类型,这个类型的子类定义了产品被产生的方法。要想使用这个工厂,必须先实例化它,然后将它传入一个针对抽象类型所写的代码中。所以,和工厂方法模式一样,我可以把客户从所使用的实际具体产品中解耦。
五、单件模式(独一无二)
1、定义:确保一个类只有一个实例,并提供一个全局访问点。
2、单件模式实现:
单件模式经典实现:
/** * 单件模式 Created by user on 16/7/7. */public class Singleton { private static Singleton uniqueSingleton; private Singleton() {} public static Singleton getInstance() { if (uniqueSingleton == null) { uniqueSingleton = new Singleton(); } return uniqueSingleton; }}
但是在多线程的情况下,这种实现会出现问题,可能会出现创建多个实例的情况,这样就不能称作单件模式了。在这种情况下,只要把getInstance()编程同步(synchronized)方法,多线程灾难几乎就可以轻易解决了:
/** * 单件模式 Created by user on 16/7/7. */public class Singleton { private static Singleton uniqueSingleton; private Singleton() {} public static synchronized Singleton getInstance() { if (uniqueSingleton == null) { uniqueSingleton = new Singleton(); } return uniqueSingleton; }}
但这种方式有一个缺点:只有在第一次执行此方法时,才真正需要同步。换句话说,一旦设置好uniqueSingleton,就不再需要同步这个方法了。之后每次调用这个方法,同步都是一种累赘。
解决方法:
1⃣️ 如果getInstance()的性能对应用程序不是很关键,就什么都别做。
如果应用程序可以接受getInstance()造成的额外负担,那就忘了这件事吧。但我们必须知道,同步一个方法可能造成应用程序执行效率下降100倍。
2⃣️ 使用“急切”创建实例,而不用延迟实例化的做法。
如果应用程序总是创建并使用单件实例,或者在创建和运行时方面的负担不太繁重,可以这样做:
public class Singleton { private static Singleton uniqueSingleton = new Singleton(); private Singleton() {} public static Singleton getInstance() { return uniqueSingleton; }}
3⃣️ 用“双重检查加锁”,在getInstance()中减少使用同步。
使用双重检查加锁,首先检查是否实例已经创建了,如果尚未创建,“才”进行同步。这样一来,只有第一次才同步,这正是我们想要的。
public class Singleton { //volatile关键词确保,当uniqueSingleton变量被初始化成实例时,多个线程正确地处理uniqueSingleton变量 private volatile static Singleton uniqueSingleton; private Singleton() {} public static Singleton getInstance() { // 检查实例,如果不存在就进入同步区域 if (uniqueSingleton == null) { //只有第一次才彻底执行这里的代码 synchronized (Singleton.class) { //进入区块后,再检查一次。如果仍是null,才创建实例 if (uniqueSingleton == null) { uniqueSingleton = new Singleton(); } } } return uniqueSingleton; }}
六、命令模式(封装调用)
1、定义:将“请求”封装成对象,以便使用不同的请求、队列或者日志来参数化其他对象。命令模式也支持可撤销的操作。
通过定义,我们知道一个命令对象通过在特定接收者上绑定一组动作来封装一个请求。要达到这一点,命令对象将动作和接收者包进对象中。这个对象只暴露出一个execute()方法。
2、实现:
首先实现命令接口:
public interface Command { public abstract void execute();}
实现打开电灯的命令:
/** * 开灯命令 * * Created by user on 16/7/11. */public class LightOnCommand implements Command { private Light light; /** * 将接收者与命令结合 * @param light */ public LightOnCommand(Light light) { this.light = light; } /** * 执行开灯命令 */ @Override public void execute() { light.on(); }
假设我们现在有一个遥控器,它只有一个按钮和对应的插槽,可以控制一个装置:
/** * 遥控器类(调用者) * * Created by user on 16/7/11. */public class RemoteControl { private Command command; public void setCommand(Command command) { this.command = command; } public void buttonWasPressed() { command.execute(); }}
遥控器使用的简单测试:
/** * 这是命令模式的客户端 Created by user on 16/7/12. */public class RemoteControlTest { public static void main(String[] args) { // 创建遥控器(调用者) RemoteControl remoteControl = new RemoteControl(); // 创建灯类(创建命令接收者) Light light = new Light(); // 将"开灯"命令与"灯"绑定(将命令与接收者绑定为请求对象) Command lightOnCommand = new LightOnCommand(light); // 开灯命令放入遥控器(将请求对象放入控制器中) remoteControl.setCommand(lightOnCommand); // 按下开灯按钮试下(执行命令) remoteControl.buttonWasPressed(); }}
这样设计的主要目标是让遥控器代码尽可能的简单,这样一来,新的厂商类一旦出现,遥控器并不需要随之修改。因此,我们采用了命令模式,从逻辑上将遥控器的类和厂商的类解耦。
3、命令模式的用途:队列请求、日志请求、事物系统
队列请求:假设又一个工作队列,我们在某一端添加命令,然后另一端则是线程。线程将进行下面的动作:从队列中取出一个命令,调用它的execute()方法,等待这个执行完成,然后将此命
令对象丢弃,再取出下一个命令...... 工作队列对象不在乎到底在做什么,它们只知道取出命令对象,然后执行execute()方法。
日志请求:某些应用需要我们将所有的动作都记录在日志中,并能在系统死机后,重新调用这些动作恢复到之前的状态。通过新增两个方法(store()、load()),命令模式就能支持这一点。当
我们执行命令的时候,将历史记录储存在磁盘中。一旦系统死机,我们就可以将命令对象重新加载,并成批地依次调用这些对象的execute()方法。
七、适配器模式(随遇而安)
1、定义:将一个类的接口,转换成客户期望的另一个接口。适配器让原本接口不兼容的类可以合作无间。
这个适配器模式充满着良好的OO设计原则:使用对象组合,以修改的接口包装被适配者:这种做法还有额外的优点,那就是,被适配者的任何子类,都可以搭配着适配器使用。
适配器模式类图
2、实现示例:
首先实现鸭子、火鸡的接口和具体类
鸭子接口:
/** * 鸭子接口 * * Created by user on 16/8/3. */public interface Duck { /** * 呱呱叫 */ public void quack(); /** * 飞行 */ public void fly();}
火鸡接口:
/** * 火鸡接口 * * Created by user on 16/8/3. */public interface Turkey { /** * 咯咯叫 */ public void gobble(); /** * 飞行 */ public void fly();}
鸭子实现类:
/** * 机智的小鸭 * * Created by user on 16/8/3. */public class SmartDuck implements Duck { @Override public void quack() { System.out.println("quack quack"); } @Override public void fly() { System.out.println("I'm flying"); }}
火鸡实现类:
/** * 沉鱼落雁的火鸡 * * Created by user on 16/8/3. */public class PrettyTurkey implements Turkey{ @Override public void gobble() { System.out.println("Gobble Gobble"); } @Override public void fly() { System.out.println("I'm flying a short distance"); }}
然后实现火鸡适配器类,让她看起来像只鸭子:
/** * 适配器(需要实现想转换成的接口类型,也就是客户期望看到的类型) * * Created by user on 16/8/3. */public class TurkeyAdapter implements Duck { private Turkey turkey; /** * 火鸡适配器 * * 需要取得被适配对象的引用,这里通过构造器取得 * @param turkey */ public TurkeyAdapter(Turkey turkey) { this.turkey = turkey; } /** * quack()在类之间的转换很简单,只要调用gobble()就可以了 */ @Override public void quack() { turkey.gobble(); } /** * 固然两个接口都具备类fly()方法,但火鸡的飞行距离很短,不像鸭子可以长途飞行。要让鸭子的飞行和火鸡的飞行 * 能够对应,必须延长火鸡的飞行距离。 */ @Override public void fly() { for (int i = 0; i < 5; i++) { turkey.fly(); } }}
最后测试一下:
/** * 测试类 * * Created by user on 16/8/3. */public class DuckTest { public static void main(String[] args) { // 先创建一个鸭子、一只火鸡 Duck smartDuck = new SmartDuck(); testDuck(smartDuck); Turkey turkey = new PrettyTurkey(); turkey.fly(); turkey.gobble(); // 然后将火鸡包装进火鸡适配器中,使它看起来像是一只鸭子 Duck turkeyAdapter = new TurkeyAdapter(turkey); // 测试一下 testDuck(turkeyAdapter); } public static void testDuck(Duck duck) { duck.quack(); duck.fly(); }}
八、外观模式
1、定义:外观模式提供了一个统一的接口,用来访问子系统中的一群接口。外观定义了一个高级接口,让子系统更容易使用。
2、实现示例:
如果我们要在家开启家庭影院模式,需要做一系列的工作:
/** * 地狱模式家庭影院 * * Created by user on 16/8/4. */public class SimpleHomeTheater { public static void main(String[] args) { popper.on(); //打开爆米花机,开始爆米花 popper.pop(); lights.dim(10); //灯光调暗到10%的亮度 screen.down(); //把屏幕放下 projecter.on(); //打开投影机,并将它设置在宽屏模式 pojecter.setInput(dvd); projecter.wideScreenMode(); amp.on(); // 打开功放,设置为DVD,调整成环绕立体声模式,音量调到5..... amp.setDvd(dvd); amp.setSurroundSound(); amp.setVolume(5); dvd.on(); //打开DVD播放机... dvd.play(movie); //现在开始享受吧 }}
用外观模式改造后:
/** * 上帝模式家庭影院 * * Created by user on 16/8/4. */public class GodHomeTheater { public void watchMovie(String movie) { popper.on(); // 打开爆米花机,开始爆米花 popper.pop(); lights.dim(10); // 灯光调暗到10%的亮度 screen.down(); // 把屏幕放下 projecter.on(); // 打开投影机,并将它设置在宽屏模式 pojecter.setInput(dvd); projecter.wideScreenMode(); amp.on(); // 打开功放,设置为DVD,调整成环绕立体声模式,音量调到5..... amp.setDvd(dvd); amp.setSurroundSound(); amp.setVolume(5); dvd.on(); // 打开DVD播放机... dvd.play(movie); // 现在开始享受吧 }}
直接调watchMovie()方法就可以看电影了。
三种模式目的比较:
装饰者模式:不改变接口,但加入责任
适配器模式:将一个接口转换成另一个接口
外观模式:让接口更简单
九、模版方法模式(封装算法)
1、定义:在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类在不改变算法结构的情况下,重新定义算法中的某些步骤。
2、实现(基于煮咖啡喝泡茶的示例):
/** * 咖啡因饮料类(抽象类) * * Created by user on 16/8/5. */public abstract class CaffeineBeverage { /** * 现在,用同一个prepareRecipe()方法来处理茶和咖啡。prepareRecipe()被声明为final, 因为我们不希望子类覆盖这个方法! */ final void prepareRecipe() { boilWater(); brew(); pourInCup(); addCondiments(); } /** * 因为咖啡和茶处理这些方法的做法不同,所以这两个方法必须被声明为抽象,剩余的东西留给子类去操心 */ abstract void brew(); abstract void addCondiments(); void boilWater() { System.out.println("Boiling water"); } void pourInCup() { System.out.println("pouring into cup"); }}
/** * 茶类 * * Created by user on 16/8/5. */public class Tea extends CaffeineBeverage { /** *茶需要定义brew()和addCondiments(),这两个抽象方法来自CaffeineBeverage */ @Override void brew() { System.out.println("Steeping the tea"); } @Override void addCondiments() { System.out.println("Adding Lemon"); }}
/** * 咖啡类 * * Created by user on 16/8/5. */public class Coffee extends CaffeineBeverage { /** * 咖啡需要定义brew()和addCondiments(),这两个抽象方法来自CaffeineBeverage */ @Override void brew() { System.out.println("Dripping Coffee through filter"); } @Override void addCondiments() { System.out.println("Adding Sugar and Milk"); }}
3、模版方法模式与策略模式的比较
1⃣️ 目的不同:模板方法模式是要定义一个算法的大纲,而由子类定义定义其中某些步骤的内容。这么一来,其在算法中的个别步骤可以有不同的实现细节,但是算法的结构依然维持不变。
策略模式中每一个算法都被封装起来了,所以客户可以轻易地使用不同的算法。
2⃣️ 模版方法模式中会重复使用到的代码,都被放进了超类中,用继承来获得,好让更多的子类共享,效率会比策略模式高。
策略模式使用对象组合,所以会更有弹性。利用策略模式,客户就可以在运行时改变他们的算法,而客户所需要做的,只是该用不同的策略对象罢了。
十、状态模式
1、定义:状态模式允许对象在内部状态改变时改变它的行为,对象看起来好像修改了它的类。
该模式将状态封装为独立的类,并将动作委托到当前状态的对象,我们知道行为会随着内部状态而改变。
状态模式类图:
2、状态模式与策略模式区别:
状态模式与策略模式的类图是一样的,但两个模式的差别在于它们的意图。
以状态模式而言,我们将一群行为封装在状态对象中,context的行为随时可委托到那些状态对象中的一个。随着时间的流逝,当前状态在状态对象集合中游走改变,以反映出context内部的状态,因此,contect的行为也会跟着改变。但是context的客户对于状态对象了解不多,甚至根本是浑然不觉。
而以策略模式而言,客户通常主动指定Context所要组合的策略对象是哪一个。现在,固然策略模式让我们更有弹性,能够在运行时改变策略,但对于某个context对象来说,通常都只有一个最适当的策略对象。
十一、代理模式(控制对象访问)
1、定义:代理模式为另一个对象提供一个替身或占位符以控制对这个对象的访问。
使用代理模式创建代表对象,让代表对象控制某对象的访问,被代理的对象可以是远程的对象、创建开销大的对象或需要安全控制的对象。
代理模式类图
首先是Subject,它为RealSubject和Proxy提供了接口。通过实现同一接口,Proxy在RealSubject出现的地方取代它。
RealSubject是真正做事的对象,它是被Proxy代理和控制访问的对象。
Proxy持有RealSubject的引用。在某些例子中,Proxy还会负责RealSubject对象的创建和销毁。客户和RealSubject的交互都必须通过Proxy。因为Proxy和RealSubject实现相同的接口,所以任何用到RealSubject的地方,都可以用Proxy取代。Proxy也控制了对RealSubject的访问。
2、动态代理:Java在java.lang.reflect包中有自己的代理支持,利用这个包你可以在运行时动态地创建一个代理类,实现一个或多个接口,并将方法的调用转发到你所指定的类。因为实际的 代理类是在运行时创建的,我们称这个Java技术为:动态代理。
动态代理类图
动态代理的实现:利用动态代理创建一个代理保护。
我们想要实现一个约会服务系统。你有一个好点子,就是在服务中加入“Hot”和“Not”的鉴评,“Hot”就表示喜欢对方,“Not”表示不喜欢。
先创建一个设置个人信息的接口:
/** * 设置个人信息的接口 * * Created by user on 16/8/10. */public interface Personbean { String getName(); String getGender(); String getInterests(); int getHotOrNotrating(); void setName(String name); void setGender(String gender); void setInterests(String interests); void setHotOrNotRating(int rating);}
现在我们来实现这个接口:
/** * 个人信息实现类 * * Created by user on 16/8/10. */public class PersonBeanImpl implements Personbean { String name; String gender; String interests; int rating = 0; int ratingCount = 0; public PersonBeanImpl(String name) { this.name = name; } @Override public String getName() { return name; } @Override public String getGender() { return gender; } @Override public String getInterests() { return interests; } /** * 该方法计算rating的平均值 * @return */ @Override public int getHotOrNotrating() { if (ratingCount == 0) { return 0; } return rating / ratingCount; } @Override public void setName(String name) { this.name = name; } @Override public void setGender(String gender) { this.gender = gender; } @Override public void setInterests(String interests) { this.interests = interests; } @Override public void setHotOrNotRating(int rating) { this.rating += rating; ratingCount++; }}
我们有些问题要修正:顾客不可以改变自己的HotOrNot评分,也不可以改变其他顾客的个人信息。要修正这些问题,我们必须创建两个代理:一个访问你自己的PersonBean对象,另一个访问另一顾客的PersonBean对象。这样,代理就可以控制在每一种情况下允许哪一种请求。
步骤 1⃣️ :创建InvocationHandler:我们需要创建两个InvocationHandler(调用处理器),分别给拥有者和非拥有者使用。究竟什么是InvocationHandler呢?你可以这么想:当代理的方法被调用时,代理就会把这个调用转发给InvocationHandler,InvocationHandler只有一个invoke()方法,不管代理被调用的是何种方法,处理器被调用的一定是invoke()方法。
/** * 所有调用处理器都实现Invocation接口 * * Created by user on 16/8/10. */public class OwnerInvocationhandler implements InvocationHandler { Personbean personbean; /** * 将person传入构造器,并保持它的引用 * * @param personbean */ public OwnerInvocationhandler(Personbean personbean) { this.personbean = personbean; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws IllegalAccessException { try { if (method.getName().startsWith("get")) { return method.invoke(personbean, args); } else if (method.getName().equals("setHotOrNotRating")) { throw new IllegalAccessException(); } else if (method.getName().startsWith("set")) { return method.invoke(personbean, args); } } catch (InvocationTargetException e) { e.printStackTrace(); } return null; }}
步骤 2⃣️ :创建Proxy类并实例化Proxy对象:
/** * 获取代理类 * 该方法用一个personbean对象作为参数,然后获取它的代理 * @param personbean * @return */ public Personbean getOwnerProxy(Personbean personbean) { return (Personbean) Proxy.newProxyInstance(personbean.getClass().getClassLoader(), personbean.getClass().getInterfaces(), new OwnerInvocationhandler(personbean)); }
步骤 3⃣️ :测试
/** * 测试类 * * Created by user on 16/8/10. */public class TestDrive { /** * 获取代理类 * 该方法用一个personbean对象作为参数,然后获取它的代理 * @param personbean * @return */ public Personbean getOwnerProxy(Personbean personbean) { return (Personbean) Proxy.newProxyInstance(personbean.getClass().getClassLoader(), personbean.getClass().getInterfaces(), new OwnerInvocationhandler(personbean)); } public static void main(String[] args) { TestDrive testDrive = new TestDrive(); testDrive.drive(); } public void drive() { Personbean john = new PersonBeanImpl("john"); Personbean ownerProxy = this.getOwnerProxy(john); System.out.println("Name is " + ownerProxy.getName()); ownerProxy.setInterests("play football"); System.out.println("Interests set from owner proxy"); try { ownerProxy.setHotOrNotRating(10); } catch (Exception e) { System.out.println("you cannot modify your hotOrRating"); } System.out.println("Rating is " + ownerProxy.getHotOrNotrating()); }}
3、代理模式和装饰者模式PK:
代理模式控制对对象的访问;装饰者模式为对象添加行为。二者的意图不同。