1 观察者
如果使用java,你不需要从头到尾来实现观察者模式,java.util中已有 Observable和 Observer,可开箱即用。
例如,很多购房者都在关注房子的价格变化,每当房子价格变化时,所有的购房者都可以知道。实现方式如下:
1.1 Observable
import java.util.Observable; /** * 可被观察的对象 */ public class House extends Observable { private float price; public House(float price) { this.price = price; } public float getPrice() { return this.price; } public void setPrice(float price) { // 每一次修改的时候都应该引起观察者的注意 // 设置变化点 super.setChanged(); // 价格被改变 super.notifyObservers(price); this.price = price; } @Override public String toString() { return "房子价格为:" + this.price; } }
1.2 Observer
import java.util.Observable; import java.util.Observer; public class HousePriceObserver implements Observer { private String name; //设置每一个购房者的名字 public HousePriceObserver(String name) { this.name = name; } @Override public void update(Observable o, Object arg) { if (arg instanceof Float) { System.out.print(this.name + "观察到价格更改为:"); System.out.println(((Float) arg).floatValue()); } } //测试 public static void main(String args[]){ House h = new House(1000000) ; HousePriceObserver hpo1 = new HousePriceObserver("购房者A") ; HousePriceObserver hpo2 = new HousePriceObserver("购房者B") ; HousePriceObserver hpo3 = new HousePriceObserver("购房者C") ; h.addObserver(hpo1) ; h.addObserver(hpo2) ; h.addObserver(hpo3) ; System.out.println(h) ; h.setPrice(666666) ; } };
结果:
房子价格为:1000000.0 购房者C观察到价格更改为:666666.0 购房者B观察到价格更改为:666666.0 购房者A观察到价格更改为:666666.0
2 工厂
2.1 生产对象
//咖啡 public abstract class Coffee { public abstract String getName(); } //卡布奇诺 public class Cappuccino extends Coffee { @Override public String getName() { return "卡布奇诺"; } } //拿铁 public class Latte extends Coffee { @Override public String getName() { return "拿铁"; } }
2.2 简单工厂
简单工厂实际不能算作一种设计模式,它引入了创建者的概念,将实例化的代码从应用代码中抽离,在创建者类(SimpleFactory)的静态方法中只处理创建对象的细节,后续创建的实例如需改变,只需改造创建者类即可,但由于使用静态方法来获取对象,使其不能在运行期间通过不同方式去动态改变创建行为,因此存在一定局限性。
/** * 简单工厂--用于创建不同类型的咖啡实例 */ public class SimpleFactory { /** * 通过类型获取Coffee实例对象 * @param type 咖啡类型 * @return */ public static Coffee createInstance(String type){ if("americano".equals(type)){ return new Americano(); }else if("cappuccino".equals(type)){ return new Cappuccino(); }else if("latte".equals(type)){ return new Latte(); }else{ throw new RuntimeException("type["+type+"]类型不可识别,没有匹配到可实例化的对象!"); } } //测试 public static void main(String[] args) { Coffee latte = SimpleFactory.createInstance("latte"); System.out.println("创建的咖啡实例为:" + latte.getName()); Coffee cappuccino = SimpleFactory.createInstance("cappuccino"); System.out.println("创建的咖啡实例为:" + cappuccino.getName()); } }
2.3 工厂方法
定义了一个创建对象的接口,但由子类决定要实例化的类是哪一个,工厂方法让类把实例化推迟到了子类。
场景延伸:不同地区咖啡工厂受制于环境、原料等因素的影响,制造出的咖啡种类有限。中国咖啡工厂能制造卡布奇诺、拿铁,而美国咖啡工厂仅能制造拿铁。
public abstract class CoffeeFactory { public abstract Coffee[] createCoffee(); } public class ChinaCoffeeFactory extends CoffeeFactory { @Override public Coffee[] createCoffee() { return new Coffee[]{new Cappuccino(), new Latte()}; } } public class AmericaCoffeeFactory extends CoffeeFactory { @Override public Coffee[] createCoffee() { return new Coffee[]{new Latte()}; } } //测试 public static void main(String[] args) { //中国咖啡工厂可以生产的咖啡 CoffeeFactory chinaCoffeeFactory = new ChinaCoffeeFactory(); Coffee[] chinaCoffees = chinaCoffeeFactory.createCoffee(); //美国咖啡工厂可以生产的咖啡 CoffeeFactory americaCoffeeFactory = new AmericaCoffeeFactory(); Coffee[] americaCoffees = americaCoffeeFactory.createCoffee(); }
2.4 抽象工厂
提供一个接口,用于创建相关或依赖对象的家族,而不需要明确指定具体类。
在上述的场景上继续延伸:咖啡工厂做大做强,引入了新的饮品种类:茶。中国工厂只能制造咖啡和茶,美国工厂只能制造咖啡。
如果用上述工厂方法方式,除去对应的产品实体类还需要新增1个抽象工厂(茶制造工厂),2个具体工厂实现(中国工厂、美国工厂)。随着产品的增多,会导致类爆炸。
所以这里引出一个概念产品家族,在此例子中,不同的饮品就组成我们的饮品家族, 饮品家族开始承担创建者的责任,负责制造不同的产品。
注意:下面的模型Coffee、Tea都应该继承抽象类 Drink,这里就不写出来了。
public interface DrinksFactory { Coffee createCoffee(); Tea createTea(); } public class ChinaDrinksFactory implements DrinksFactory { @Override public Coffee createCoffee() { return new Latte(); } @Override public Coffee createTea() { return new MilkTea(); } } public class AmericaDrinksFactory implements DrinksFactory { @Override public Coffee createCoffee() { return new Latte(); } @Override public Tea createTea() { return null; } } //测试 public static void main(String[] args) { //中国饮品工厂有如下产品 DrinksFactory chinaDrinksFactory = new ChinaDrinksFactory(); Coffee coffee = chinaDrinksFactory.createCoffee(); Tea tea = chinaDrinksFactory.createTea(); //美国饮品工厂有如下产品 DrinksFactory americaDrinksFactory = new AmericaDrinksFactory(); Coffee coffee = americaDrinksFactory.createCoffee(); }
2.5 总结
简单工厂:不能算是真正意义上的设计模式,但可以将客户程序从具体类解耦。
工厂方法:使用继承,把对象的创建委托给子类,由子类来实现创建方法,可以看作是抽象工厂模式中只有单一产品的情况。
抽象工厂:使对象的创建被实现在工厂接口所暴露出来的方法中。
工厂模式可以帮助我们针对抽象/接口编程,而不是针对具体类编程,在不同的场景下按具体情况来使用。
3 代理
3.1 代理模式是什么
简单说即是在不改变源码的情况下,实现对目标对象的功能扩展。
比如有个歌手对象叫Singer,这个对象有一个唱歌方法叫sing()。
public class Singer{ public void sing(){ System.out.println("唱一首歌"); } }
假如你希望,通过你的某种方式生产出来的歌手对象,在唱歌前后还要想观众问好和答谢,也即对目标对象Singer的sing方法进行功能扩展。
public void sing(){ System.out.println("向观众问好"); System.out.println("唱一首歌"); System.out.println("谢谢大家"); }
但是往往你又不能直接对源代码进行修改,可能是你希望原来的对象还保持原来的样子,又或许你提供的只是一个可插拔的插件,甚至你有可能都不知道你要对哪个目标对象进行扩展。这时就需要用到java的代理模式了。
下面我们就讲讲Java的三种代理模式。
3.2 源代码
public interface ISinger { void sing(); } public class Singer implements ISinger { @Override public void sing() { System.out.println("唱一首歌"); } }
3.3 静态代理
public class SingerProxy implements ISinger { private ISinger target; public SingerProxy(ISinger target) { this.target = target; } @Override public void sing() { System.out.println("向观众问好"); target.sing(); System.out.println("谢谢大家"); } //测试 public static void main(String[] args) { //目标对象 ISinger target = new Singer(); //代理对象 ISinger proxy = new SingerProxy(target); //执行的是代理的方法 proxy.sing(); } }
这种实现方式很直观也很简单,但其缺点是代理对象必须提前写出,如果接口层发生了变化,代理对象的代码也要进行维护。如果能在运行时动态地写出代理对象,不但减少了一大批代理类的代码,也少了不断维护的烦恼,不过运行时的效率必定受到影响。这种方式就是接下来的动态代理。
3.4 动态代理(也叫JDK代理)
import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; public class JdkProxyTest { public static void main(String[] args) { ISinger target = new Singer(); ISinger proxy = (ISinger) Proxy.newProxyInstance( target.getClass().getClassLoader(), target.getClass().getInterfaces(), new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("向观众问好"); //执行目标对象方法 Object returnValue = method.invoke(target, args); System.out.println("谢谢大家"); return returnValue; } }); proxy.sing(); } }
总结:由于java封装了newProxyInstance这个方法的实现细节,所以使用起来才能这么方便,具体的底层原理将会在下一小节说明。
缺点:可以看出静态代理和JDK代理有一个共同的缺点,就是目标对象必须实现一个或多个接口,加入没有,则可以使用Cglib代理。
3.5 Cglib代理
前提条件:
- 需要引入cglib的jar文件,由于Spring的核心包中已经包括了Cglib功能,所以也可以直接引入spring-core-3.2.5.jar
- 目标类不能为final
- 目标对象的方法如果为final/static,那么就不会被拦截,即不会执行目标对象额外的业务方法
/** * 目标对象,没有实现任何接口 */ public class Singer { public void sing() { System.out.println("唱一首歌"); } }
import net.sf.cglib.proxy.Enhancer; import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; import java.lang.reflect.Method; /** * Cglib子类代理工厂 */ public class ProxyFactory implements MethodInterceptor { // 维护目标对象 private Object target; public ProxyFactory(Object target) { this.target = target; } // 给目标对象创建一个代理对象 public Object getProxyInstance() { //1.工具类 Enhancer en = new Enhancer(); //2.设置父类 en.setSuperclass(target.getClass()); //3.设置回调函数 en.setCallback(this); //4.创建子类(代理对象) return en.create(); } @Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { System.out.println("向观众问好"); //执行目标对象的方法 Object returnValue = method.invoke(target, args); System.out.println("谢谢大家"); return returnValue; } public static void main(String[] args) { //目标对象 Singer target = new Singer(); //Cglib代理对象 Singer proxy = (Singer) new ProxyFactory(target).getProxyInstance(); //执行代理对象的方法 proxy.sing(); } }
3.6 总结
三种代理模式各有优缺点和相应的适用范围,主要看目标对象是否实现了接口。以Spring框架所选择的代理模式举例
在Spring的AOP编程中:
- 如果加入容器的目标对象有实现接口,用JDK代理。
- 如果目标对象没有实现接口,用Cglib代理。
4 策略
4.1 介绍
在策略模式(Strategy Pattern)中,一个类的行为或其算法可以在运行时更改。这种类型的设计模式属于行为型模式。
在策略模式中,我们创建表示各种策略的对象和一个行为随着策略对象改变而改变的 context 对象。策略对象改变 context 对象的执行算法。
意图:定义一系列的算法,把它们一个个封装起来, 并且使它们可相互替换。
主要解决:在有多种算法相似的情况下,使用 if…else 所带来的复杂和难以维护。
何时使用:一个系统有许多许多类,而区分它们的只是他们直接的行为。
如何解决:将这些算法封装成一个一个的类,任意地替换。
关键代码:实现同一个接口。
应用实例: 1、旅行的出游方式,选择骑自行车、坐汽车,每一种旅行方式都是一个策略。
优点: 1、算法可以自由切换。 2、避免使用多重条件判断。 3、扩展性良好。
缺点: 1、策略类会增多。 2、所有策略类都需要对外暴露。
使用场景: 1、如果在一个系统里面有许多类,它们之间的区别仅在于它们的行为,那么使用策略模式可以动态地让一个对象在许多行为中选择一种行为。 2、一个系统需要动态地在几种算法中选择一种。 3、如果一个对象有很多的行为,如果不用恰当的模式,这些行为就只好使用多重的条件选择语句来实现。
注意事项:如果一个系统的策略多于四个,就需要考虑使用混合模式,解决策略类膨胀的问题。
4.2 实现
我们将创建一个定义活动的 Strategy 接口和实现了 Strategy 接口的实体策略类。Context 是一个使用了某种策略的类。
public interface Strategy { public int doOperation(int num1, int num2); } public class OperationAdd implements Strategy{ @Override public int doOperation(int num1, int num2) { return num1 + num2; } } public class OperationSubstract implements Strategy{ @Override public int doOperation(int num1, int num2) { return num1 - num2; } }
public class Context { private Strategy strategy; public Context(Strategy strategy){ this.strategy = strategy; } public int executeStrategy(int num1, int num2){ return strategy.doOperation(num1, num2); } //测试 public static void main(String[] args) { Context context = new Context(new OperationAdd()); System.out.println("10 + 5 = " + context.executeStrategy(10, 5)); context = new Context(new OperationSubstract()); System.out.println("10 - 5 = " + context.executeStrategy(10, 5)); } }
5 单例
5.1 介绍
这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
注意:
- 单例类只能有一个实例。
- 单例类必须自己创建自己的唯一实例。
- 单例类必须给所有其他对象提供这一实例。
意图:保证一个类仅有一个实例,并提供一个访问它的全局访问点。
主要解决:一个全局使用的类频繁地创建与销毁。
何时使用:当您想控制实例数目,节省系统资源的时候。
如何解决:判断系统是否已经有这个单例,如果有则返回,如果没有则创建。
关键代码:构造函数是私有的。
注意事项:getInstance() 方法中需要使用同步锁 synchronized (Singleton.class) 防止多线程同时进入造成 instance 被多次实例化。
5.2 实现(仅介绍线程安全的实现)
注意:下面仅用线程安全的方式实现。
懒汉式(效率较低):
public class Singleton { private static Singleton instance; private Singleton (){} public static synchronized Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } }
饿汉式(较常用,但容易产生垃圾对象,基于 classloader 机制避免了多线程的同步问题,instance 在类装载时就实例化)
public class Singleton { private static Singleton instance = new Singleton(); private Singleton (){} public static Singleton getInstance() { return instance; } }
6 适配器
6.1 介绍
适配器模式是作为两个不兼容的接口之间的桥梁。这种类型的设计模式属于结构型模式,它结合了两个独立接口的功能。
这种模式涉及到一个单一的类(即Adapter),该类负责加入独立的或不兼容的接口功能。举个真实的例子,读卡器是作为内存卡和笔记本之间的适配器。您将内存卡插入读卡器,再将读卡器插入笔记本,这样就可以通过笔记本来读取内存卡。
我们通过下面的实例来演示适配器模式的使用。其中,音频播放器设备只能播放 mp3 文件,通过使用一个更高级的音频播放器来播放 vlc 和 mp4 文件。
意图:将一个类的接口转换成客户希望的另外一个接口。适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
主要解决:主要解决在软件系统中,常常要将一些”现存的对象”放到新的环境中,而新环境要求的接口是现对象不能满足的。
何时使用: 1、系统需要使用现有的类,而此类的接口不符合系统的需要。 2、想要建立一个可以重复使用的类,用于与一些彼此之间没有太大关联的一些类,包括一些可能在将来引进的类一起工作,这些源类不一定有一致的接口。 3、通过接口转换,将一个类插入另一个类系中。(比如老虎和飞禽,现在多了一个飞虎,在不增加实体的需求下,增加一个适配器,在里面包容一个虎对象,实现飞的接口。)
如何解决:继承或依赖(推荐)。
关键代码:适配器继承或依赖已有的对象,实现想要的目标接口。
缺点: 1、过多地使用适配器,会让系统非常零乱,不易整体进行把握。比如,明明看到调用的是 A 接口,其实内部被适配成了 B 接口的实现,一个系统如果太多出现这种情况,无异于一场灾难。因此如果不是很有必要,可以不使用适配器,而是直接对系统进行重构。 2.由于 JAVA 至多继承一个类,所以至多只能适配一个适配者类,而且目标类必须是抽象类。
注意事项:适配器不是在详细设计时添加的,而是解决正在服役的项目的问题。
6.2 实现
我们有一个 MediaPlayer 接口和一个实现了 MediaPlayer 接口的实体类 AudioPlayer。默认情况下,AudioPlayer 可以播放 mp3 格式的音频文件。
我们还有另一个接口 AdvancedMediaPlayer 和实现了 AdvancedMediaPlayer 接口的实体类。该类可以播放 vlc 和 mp4 格式的文件。
我们想要让 AudioPlayer 播放其他格式的音频文件。为了实现这个功能,我们需要创建一个实现了 MediaPlayer 接口的适配器类 MediaAdapter,并使用 AdvancedMediaPlayer 对象来播放所需的格式。
AudioPlayer 使用适配器类 MediaAdapter 传递所需的音频类型,不需要知道能播放所需格式音频的实际类。
接口:
public interface MediaPlayer { void play(String audioType, String fileName); } public interface AdvancedMediaPlayer { void playVlc(String fileName); void playMp4(String fileName); }
实现类:
public class Mp4Player implements AdvancedMediaPlayer { @Override public void playVlc(String fileName) { //什么也不做 } @Override public void playMp4(String fileName) { System.out.println("Playing mp4 file. Name: " + fileName); } } public class VlcPlayer implements AdvancedMediaPlayer{ @Override public void playVlc(String fileName) { System.out.println("Playing vlc file. Name: "+ fileName); } @Override public void playMp4(String fileName) { //什么也不做 } } public class AudioPlayer implements MediaPlayer { MediaAdapter mediaAdapter; @Override public void play(String audioType, String fileName) { //播放 mp3 音乐文件的内置支持 if (audioType.equalsIgnoreCase("mp3")) { System.out.println("Playing mp3 file. Name: " + fileName); } } }
现在我们要让 AudioPlayer 支持播放 Mp4和Vlc,但又不能改变接口,这时就要使用适配器了。
适配器:
public class MediaAdapter implements MediaPlayer { AdvancedMediaPlayer advancedMusicPlayer; public MediaAdapter(String audioType) { if (audioType.equalsIgnoreCase("vlc")) { advancedMusicPlayer = new VlcPlayer(); } else if (audioType.equalsIgnoreCase("mp4")) { advancedMusicPlayer = new Mp4Player(); } } @Override public void play(String audioType, String fileName) { if (audioType.equalsIgnoreCase("vlc")) { advancedMusicPlayer.playVlc(fileName); } else if (audioType.equalsIgnoreCase("mp4")) { advancedMusicPlayer.playMp4(fileName); } } }
使用适配器改造 AudioPlayer:
public class AudioPlayer implements MediaPlayer { MediaAdapter mediaAdapter; @Override public void play(String audioType, String fileName) { //播放 mp3 音乐文件的内置支持 if (audioType.equalsIgnoreCase("mp3")) { System.out.println("Playing mp3 file. Name: " + fileName); } //mediaAdapter 提供了播放其他文件格式的支持 else if (audioType.equalsIgnoreCase("vlc") || audioType.equalsIgnoreCase("mp4")) { mediaAdapter = new MediaAdapter(audioType); mediaAdapter.play(audioType, fileName); } else { System.out.println("Invalid media. " + audioType + " format not supported"); } } //测试 public static void main(String[] args) { //使用 AudioPlayer 来播放不同类型的音频格式。 AudioPlayer audioPlayer = new AudioPlayer(); audioPlayer.play("mp3", "beyond the horizon.mp3"); audioPlayer.play("mp4", "alone.mp4"); audioPlayer.play("vlc", "far far away.vlc"); audioPlayer.play("avi", "mind me.avi"); } }
6.4 HandlerInterceptorAdapter
其实SpringMVC下,就有一个 HandlerInterceptorAdapter,下面我们看下源码:
package org.springframework.web.servlet.handler; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.lang.Nullable; import org.springframework.web.servlet.AsyncHandlerInterceptor; import org.springframework.web.servlet.ModelAndView; public abstract class HandlerInterceptorAdapter implements AsyncHandlerInterceptor { /** * This implementation always returns {@code true}. */ @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { return true; } /** * This implementation is empty. */ @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception { } /** * This implementation is empty. */ @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception { } /** * This implementation is empty. */ @Override public void afterConcurrentHandlingStarted(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { } }
package org.springframework.web.servlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.web.method.HandlerMethod; public interface AsyncHandlerInterceptor extends HandlerInterceptor { default void afterConcurrentHandlingStarted(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { } }
7 装饰器
7.1 介绍
装饰器模式允许向一个现有的对象添加新的功能,同时又不改变其结构。这种类型的设计模式属于结构型模式,它是作为现有的类的一个包装。
这种模式创建了一个装饰类,用来包装原有的类,并在保持类方法签名完整性的前提下,提供了额外的功能。
意图:动态地给一个对象添加一些额外的职责。就增加功能来说,装饰器模式相比生成子类更为灵活。
主要解决:一般的,我们为了扩展一个类经常使用继承方式实现,由于继承为类引入静态特征,并且随着扩展功能的增多,子类会很膨胀。
何时使用:在不想增加很多子类的情况下扩展类。
如何解决:将具体功能职责划分,同时继承装饰者模式。
关键代码: 1、Component 类充当抽象角色,不应该具体实现。 2、修饰类引用和继承 Component 类,具体扩展类重写父类方法。
优点:装饰类和被装饰类可以独立发展,不会相互耦合,装饰模式是继承的一个替代模式,装饰模式可以动态扩展一个实现类的功能。
缺点:多层装饰比较复杂。
使用场景: 1、扩展一个类的功能。 2、动态增加功能,动态撤销。
注意事项:可代替继承。
7.2 实现
我们将创建一个 Shape 接口和实现了 Shape 接口的实体类。然后我们创建一个实现了 Shape 接口的抽象装饰类 ShapeDecorator,并把 Shape对象作为它的实例变量。
RedShapeDecorator 是实现了 ShapeDecorator 的实体类。
public interface Shape { void draw(); } public class Circle implements Shape { @Override public void draw() { System.out.println("Shape: Circle"); } } public class Rectangle implements Shape { @Override public void draw() { System.out.println("Shape: Rectangle"); } }
//装饰器类 public abstract class ShapeDecorator implements Shape { protected Shape decoratedShape; public ShapeDecorator(Shape decoratedShape){ this.decoratedShape = decoratedShape; } public void draw(){ decoratedShape.draw(); } }
public class RedShapeDecorator extends ShapeDecorator { public RedShapeDecorator(Shape decoratedShape) { super(decoratedShape); } @Override public void draw() { decoratedShape.draw(); setRedBorder(decoratedShape); } private void setRedBorder(Shape decoratedShape){ System.out.println("Border Color: Red"); } //测试 public static void main(String[] args) { //Circle with normal border Shape circle = new Circle(); circle.draw(); //Circle of red border Shape redCircle = new RedShapeDecorator(new Circle()); redCircle.draw(); //Rectangle of red border Shape redRectangle = new RedShapeDecorator(new Rectangle()); redRectangle.draw(); } }
更多设计模式介绍请点击:设计模式