1.简述
设计模式(Design pattern)是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性。 毫无疑问,设计模式于己于他人于系统都是多赢的,设计模式使代码编制真正工程化,设计模式是软件工程的基石,如同大厦的一块块砖石一样。项目中合理的运用设计模式可以完美的解决很多问题,每种模式在现在中都有相应的原理来与之对应,每一个模式描述了一个在我们周围不断重复发生的问题,以及该问题的核心解决方案,这也是它能被广泛应用的原因。可以理解为:
模式:在某些场景下,针对某类问题的某种通用的解决方案。
场景:项目所在的环境
问题:约束条件,项目目标等
解决方案:通用、可复用的设计,解决约束达到目标。
2.设计模式分类
创建型模式:对象实例化的模式,创建型模式用于解耦对象的实例化过程。
结构型模式:把类或对象结合在一起形成一个更大的结构。
行为型模式:类和对象如何交互,及划分责任和算法。
3.分类中各种模式的关键点
创建型模式:
单例模式:某个类只能有一个实例,提供一个全局的访问点。
简单工厂:一个工厂类根据传入的参量决定创建出那一种产品类的实例。
工厂方法:定义一个创建对象的接口,让子类决定实例化那个类。
抽象工厂:创建相关或依赖对象的家族,而无需明确指定具体类。
建造者模式:封装一个复杂对象的构建过程,并可以按步骤构造。
原型模式:通过复制现有的实例来创建新的实例。
结构型模式:
适配器模式:将一个类的方法接口转换成客户希望的另外一个接口。
桥接模式:将抽象部分和它的实现部分分离,使它们都可以独立的变化。
组合模式:将对象组合成树形结构以表示“”部分-整体“”的层次结构。
装饰模式:动态的给对象添加新的功能。
外观模式:对外提供一个统一的方法,来访问子系统中的一群接口。
亨元(蝇量)模式:通过共享技术来有效的支持大量细粒度的对象。
代理模式:为其他对象提供一个代理以便控制这个对象的访问。
行为型模式:
访问者模式:在不改变数据结构的前提下,增加作用于一组对象元素的新功能。
模板模式:定义一个算法结构,而将一些步骤延迟到子类实现。
策略模式:定义一系列算法,把他们封装起来,并且使它们可以相互替换。
状态模式:允许一个对象在其对象内部状态改变时改变它的行为。
观察者模式:对象间的一对多的依赖关系。
备忘录模式:在不破坏封装的前提下,保持对象的内部状态。
中介者模式:用一个中介对象来封装一系列的对象交互。
迭代器模式:一种遍历访问聚合对象中各个元素的方法,不暴露该对象的内部结构。
解释器模式:给定一个语言,定义它的文法的一种表示,并定义一个解释器。
命令模式:将命令请求封装为一个对象,使得可以用不同的请求来进行参数化。
责任链模式:将请求的发送者和接收者解耦,使的多个对象都有处理这个请求的机会。
4.介绍23种设计模式
(1)单例模式
单例模式,它的定义就是确保某一个类只有一个实例,并且提供一个全局访问点。
因此当系统中只需要一个实例对象或者系统中只允许一个公共访问点,除了这个公共访问点外,不能通过其他访问点访问该实例时,可以使用单例模式。
单例模式的优点:
- 在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例。
- 避免对资源的多重占用(比如写文件操作)。
单例模式的缺点:
- 没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。
单例模式的使用场景:
- 要求生产唯一序列号。
- WEB 中的计数器,不用每次刷新都在数据库里加一次,用单例先缓存起来。
- 创建的一个对象需要消耗的资源过多,比如 I/O 与数据库的连接等。
单例模式的实现举例如下:
// 饿汉式单例 public class Singleton { // 指向自己实例的私有静态引用,主动创建 private static Singleton singleton = new Singleton(); // 私有的构造方法 private Singleton(){} // 以自己实例为返回值的静态的公有方法,静态工厂方法 public static Singleton getSingleton(){ return singleton; } }
类加载的方式是按需加载,且加载一次。上述单例类被加载时,就会实例化一个对象并交给自己的引用,供系统使用;而且,由于这个类在整个生命周期中只会被加载一次,因此只会创建一个实例,即能够充分保证单例。
// 懒汉式单例 public class Singleton { // 指向自己实例的私有静态引用 private static Singleton singleton; // 私有的构造方法 private Singleton(){} // 以自己实例为返回值的静态的公有方法,静态工厂方法 public static Singleton getSingleton(){ // 被动创建,在真正需要使用时才去创建 if (singleton == null) { singleton = new Singleton(); } return singleton; } }
从懒汉式单例可以看到,单例实例被延迟加载,即只有在真正使用的时候才会实例化一个对象并交给自己的引用。
这种写法起到了Lazy Loading的效果,但是只能在单线程下使用。如果在多线程下,一个线程进入了if (singleton == null)判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,这时便会产生多个实例。所以在多线程环境下不可使用这种方式。
//双重加锁创建单例 public class Singleton{ private static Singleton instance; //程序运行时创建一个静态只读的进程辅助对象 private static readonly object syncRoot = new object(); private Singleton() { } public static Singleton GetInstance(){ //先判断是否存在,不存在再加锁处理 if (instance == null){ //在同一个时刻加了锁的那部分程序只有一个线程可以进入 synchronized(syncRoot){ if (instance == null){ instance = new Singleton(); } } } return instance; } }
如代码中所示,我们进行了两次if (singleton == null)检查,这样就可以保证线程安全了。这样,实例化代码只用执行一次,后面再次访问时,判断if (singleton == null),直接return实例化对象。
使用双重检测同步延迟加载去创建单例的做法是一个非常优秀的做法,其不但保证了单例,而且切实提高了程序运行效率
优点:线程安全;延迟加载;效率较高。
//静态代码块实现线程单例 public class SingletonUtils{ //在第一次引用类的任何成员时创建实例,公共语言运行库负责处理变量初始化 private static Queue<Bean> queue = new ConcurrentLinkedQueue<Bean>(); static { new Thread(new Runnable() { public void run() { SingletonUtils.start(); } }).start(); } public static void addData(Bean bean) { queue.add(bean); } private static void start() { while (true) { while (!queue.isEmpty()) { try { //处理数据方法 handleData(queue.poll()); Thread.yield(); Thread.sleep(10); } catch (Exception e) { e.printStackTrace(); } } Thread.yield(); Thread.sleep(1000); } } }
单例模式的实现方法还有很多。但是,这四种是比较经典的实现,也是我们应该掌握的几种实现方式。从这四种实现中,我们可以总结出,要想实现效率高的线程安全的单例,我们必须注意以下两点:
- 尽量减少同步块的作用域。
- 尽量使用细粒度的锁。
(2)简单工厂
简单工厂,它的定义就是提供一个创建对象实例的功能,而无须关心其具体实现。被创建实例的类型可以是接口、抽象类,也可以是具体的类。
简单工厂就相当于创建实例对象的new,我们经常要根据类Class生成实例对象,如A a=new A() 工厂模式也是用来创建实例对象的,所以以后new时就要多个心眼,是否可以考虑实用工厂模式,虽然这样做,可能多做一些工作,但会给你系统带来更大的可扩展性和尽量少的修改量。
简单工厂的优点:
- 不需要关心类的创建细节。
- 减轻类之间的耦合依赖,具体类的实现只是依赖于简单工厂,而不依赖其他类。
简单工厂的缺点:
- 每增加一个产品,相应的也要增加一个子工厂,加大了额外的开发量。
简单工厂的使用场景:
- 想要隔离具体实现,让客户端只通过接口操作。
- 想要把创建对象的职责集中管理和控制。
简单工厂的实现举例如下:
/*抽象接口 */ interface Colour { /*获得相应的颜色 */ public void get(); } /*红色对抽象接口的实现 */ class RedColour implements Colour{ //获取一份红色的颜料 public void get(){ System.out.println("我要一份红色的颜料"); } } /*黄色对抽象接口的实现 */ class YellowColour implements Colour{ //获取一份黄色的颜料 public void get(){ System.out.println("我要一份黄色的颜料"); } } class ColourFactory { public static Colour getColour(String type){ try { if(type.equalsIgnoreCase("redColour")) { return RedColour.class.newInstance(); } else if(type.equalsIgnoreCase("yellowColour")) { return YellowColour.class.newInstance(); } else { System.out.println("找不到相应的实例化类!"); return null; } } catch (Exception e) { System.out.println("实例化类出现异常,异常原因为:"+e.getMessage()); e.printStackTrace(); return null; } } } public class Test{ public static void main(String[] args) { //实例化各种颜料 Colour redColour = ColourFactory.getColour("redColour"); Colour yellowColour = ColourFactory.getColour("yellowColour"); //获取颜料 if(redColour!=null){ redColour.get(); } if(yellowColour!=null){ yellowColour.get(); } } }
运行结果
我要一份红色的颜料
我要一份黄色的颜料
简单工厂模式相对于23种设计模式来说要简单一些,在我们不知不觉中可能已经用过很多次简单工厂模式——尽管我们当初并不知道。
(3)工厂方法
工厂方法,它的定义就是定义了一个创建对象的接口,但由子类决定要实例化的类是哪一个,也就是说工厂方法模式让实例化推迟到子类。
工厂方法模式中,工厂父类负责定义创建产品对象的公共接口,而工厂子类负责生成具体的产品对象,这样做的目的是将产品类的实例操作延迟到工厂子类中完成,即通过工厂子类来确定究竟应该实例化哪一个具体产品类。
工厂方法的优点:
- 用户只需要关心所需产品对应的工厂,无需关心创建细节,甚至无需知道具体产品类名。
- 所有的具体工厂类都具有同一抽象父类,被称为多态工厂模式。
- 符合开闭原则,新增产品只需要添加工厂类和具体产品,无需修改代码,扩展性好。
工厂方法的缺点:
- 添加一个新的产品,系统中类的个数增加,导致增加了系统的复杂性,有更多的类需要编译和运行,会增加系统性能的开销。
- 由于考虑到系统的可扩展性,需要引入抽象层,在客户端代码中均使用抽象层进行定义,增加了系统的抽象性和理解难度。
工厂方法的使用场景:
- 与简单工厂类似
工厂方法的实现举例如下:
/**抽象接口 */ interface Sender { //发送信息 public void Send(); } /**发送eEmail对抽象接口的实现 */ class EmailSender implements Sender { public void Send() { System.out.println("this is eMailsender!"); } } /**发送短信对抽象接口的实现 */ class SmsSender implements Sender { public void Send() { System.out.println("this is sms sender!"); } } /**抽象工厂接口 */ interface Provider { public Sender produce(); } /**发送邮件工厂子类 */ class SendEmailFactory implements Provider { public Sender produce(){ return new EmailSender(); } } /**发送短信工厂子类 */ class SendSmsFactory implements Provider{ public Sender produce() { return new SmsSender(); } } public class Test{ public static void main(String[] args) { Provider provider = null; provider = new SendEmailFactory(); Sender sender = provider.produce(); sender.Send(); provider = new SendSmsFactory(); sender = provider.produce(); sender.Send(); } }
运行结果
this is eMailsender!
this is sms sender!
这个模式的好处就是,如果你现在想增加一个功能:发及时信息,则只需做一个实现类,实现Sender接口,同时做一个工厂类,实现Provider接口,就OK了,无需去改动现成的代码。这样做,拓展性较好!
工厂方法的特点:
- 一个抽象产品类,可以派生出多个具体产品类。
- 一个抽象工厂类,可以派生出多个具体工厂类。
- 每个具体工厂类只能创建一个具体产品类的实例。
(4)抽象工厂
抽象工厂,它的定义就是提供一个接口,用于创建相关或者依赖对象的家族,而不需要明确指定具体类。
抽象工厂的优点:
- 抽象工厂模式隔离了具体类的生产,使得客户并不需要知道什么被创建。
- 当一个产品族中的多个对象被设计成一起工作时,它能保证客户端始终只使用同一个产品族中的对象。
- 增加新的具体工厂和产品族很方便,无须修改已有系统,符合开闭原则
抽象工厂的缺点:
- 规定了所有可能被创建的产品集合,产品族中扩展新的产品困难。
- 增加了系统的抽象性和理解难度
抽象工厂的使用场景:
- 适合于产品之间相互关联、相互依赖且相互约束的地方
- 需要动态切换产品族的地方
抽象工厂的实现举例如下:
/**抽象接口 */ interface Fruit { public void get(); } /**苹果抽象类 */ abstract class Apple implements Fruit{ public abstract void get(); } /**香蕉抽象类 */ abstract class Banana implements Fruit{ public abstract void get(); } /**中国苹果抽象类 */ class ChinaApple extends Apple { public void get() { System.out.println("中国的苹果..."); } } /**英国苹果抽象类 */ class EnglandApple extends Apple { public void get() { System.out.println("英国的苹果..."); } } /**中国香蕉抽象类 */ class ChinaBanana extends Banana { public void get() { System.out.println("中国的香蕉..."); } } /**英国香蕉抽象类 */ class EnglandBanana extends Banana { public void get() { System.out.println("英国的香蕉..."); } } /**抽象工厂接口 */ interface FruitFactory { //实例化苹果 public Fruit getApple(); //实例化香蕉 public Fruit getBanana(); } /**中国的工厂实现类 */ class ChinaFactory implements FruitFactory { public Fruit getApple() { return new ChinaApple(); } public Fruit getBanana() { return new ChinaBanana(); } } /**英国的工厂实现类 */ class EnglandFactory implements FruitFactory { public Fruit getApple() { return new EnglandApple(); } public Fruit getBanana() { return new EnglandBanana(); } } public class Test{ public static void main(String[] args) { //创建中国工厂 FruitFactory chinaFactory = new ChinaFactory(); //通过中国工厂生产中国苹果实例 Fruit apple = chinaFactory.getApple(); apple.get(); //通过中国工厂生产中国香蕉实例 Fruit banana = chinaFactory.getBanana(); banana.get(); //创建英国工厂 FruitFactory englandFactory = new EnglandFactory(); //通过英国工厂生产英国苹果实例 apple = englandFactory.getApple(); apple.get(); //通过英国工厂生产英国香蕉实例 banana = englandFactory.getBanana(); banana.get(); } }
- 多个抽象产品类,每个抽象产品类可以派生出多个具体产品类。
- 一个抽象工厂类,可以派生出多个具体工厂类。
- 每个具体工厂类可以创建多个具体产品类的实例,也就是创建的是一个产品线下的多个产品。
(5)建造者模式
建造者模式,它的定义就将一个复杂对象的构建与它表示分离,使得同样的构建过程可以创建不同的表示。
建造者模式的优点:
- 客户端不必知道产品内部组成的细节,将产品本身与产品的创建过程解耦,使得相同的创建过程可以创建不同的产品对象。
- 每一个具体建造者都独立,因此可以方便地替换具体建造者或增加新的具体建造者, 用户使用不同的具体建造者即可得到不同的产品对象 。
- 可以更加精细地控制产品的创建过程 。将复杂产品的创建步骤分解在不同的方法中,使得创建过程更加清晰,也更方便使用程序来控制创建过程。
- 增加新的具体建造者无须修改原有类库的代码,指挥者类针对抽象建造者类编程,系统扩展方便,符合“开闭”。
建造者模式的缺点:
- 当建造者过多时,会产生很多类,难以维护。
- 建造者模式所创建的产品一般具有较多的共同点,其组成部分相似,若产品之间的差异性很大,则不适合使用该模式,因此其使用范围受到一定限制。
- 若产品的内部变化复杂,可能会导致需要定义很多具体建造者类来实现这种变化,导致系统变得很庞大。
建造者模式的使用场景:
- 需要生成的对象具有复杂的内部结构。
- 需要生成的对象内部属性本身相互依赖。
建造者模式的实现举例如下:
/**产品类Product,由多个部件组成 */ class Product{ List<String> parts = new ArrayList<String>(); //添加产品部件 public void add(String part){ parts.add(part); } public void show(){ System.out.println("创建产品"); for (String part : parts){ System.out.println(part); } } } /**抽象建造者类Builder */ abstract class Builder{ public abstract void BuilderPartA(); public abstract void BuilderPartB(); public abstract Product GetResult(); } //具体建造者类ComponentBuilder1 class ComponentBuilder1 extends Builder{ private Product product = new Product(); //建造具体的两个部件 public void BuilderPartA(){ product.add("部件A"); } public void BuilderPartB(){ product.add("部件B"); } public Product GetResult(){ return product; } } //具体建造者类ComponentBuilder2 class ComponentBuilder2 extends Builder{ private Product product = new Product(); //建造具体的两个部件 public void BuilderPartA(){ product.add("部件C"); } public void BuilderPartB(){ product.add("部件D"); } public Product GetResult(){ return product; } } /**指挥者类Director */ class Director{ public void Construct(Builder builder){ //用来指挥建造过程 builder.BuilderPartA(); builder.BuilderPartB(); } } public class Test{ public static void main(String[] args) { Director director = new Director(); Builder b1 = new ComponentBuilder1(); Builder b2 = new ComponentBuilder2(); //指挥者用ConcreteBuilder1方法建造产品 director.Construct(b1); Product p1 = b1.GetResult(); p1.show(); //指挥者用ConcreteBuilder2方法建造产品 director.Construct(b2); Product p2 = b2.GetResult(); p2.show(); } }
建造者模式的使用场合是当创建复杂对象时,把创建对象成员和装配方法分离出来,放在建造者类中去实现,用户使用该复杂对象时,不用理会它的创建和装配过程,只关心它的表示形式。
建造者模式与抽象工厂模式的比较:
- 与抽象工厂模式相比,建造者模式返回一个组装好的完整产品,而抽象工厂模式返回一系列相关的产品,这些产品位于不同的产品等级结构,构成了一个产品族 。
- 在抽象工厂模式中,客户端实例化工厂类,然后调用工厂方法获取所需产品对象,而在建造者模式中,客户端可以不直接调用建造者的相关方法,而是通过指挥者类来指导如何生成对象,包括对象的组装过程和建造步骤,它侧重于一步步构造一个复杂对象,返回一个完整的对象 。
- 如果将抽象工厂模式看成汽车配件生产工厂,生产一个产品族的产品,那么建造者模式就是一个汽车组装工厂,通过对部件的组装可以返回一辆完整的汽车
(6)原型模式
原型模式,它的定义是用原型实例指定创建对象的种类,并通过拷贝这些原型创建新的对象。
原型模式的优点:
- 性能较好:原型模式是在内存二进制流的拷贝,要比直接new一个对象性能好很多,特别是在一个循环体内产生大量的对象时,原型模式可以更好地体现其优点。
- 逃避构造函数的约束:这既是它的优点也是缺点,直接在内存中拷贝,构造函数不会执行。
原型模式的缺点:
- 配备克隆方法需要对类的功能进行通盘考虑,这对于全新的类不是很难,但对于已有的类不一定很容易,特别当一个类引用不支持串行化的间接对象,或者引用含有循环结构的时候。
- 实现原型模式每个派生类都必须实现 Clone接口。
- 逃避构造函数的约束。
原型模式的使用场景:
- 资源优化场景。
- 类初始化需要消化非常多的资源,这个资源包括数据、硬件资源等。
- 性能和安全要求的场景。
- 通过 new 产生一个对象需要非常繁琐的数据准备或访问权限,则可以使用原型模式。
- 一个对象多个修改者的场景。
- 一个对象需要提供给其他对象访问,而且各个调用者可能都需要修改其值时,可以考虑使用原型模式拷贝多个对象供调用者使用。
- 在实际项目中,原型模式很少单独出现,一般是和工厂方法模式一起出现,通过 clone 的方法创建一个对象,然后由工厂方法提供给调用者。
原型模式的实现举例如下:
public class Data { public static void main(String[] args) { CloneComply cloneComply = new CloneComply(); CloneComply cloneComply2 = cloneComply.clone(); } } /**Cloneable实现类 */ class CloneComply implements Cloneable { public CloneComply(){ System.out.println("我来了......"); } public void setName(String name) { this.setName(name); } public String getName() { return this.getName(); } @Override protected CloneComply clone(){ CloneComply cloneComply = null; try { cloneComply = (CloneComply) super.clone(); }catch (CloneNotSupportedException e){ e.printStackTrace(); } return cloneComply; } }
运行结果
我来了......
可以看到只输出了一次,证明了对象拷贝的时候构造函数是不会执行的,原因在于拷贝是直接在堆中进行,这其实也可以理解,new的时候,JVM要走一趟类加载流程,这个流程非常麻烦,在类加载流程中会调用构造函数,最后生成的对象会放到堆中,而拷贝就是直接拷贝堆中的现成的二进制对象,然后重新一个分配内存块。
public class Test{ public static void main(String[] args) { CloneComply cloneComply = new CloneComply(); cloneComply.setName("我来了......"); System.out.println(cloneComply.getName()); CloneComply cloneComply2 = cloneComply.clone(); System.out.println(cloneComply2.getName()); } } /**产品类Product */ class Product{ private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } } /**Cloneable实现类 */ class CloneComply implements Cloneable { private Product product = new Product(); public void setName(String name) { this.product.setName(name); } public String getName() { return this.product.getName(); } @Override protected CloneComply clone(){ CloneComply cloneComply = null; try { cloneComply = (CloneComply) super.clone(); }catch (CloneNotSupportedException e){ e.printStackTrace(); } return cloneComply; } }
运行结果
我来了......
我来了......
可以看到两次输出结果都是一样的,这就是浅拷贝,浅拷贝是指在拷贝对象时,对于基本数据类型的变量会重新复制一份,而对于引用类型的变量只是对直接引用进行拷贝,没有对直接引用指向的对象进行拷贝。
public class Data { public static void main(String[] args) { CloneComply cloneComply = new CloneComply(); cloneComply.setName("我来了......"); System.out.println(cloneComply.getName()); CloneComply cloneComply2 = cloneComply.clone(); System.out.println(cloneComply2.getName()); } } /**产品类Product */ class Product{ private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } } /**Cloneable实现类 */ class CloneComply implements Cloneable { private Product product = new Product(); public void setName(String name) { this.product.setName(name); } public String getName() { return this.product.getName(); } @Override protected CloneComply clone(){ CloneComply cloneComply = null; try { cloneComply = (CloneComply) super.clone(); cloneComply.product = new Product(); }catch (CloneNotSupportedException e){ e.printStackTrace(); } return cloneComply; } }
运行结果
我来了......
null
这样就实现了完全的拷贝,两个对象之间没有任何瓜葛,你改你的,我改我的,互不影响,这种拷贝就叫做深拷贝。
(7)适配器模式
适配器模式,它的定义是将一个类的接口转换成客户希望的另外一个接口,适配器模式使得原本由于接口不兼容而不能在一起工作的那些类可以一起工作。
适配器模式的优点:
- 可以让任何两个没有关联的类一起运行。
- 提高了类的复用。
- 增加了类的透明度。
- 灵活性好。
适配器模式的缺点:
- 过多地使用适配器,会让系统非常零乱,不易整体进行把握。比如,明明看到调用的是 A 接口,其实内部被适配成了 B 接口的实现,一个系统如果太多出现这种情况,无异于一场灾难。因此如果不是很有必要,可以不使用适配器,而是直接对系统进行重构。
- 由于 JAVA 不能继承多个类,所以只能适配一个适配者类,而且目标类必须是抽象类。
适配器模式的使用场景:
- 有动机地修改一个正常运行的系统的接口,这时应该考虑使用适配器模式。
适配器模式的实现举例如下:
public class Test{ public static void main(String[] args) { // 使用普通功能类 Target concreteTarget = new ConcreteTarget();// 实例化一个普通类 concreteTarget.request(); // 使用特殊功能类,即适配类 Target adapter = new Adapter(); adapter.request(); } } // 已存在的、具有特殊功能、但不符合我们既有的标准接口的类 class Adaptee { public void specificRequest() { System.out.println("被适配类 具有特殊功能..."); } } // 目标接口,或称为标准接口 interface Target { public void request(); } // 具体目标类,只提供普通功能 class ConcreteTarget implements Target { public void request() { System.out.println("普通类 具有普通功能..."); } } // 适配器类,继承了被适配类,同时实现标准接口 class Adapter extends Adaptee implements Target { public void request() { super.specificRequest(); } }
运行结果
普通类 具有普通功能...
被适配类 具有特殊功能...
上面这种实现的适配器称为类适配器,因为 Adapter 类既继承了 Adaptee (被适配类),也实现了 Target 接口(因为 Java 不支持多继承,所以这样来实现),在 Client 类中我们可以根据需要选择并创建任一种符合需求的子类,来实现具体功能。
public class Test{ public static void main(String[] args) { // 使用普通功能类 Target concreteTarget = new ConcreteTarget(); concreteTarget.request(); // 使用特殊功能类,即适配类, // 需要先创建一个被适配类的对象作为参数 Target adapter = new Adapter(new Adaptee()); adapter.request(); } } // 已存在的、具有特殊功能、但不符合我们既有的标准接口的类 class Adaptee { public void specificRequest() { System.out.println("被适配类 具有特殊功能..."); } } // 目标接口,或称为标准接口 interface Target { public void request(); } // 具体目标类,只提供普通功能 class ConcreteTarget implements Target { public void request() { System.out.println("普通类 具有普通功能..."); } } // 适配器类,直接关联被适配类,同时实现标准接口 class Adapter implements Target { // 直接关联被适配类 private Adaptee adaptee; // 可以通过构造函数传入具体需要适配的被适配类对象 public Adapter(Adaptee adaptee) { this.adaptee = adaptee; } public void request() { // 这里是使用委托的方式完成特殊功能 this.adaptee.specificRequest(); } }
输出结果
普通类 具有普通功能...
被适配类 具有特殊功能...
上面这种实现的适配器称为对象适配器,它不是使用多继承或继承再实现的方式,而是使用直接关联,或者称为委托的方式。
(8)桥接模式
桥接模式,它的定义是将抽象部分与它的实现部分分离,使它们都可以独立地变化。它是一种对象结构型模式,又称为柄体(Handle and Body)模式或接口(Interface)模式。
桥接模式的优点:
- 分离抽象接口及其实现部分。
- 桥接模式有时类似于多继承方案,但是多继承方案违背了类的单一职责原则(即一个类只有一个变化的原因),复用性比较差,而且多继承结构中类的个数非常庞大,桥接模式是比多继承方案更好的解决方法。
- 桥接模式提高了系统的可扩充性,在两个变化维度中任意扩展一个维度,都不需要修改原有系统。
- 实现细节对客户透明,可以对用户隐藏实现细节。
桥接模式的缺点:
- 桥接模式的引入会增加系统的理解与设计难度,由于聚合关联关系建立在抽象层,要求开发者针对抽象进行设计与编程。
- 桥接模式要求正确识别出系统中两个独立变化的维度,因此其使用范围具有一定的局限性。
桥接模式的使用场景:
- 如果一个系统需要在构件的抽象化角色和具体化角色之间增加更多的灵活性,避免在两个层次之间建立静态的继承联系,通过桥接模式可以使它们在抽象层建立一个关联关系。
- 对于那些不希望使用继承或因为多层次继承导致系统类的个数急剧增加的系统,桥接模式尤为适用。
- 一个类存在两个独立变化的维度,且这两个维度都需要进行扩展。
桥接模式的实现举例如下:
public class Test{ public static void main(String[] args) { // 大杯咖啡:原味 new LargeCoffee(new PlainFlavor()).makeCoffee(); // 大杯咖啡:加糖 new LargeCoffee(new SugarFlavor()).makeCoffee(); // 小杯咖啡:原味 new SmallCoffee(new PlainFlavor()).makeCoffee(); // 小杯咖啡:加糖 new SmallCoffee(new SugarFlavor()).makeCoffee(); } } // Implementor:有无糖 interface ICoffeeFlavor { String addWhat(); } // ConcreteImplementor:原味 class PlainFlavor implements ICoffeeFlavor { @Override public String addWhat() { return "原味"; } } // ConcreteImplementor:加糖 class SugarFlavor implements ICoffeeFlavor { @Override public String addWhat() { return "加糖"; } } // Abstraction:咖啡 abstract class Coffee { protected ICoffeeFlavor mFlavor; public Coffee(ICoffeeFlavor flavor) { this.mFlavor = flavor; } public abstract void makeCoffee(); } // RefinedAbstraction:大杯咖啡 class LargeCoffee extends Coffee { public LargeCoffee(ICoffeeFlavor flavor) { super(flavor); } @Override public void makeCoffee() { System.out.println("大杯咖啡: " + this.mFlavor.addWhat()); } } // RefinedAbstraction:小杯咖啡 class SmallCoffee extends Coffee { public SmallCoffee(ICoffeeFlavor flavor) { super(flavor); } @Override public void makeCoffee() { System.out.println("小杯咖啡:" + this.mFlavor.addWhat()); } }
上面这个例子,我们采用桥接模式解耦了 “咖啡大小杯” 和 “有无糖(味道)” 这两个独立变化的维度。后续如果咖啡馆提供更多的服务,比如中杯咖啡,那么直接新建一个中杯类继承Coffee
即可。如果是咖啡味道更改,比如可以加牛奶,蜂蜜等,那么同样只需新建一个类实现ICoffeeFlavor
即可。
桥接模式遵循了设计模式里氏替换原则、依赖倒置原则,最终实现了 开闭原则,对修改关闭,对扩展开放。
(9)组合模式
组合模式,它的定义是将对象组合成树形结构以表示“部分-整体”的层次结构。组合模式使得用户可以使用一致的方法操作单个对象和组合对象。
组合模式的优点:
- 组合模式屏蔽了对象系统的层次差异性(树节点和叶子节点为不同类型),将客户代码与复杂的容器对象解耦,使得客户端可以忽略层次间的差异,使用一致的行为控制不同层次。
- 组合模式可以很方便地增加树枝节点和叶子节点 对象,并对现有类库无侵入,符合开闭原则。
组合模式的缺点:
- 如果类系统(树形结构)过于庞大,虽然对不同层次都提供一致性操作,但客户端仍需花费时间理清类之间的层次关系。
- 组合模式在具体实现上违背了设计模式接口隔离原则或依赖倒置原则。
组合模式的使用场景:
- 系统对象层次具备整体和部分,呈树形结构,且要求具备统一行为(如树形菜单,操作系统目录结构,公司组织架构等)。
组合模式的实现举例如下:
public class Test{ public static void main(String[] args) { // 创建根节点 Component root = new Composite("root"); // 创建树枝节点 Component branchA = new Composite("---branchA"); Component branchB = new Composite("------branchB"); // 创建叶子节点 Component leafA = new Leaf("------leafA"); Component leafB = new Leaf("---------leafB"); Component leafC = new Leaf("---leafC"); root.addChild(branchA); root.addChild(leafC); branchA.addChild(leafA); branchA.addChild(branchB); branchB.addChild(leafB); String result = root.operation(); System.out.println(result); } } // 抽象根节点 abstract class Component { protected String name; public Component(String name) { this.name = name; } public abstract String operation(); public boolean addChild(Component component) { throw new UnsupportedOperationException("addChild not supported!"); } public boolean removeChild(Component component) { throw new UnsupportedOperationException("removeChild not supported!"); } public Component getChild(int index) { throw new UnsupportedOperationException("getChild not supported!"); } } // 树节点 class Composite extends Component { private List<Component> mComponents; public Composite(String name) { super(name); this.mComponents = new ArrayList<>(); } @Override public String operation() { StringBuilder builder = new StringBuilder(this.name); for (Component component : this.mComponents) { builder.append(" "); builder.append(component.operation()); } return builder.toString(); } @Override public boolean addChild(Component component) { return this.mComponents.add(component); } @Override public boolean removeChild(Component component) { return this.mComponents.remove(component); } @Override public Component getChild(int index) { return this.mComponents.get(index); } } // 叶子节点 class Leaf extends Component { public Leaf(String name) { super(name); } @Override public String operation() { return this.name; } }
运行结果
root
---branchA
------leafA
------branchB
---------leafB
---leafC
上面这种实现称为透明组合模式把所有公共方法都定义在Component中,这样做的好处是客户端无需分辨是叶子节点(Leaf)和树枝节点(Composite),它们具备完全一致的接口。缺点是叶子节点(Leaf)会继承得到一些它所不需要(管理子类操作的方法)的方法,这与设计模式接口隔离原则相违背。
透明组合模式 中,由于Component 包含叶子节点所不需要的方法,因此,直接将这些方法默认抛出UnsupportedOperationException
异常。
public class Test{ public static void main(String[] args) { // 创建根节点 Composite root = new Composite("root"); // 创建树枝节点 Composite branchA = new Composite("---branchA"); Composite branchB = new Composite("------branchB"); // 创建叶子节点 Component leafA = new Leaf("------leafA"); Component leafB = new Leaf("---------leafB"); Component leafC = new Leaf("---leafC"); root.addChild(branchA); root.addChild(leafC); branchA.addChild(leafA); branchA.addChild(branchB); branchB.addChild(leafB); String result = root.operation(); System.out.println(result); } } // 抽象根节点 abstract class Component { protected String name; public Component(String name) { this.name = name; } public abstract String operation(); } // 树节点 class Composite extends Component { private List<Component> mComponents; public Composite(String name) { super(name); this.mComponents = new ArrayList<>(); } @Override public String operation() { StringBuilder builder = new StringBuilder(this.name); for (Component component : this.mComponents) { builder.append(" "); builder.append(component.operation()); } return builder.toString(); } public boolean addChild(Component component) { return this.mComponents.add(component); } public boolean removeChild(Component component) { return this.mComponents.remove(component); } public Component getChild(int index) { return this.mComponents.get(index); } } // 叶子节点 class Leaf extends Component { public Leaf(String name) { super(name); } @Override public String operation() { return this.name; } }
运行结果
root
---branchA
------leafA
------branchB
---------leafB
---leafC
上面这种实现称为安全组合模式统一行为(Component)只规定系统各个层次的最基础的一致行为,而把组合(树节点)本身的方法(管理子类对象的添加,删除等)放到自身当中。这样做的好处是接口定义职责清晰,符合设计模式单一职责原则和接口隔离原则。缺点是客户需要区分树枝节点(Composite)和叶子节点(Leaf),这样才能正确处理各个层次的操作,客户端无法依赖抽象(Component),违背了设计模式依赖倒置原则。
(10)装饰模式
装饰模式,它的定义是允许向一个现有的对象添加新的功能,同时又不改变其结构。这种类型的设计模式属于结构型模式,它是作为现有的类的一个包装。
装饰模式的优点:
- 动态扩展类功能,比类继承灵活,且对客户端透明。
- 继承关系的一种替代方案。相比与类继承的父子关系,装饰模式更像是一种组合关系(is-a)。
- 可以对同一个被装饰对象进行多次装饰,创建出不同行为的复合功能。
装饰模式的缺点:
- 多层装饰比较复杂(灵活的同时会带来复杂性的增加)。
- 装饰嵌套过多,会产生过多小对象(每个装饰层都创建一个相应的对象)。
- 装饰嵌套过多,易于出错,且调试排查比较麻烦(需要一层层对装饰器进行排查,以确定是哪一个装饰层出错)。
装饰模式的使用场景:
- 需要扩展一个类的功能,或给一个类增加附加功能。
- 需要动态地给一个对象增加功能,且这些功能可以再动态地撤销。
- 需要为一批的兄弟类进行改装或加装功能。
装饰模式的实现举例如下:
public class Test{ public static void main(String[] args) { IPerson person = new Man(); person.dress(); System.out.println("----------------------"); System.out.println("增加裤子适配器"); person = new TrousersDecorator(person); person.dress(); } } // 抽象组件(Component) interface IPerson { void dress(); } // 具体组件(ConcreteComponent),即被修饰者 class Man implements IPerson { @Override public void dress() { System.out.println("穿了内裤!"); } } // 抽象适配器(Decorator),接收一个具体的Component,本身也是一个Component abstract class ClothesDecorator implements IPerson { protected IPerson mPerson; // 构造方法强制子类构造必须传入一个IPerson public ClothesDecorator(IPerson person) { this.mPerson = person; } @Override public void dress() { this.mPerson.dress(); } } // 具体装饰器(ConcreteDecorator):裤子装饰器 class TrousersDecorator extends ClothesDecorator { public TrousersDecorator(IPerson person) { super(person); } @Override public void dress() { super.dress(); this.dressTrousers(); } private void dressTrousers() { System.out.println("穿上裤子了!"); } }
运行结果
穿了内裤!
----------------------
增加裤子适配器
穿了内裤!
穿上裤子了!
上面的举例中Man
这个IPerson
本身只穿了件内裤,衣裳不整,不能出门,因此我们使用装饰器为其增加了穿裤子的功能,最终满足了出门要求。
public class Test{ public static void main(String[] args) { IPerson person = new TrousersDecorator(new Man()); person.dress(); } } // 抽象组件(Component) interface IPerson { void dress(); } // 具体组件(ConcreteComponent),即被修饰者 class Man implements IPerson { @Override public void dress() { System.out.println("穿了内裤!"); } } // 抽象适配器(Decorator),接收一个具体的Component,本身也是一个Component abstract class ClothesDecorator implements IPerson { protected IPerson mPerson; // 构造方法强制子类构造必须传入一个IPerson public ClothesDecorator(IPerson person) { this.mPerson = person; } @Override public void dress() { this.mPerson.dress(); } } // 具体装饰器(ConcreteDecorator):裤子装饰器 class TrousersDecorator extends ClothesDecorator { public TrousersDecorator(IPerson person) { super(person); } @Override public void dress() { super.dress(); this.dressTrousers(); } private void dressTrousers() { System.out.println("穿上裤子了!"); } }
运行结果
穿了内裤!
穿上裤子了!
new Man()
这个IPerson
一件一件的进行衣服试穿,太浪费时间了,我们完全可以使用装饰器嵌套(因为装饰器接收一个IPerson
,而自己同时也是一个IPerson
,因此完全支持嵌套)模式,这样一次性就穿完。(11)外观模式
外观模式,它的定义是隐藏系统的复杂性,并向客户端提供了一个客户端可以访问系统的接口。这种类型的设计模式属于结构型模式,它向现有的系统添加一个接口,来隐藏系统的复杂性。
外观模式的优点:
- 简化了调用过程,无须了解、深入子系统,防止带来风险。
- 减少系统依赖、松散耦合。
- 更好的划分访问层次。
- 符合迪米特法则,即最少知道原则。
外观模式的缺点:
- 增加子系统、扩展子系统行为容易引入风险。
- 不符合开闭原则。
外观模式的使用场景:
- 子系统越来越复杂,增加外观模式提供简单调用接口。
- 构造多层系统结构,利用外观对象作为每层的入口,简化层间调用。
外观模式的实现举例如下:
public class Test{ public static void main(String[] args) { MobilePhone s10 = new MobilePhone(); s10.deil(); s10.takePicture(); } } /** * 外观类 */ class MobilePhone { private Phone mPhone = new phoneImpl(); private Camera mCamera = new CameraImpl(); public void deil() { mPhone.dail(); } public void close() { mPhone.hangup(); } public void takePicture() { mCamera.takePicture(); } } /** * 电话抽象接口 */ interface Phone { // 打电话 void dail(); // 挂电话 void hangup(); } /**相机抽象接口 */ interface Camera { // 拍照片 void takePicture(); } /**电话实现类 */ class phoneImpl implements Phone { @Override public void dail() { System.out.println("打电话"); } @Override public void hangup() { System.out.println("挂电话"); } } /**相机实现类 */ class CameraImpl implements Camera { @Override public void takePicture() { System.out.println("拍照片"); } }
(12)亨元(蝇量)模式
亨元模式,它的定义是通过共享方式高效支持大量细粒度对象. 将一个对象抽象出内部属性作为蝇量对象 抽象出外部属性 作为管理外部状态。
亨元模式的优点:
- 大大减少了对象的创建,降低了程序内存的占用,提高效率
亨元模式的缺点:
- 提高了系统的复杂度。需要分离出内部状态和外部状态,而外部状态具有固化特性,不应该随着内部状态的改变而改变,否则会造成系统的混乱。
亨元模式的使用场景:
- 系统中存在大量相似对象。
- 需要缓冲池的场景。
亨元模式的实现举例如下:
public class Test{ public static void main(String[] args) { for (int i = 0; i < 4; i++) { String key = "test" + i; FlyweightFactory.getFlyweight(key); } Flyweight flyweight = FlyweightFactory.getFlyweight("test0"); flyweight.operate(); } } /**抽象享元角色 */ abstract class Flyweight { private String intrinsic; // 外部状态 protected final String Extrinsic; // 要求享元角色必须接受外部状态 public Flyweight(String extrinsic) { Extrinsic = extrinsic; } // 定义业务操作 public abstract void operate(); // 内部状态的getter/setter public String getIntrinsic() { return intrinsic; } public void setIntrinsic(String intrinsic) { this.intrinsic = intrinsic; } } /**具体享元角色A */ class ConcreteFlyweightA extends Flyweight { // 接受外部状态 public ConcreteFlyweightA(String extrinsic) { super(extrinsic); } // 根据外部状态进行逻辑处理 @Override public void operate() { System.out.println("ConcreteFlyweightA"); } } /**具体享元角色B */ class ConcreteFlyweightB extends Flyweight { // 接受外部状态 public ConcreteFlyweightB(String extrinsic) { super(extrinsic); } // 根据外部状态进行逻辑处理 @Override public void operate() { System.out.println("ConcreteFlyweightB"); } } /**享元工厂 */ class FlyweightFactory { // 定义一个池容器 private static Map<String, Flyweight> pool = new HashMap<String, Flyweight>(); public static Flyweight getFlyweight(String Extrinsic) { // 需要返回的对象 Flyweight flyweight = null; // 在池中没有该对象 if (pool.containsKey(Extrinsic)) { flyweight = pool.get(Extrinsic); } else { // 根据外部状态创建享元对象 flyweight = new ConcreteFlyweightA(Extrinsic); // 放置到池中 pool.put(Extrinsic, flyweight); } return flyweight; } }
运行结果
ConcreteFlyweightA
上面的举例可以看出如果未找到匹配的对象,则创建新对象。享元模式(Flyweight Pattern)主要用于减少创建对象的数量,以减少内存占用和提高性能。
(13)代理模式
代理模式,它的定义是为其他对象提供一种代理以控制对这个对象的访问。
代理模式的优点:
- 职责清晰、高扩展性、智能化
代理模式的缺点:
- 由于在客户端和真实主题之间增加了代理对象,因此有些类型的代理模式可能会造成请求的处理速度变慢。
- 实现代理模式需要额外的工作,有些代理模式的实现非常复杂。
代理模式的使用场景:
- 当我们使用别人的代码的时候,可以通过代理,对方法增强,避免了修改别人的代码。Spring AOP。
代理模式的实现举例如下:
public class Test{ public static void main(String[] args) { Proxy proxy = new Proxy(); proxy.request(); } } /**抽象接口 */ interface Subject { void request(); } /**抽象接口实现类 */ class RealSubject implements Subject{ @Override public void request() { System.out.println("真实请求"); } } /**代理实现类 */ class Proxy implements Subject{ private RealSubject realSubject; @Override public void request() { if (realSubject == null) { realSubject = new RealSubject(); } realSubject.request(); System.out.println("代理请求"); } }
运行结果
真实请求
代理请求
上面的举例可以看出在不修改目标对象的功能前提下,对目标功能扩展。这种实现称为静态代理,静态代理在使用时,需要定义接口或者父类,被代理对象
与代理对象一起实现相同的接口或者是继承相同父类。这种实现方式的缺点,因为代理对象需要与目标对象实现一样的接口,所以会有很多代理类,类太多。同时,一旦接口增加方法,目标对象与代理对象都要维护。
public class Test{ public static void main(String[] args) { // 目标对象 Subject target = new RealSubject(); Subject proxy = (Subject)new ProxyFactory(target).getProxyInstance(); proxy.request(); } } /**抽象接口 */ interface Subject { void request(); } /**抽象接口实现类 */ class RealSubject implements Subject{ @Override public void request() { System.out.println("真实请求"); } } /** * 创建动态代理对象 * 动态代理不需要实现接口,但是需要指定接口类型 */ class ProxyFactory{ //维护一个目标对象 private Object target; public ProxyFactory(Object target){ this.target=target; } //给目标对象生成代理对象 public Object getProxyInstance(){ return 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; } } ); } }
运行结果
请求前...
真实请求
请求后...
上面的举例可以看出动态代理的关键,在于通过InvocationHandler和Proxy媒介,在运行时动态生成动态代理类,生成的动态代理类依然实现了Subject接口,并在调用方法时回调了InvocationHandler实现类的invoke方法,InvocationHandler实现类的invoke方法通过反射,回调了被代理实体的对应方法。
代理类所在包:java.lang.reflect.Proxy
JDK实现代理只需要使用newProxyInstance方法,但是该方法需要接收三个参数,完整的写法是:
- static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h);
方法内三个参数的意义是:
- ClassLoader loader:指定当前目标对象使用的类加载器,也就是代理对象和被代理对象使用相同的类加载器。
- Class<?>[] interfaces:目标对象实现的接口的类型。也就是代理对象和被代理对象具有相同的行为。实现相同的接口。
- InvocationHandler h:事件处理,执行目标对象的方法时,会触发事件处理器的方法,会把当前执行目标对象的方法作为参数传入。
在Spring的AOP编程中:
- 如果加入容器的目标对象有实现接口,用JDK代理。
- 如果目标对象没有实现接口,用Cglib代理(Spring的核心包中已经包括了Cglib功能,s
pring-core-3.2.5.jar
)。
(14)访问者模式
访问者模式,它的定义是将数据结构与数据操作分离。
访问者模式的优点:
- 各角色职责分离,符合单一职责原则。
- 具有优秀的扩展性。
- 使得数据结构和作用于结构上的操作解耦,使得操作集合可以独立变化。
- 灵活性。
访问者模式的缺点:
- 具体元素对访问者公布细节,违反了迪米特原则。
- 具体元素变更比较困难。
- 违反了依赖倒置原则,依赖了具体类,没有依赖抽象。
访问者模式的使用场景:
- 对象结构中对象对应的类很少改变,但经常需要在此对象结构上定义新的操作。
- 需要对一个对象结构中的对象进行很多不同的并且不相关的操作,而需要避免让这些操作污染这些对象的类,也不希望在增加新操作时修改这些类。
访问者模式的实现举例如下:
/**测试类 */ public class Test{ public static void main(String[] args) { List<Person> list = Allperson.getList(); System.out.println("得到名字:"); for(Person e: list){ e.accept(new VisitorName()); } System.out.println("得到年龄:"); for(Person e: list){ e.accept(new VisitorAge()); } } } /**抽象类 */ abstract class Person { public abstract void accept(IVisitor visitor); } /**学生类 */ class Student extends Person { private String name; private int age; public Student(String name,int age){ this.name = name; this.age = age; } public String getName() { return name; } public int getAge() { return age; } public void accept(IVisitor visitor) { visitor.visit(this); } } /**教师类 */ class Teacher extends Person { private String name; private int age; public Teacher(String name,int age){ this.name = name; this.age = age; } public int getAge() { return age; } public String getName() { return name; } public void accept(IVisitor visitor) { visitor.visit(this); } } /**访问者接口 */ interface IVisitor { public void visit(Student student); public void visit(Teacher teacher); } /**访问者名称实现类 */ class VisitorName implements IVisitor { public void visit(Student student) { System.out.println(student.getName()); } public void visit(Teacher teacher) { System.out.println(teacher.getName()); } } /**访问者年龄实现类 */ class VisitorAge implements IVisitor { public void visit(Student student) { System.out.println(student.getAge()); } public void visit(Teacher teacher) { System.out.println(teacher.getAge()); } } /**人员类 */ class Allperson { public static List<Person> getList(){ List<Person> list = new ArrayList<Person>(); Student s1 = new Student("路飞",20); Student s2 = new Student("索隆",19); Teacher t1 = new Teacher("红发",44); Teacher t2 = new Teacher("雷利",59); list.add(s1); list.add(s2); list.add(t1); list.add(t2); return list; } }
运行结果
得到名字:
路飞
索隆
红发
雷利
得到年龄:
20
19
44
59
上面的例子中的策略就相当于依次访问各个元素的访问者,每个元素可以接受(accept
)不同访问者作为参数,从而交由访问者做出不同的操作。
(15)模板模式
模板模式,它的定义是将不变的行为从子类搬到超类,去除了子类中的重复代码。
模板模式的优点:
- 封装不变部分,扩展可变部分。
- 提取公共代码,便于维护。
- 行为由父类控制,子类实现。
模板模式的缺点:
- 每一个不同的实现都需要一个子类来实现,导致类的个数增加,使得系统更加庞大。
模板模式的使用场景:
- 有多个子类共有的方法,且逻辑相同。
- 重要的、复杂的方法,可以考虑作为模板方法。
模板模式的实现举例如下:
/**测试类 */ public class Test{ public static void main(String[] args) { AbstractClass abstractClass; abstractClass = new ConcreteClassA(); abstractClass.TemplateMethod(); abstractClass = new ConcreteClassB(); abstractClass.TemplateMethod(); } } /**抽象模板类 */ abstract class AbstractClass { public abstract void PrimitiveOperation1(); public abstract void PrimitiveOperation2(); public void TemplateMethod() { PrimitiveOperation1(); PrimitiveOperation2(); } } /**具体模板类A */ class ConcreteClassA extends AbstractClass { @Override public void PrimitiveOperation1() { System.out.println("具体方法A方法1实现"); } @Override public void PrimitiveOperation2() { System.out.println("具体方法A方法2实现"); } } /**具体模板类B */ class ConcreteClassB extends AbstractClass { @Override public void PrimitiveOperation1() { System.out.println("具体方法B方法1实现"); } @Override public void PrimitiveOperation2() { System.out.println("具体方法B方法2实现"); } }
上面的例子中使用模板模式做的,父类中做了算法骨架,子类中具体实现算法中的不同部分。
(16)策略模式
策略模式,它的定义是对象具备某个行为,但是在不同的场景中,该行为有不同的实现算法。
策略模式的优点:
- 算法可以自由切换。
- 避免使用多重条件判断。
- 扩展性良好。
策略模式的缺点:
- 策略类会增多。
- 所有策略类都需要对外暴露。
策略模式的使用场景:
- 如果在一个系统里面有许多类,它们之间的区别仅在于它们的行为,那么使用策略模式可以动态地让一个对象在许多行为中选择一种行为。
- 一个系统需要动态地在几种算法中选择一种。
- 如果一个对象有很多的行为,如果不用恰当的模式,这些行为就只好使用多重的条件选择语句来实现。
策略模式的实现举例如下:
/**测试类 */ public class Test{ 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 OperationSubtract()); System.out.println("10 - 5 = " + context.executeStrategy(10, 5)); context = new Context(new OperationMultiply()); System.out.println("10 * 5 = " + context.executeStrategy(10, 5)); } } /**策略接口 */ interface Strategy { //算法方法 public int doOperation(int num1, int num2); } /**加法具体策略类 */ class OperationAdd implements Strategy{ @Override public int doOperation(int num1, int num2) { return num1 + num2; } } /**减法具体策略类 */ class OperationSubtract implements Strategy{ @Override public int doOperation(int num1, int num2) { return num1 - num2; } } /**乘法具体策略类 */ class OperationMultiply implements Strategy{ @Override public int doOperation(int num1, int num2) { return num1 * num2; } } /**Context上下文 */ class Context { private Strategy strategy; public Context(Strategy strategy){ this.strategy = strategy; } public int executeStrategy(int num1, int num2){ return strategy.doOperation(num1, num2); } }
运行结果
10 + 5 = 15
10 - 5 = 5
10 * 5 = 50
上面的例子中,我们可以看到,我们完全消除了对运算符号进行判断的哪些if...else
的冗余代码,取而代之的是客户端直接决定使用哪种算法,然后交由上下文获取结果。
(17)状态模式
状态模式,它的定义是当一个对象的内在状态改变时允许改变其行为,这个对象看起来像是改变了其类。
状态模式的优点:
- 封装了转换规则。
- 枚举可能的状态,在枚举状态之前需要确定状态种类。
- 将所有与某个状态有关的行为放到一个类中,并且可以方便地增加新的状态,只需要改变对象状态即可改变对象的行为。
- 允许状态转换逻辑与状态对象合成一体,而不是某一个巨大的条件语句块。
- 可以让多个环境对象共享一个状态对象,从而减少系统中对象的个数。
状态模式的缺点:
- 状态模式的使用必然会增加系统类和对象的个数。
- 状态模式的结构与实现都较为复杂,如果使用不当将导致程序结构和代码的混乱。
- 状态模式对"开闭原则"的支持并不太好,对于可以切换状态的状态模式,增加新的状态类需要修改那些负责状态转换的源代码,否则无法切换到新增状态,而且修改某个状态类的行为也需修改对应类的源代码。
状态模式的使用场景:
- 行为随状态改变而改变的场景。
- 条件、分支语句的代替者。
状态模式的实现举例如下:
/**测试类 */ public class Test{ public static void main(String[] args) { Context context = new Context(); StartState startState = new StartState(); startState.doAction(context); System.out.println(context.getState().toString()); StopState stopState = new StopState(); stopState.doAction(context); System.out.println(context.getState().toString()); } } /**状态接口 */ interface State { public void doAction(Context context); } /**开始状态实现类 */ class StartState implements State { public void doAction(Context context) { System.out.println("Player is in start state"); context.setState(this); } public String toString(){ return "Start State"; } } /**停止状态实现类 */ class StopState implements State { public void doAction(Context context) { System.out.println("Player is in stop state"); context.setState(this); } public String toString(){ return "Stop State"; } } /**状态类 */ class Context { private State state; public Context(){ state = null; } public void setState(State state){ this.state = state; } public State getState(){ return state; } }
运行结果
Player is in start state
Start State
Player is in stop state
Stop State
上面的例子中,我们可以看到具体状态由State接口的实现类实现,具体过渡由Context实现,State负责对所有状态的封装,也是依赖倒置原则的体现。
(18)观察者模式
观察者模式,它的定义在对象之间定义了一对多的依赖,这样一来,当一个对象改变状态,依赖它的对象会收到通知并自动更新。
观察者模式的优点:
- 观察者和被观察者是抽象耦合的。
- 建立一套触发机制。
观察者模式的缺点:
- 如果一个被观察者对象有很多的直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间。
- 如果在观察者和观察目标之间有循环依赖的话,观察目标会触发它们之间进行循环调用,可能导致系统崩溃。
- 观察者模式没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的,而仅仅只是知道观察目标发生了变化。
观察者模式的使用场景:
- 一个抽象模型有两个方面,其中一个方面依赖于另一个方面。将这些方面封装在独立的对象中使它们可以各自独立地改变和复用。
- 一个对象的改变将导致其他一个或多个对象也发生改变,而不知道具体有多少对象将发生改变,可以降低对象之间的耦合度。
- 一个对象必须通知其他对象,而并不知道这些对象是谁。
- 需要在系统中创建一个触发链,A对象的行为将影响B对象,B对象的行为将影响C对象……,可以使用观察者模式创建一种链式触发机制。
观察者模式的实现举例如下:
/**测试类 */ public class Test{ public static void main(String[] args) { //前台为通知者 Secretary secretary = new Secretary(); StockObserver observer = new StockObserver("adam", secretary); StockObserver observer2 = new StockObserver("tom", secretary); //前台通知 secretary.attach(observer); secretary.attach(observer2); //adam没被前台通知到,所以被老板抓了个现行 secretary.detach(observer); //老板回来了 secretary.setAction("小心!Boss回来了!"); //发通知 secretary.notifyObservers(); } } /**观察者抽象类 */ abstract class Observer { protected String name; protected Subject subject; public Observer(String name, Subject subject) { this.name = name; this.subject = subject; } public abstract void update(); } /**通知者接口 */ interface Subject { //增加 public void attach(Observer observer); //删除 public void detach(Observer observer); //通知 public void notifyObservers(); //状态 public void setAction(String action); public String getAction(); } /**具体通知者实现类 */ class Secretary implements Subject { //同事列表 private List<Observer> observers = new LinkedList<>(); private String action; //添加 @Override public void attach(Observer observer) { observers.add(observer); } //删除 @Override public void detach(Observer observer) { observers.remove(observer); } //通知 @Override public void notifyObservers() { for(Observer observer : observers) { observer.update(); } } //前台状态 @Override public String getAction() { return action; } @Override public void setAction(String action) { this.action = action; } } /**具体观察者实现类 */ class StockObserver extends Observer { public StockObserver(String name, Subject subject) { super(name, subject); } @Override public void update() { System.out.println(subject.getAction() + " " + name + "关闭股票行情,继续工作"); } }
运行结果
小心!Boss回来了!
tom关闭股票行情,继续工作
上面的例子中,我们可以看到,每当被观察者有所举动,且此举动是需要通知的,那么观察者就会收到相应消息,并作出相应的举动。
(19)备忘录模式
备忘录模式,它的定义是在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存着这个状态。这样,以后就可以将该对象恢复到原先保存的状态。
备忘录模式的优点:
- 给用户提供了一种可以恢复状态的机制,可以使用户能够比较方便地回到某个历史的状态。
- 实现了信息的封装,使得用户不需要关心状态的保存细节。
备忘录模式的缺点:
- 消耗资源。如果类的成员变量过多,势必会占用比较大的资源,而且每一次保存都会消耗一定的内存。
备忘录模式的使用场景:
- 需要保存/恢复数据的相关状态场景。
- 提供一个可回滚的操作。
备忘录模式的实现举例如下:
/**测试类 */ public class Test{ public static void main(String[] args) { Game game = new Game(); game.playGame(); try { //玩了一会 Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } //暂停游戏 game.exitGame(); GameManager.getGameManager().saveGameInfo(game.saveGameInfo()); //恢复游戏 game.resetGame(GameManager.getGameManager().getGameInfo()); } } /**备忘录类 */ class GameInfo { //开始的总时长 private int time; // 当前的等级 private int level; public GameInfo(int time, int level) { this.time = time; this.level = level; } public int getTime() { return time; } public void setTime(int time) { this.time = time; } public int getLevel() { return level; } public void setLevel(int level) { this.level = level; } } /**备忘录管理者类 */ class GameManager { private GameInfo mGameInfo; private static volatile GameManager instance; private GameManager() { } public static GameManager getGameManager() { if (instance == null) { synchronized (GameManager.class) { if (instance == null) { instance = new GameManager(); } } } return instance; } /**保存游戏信息 */ public void saveGameInfo(GameInfo gameInfo) { mGameInfo = gameInfo; } /**读取游戏信息 */ public GameInfo getGameInfo() { return mGameInfo; } } /**游戏类 */ class Game { //开始的总时长 private int time; // 当前的等级 private int level; // 是否退出 private boolean isExit = false; /** * 开始一把排位 */ public void playGame() { new Thread(new Runnable() { @Override public void run() { while (!isExit) { System.out.println("游戏开始了:" + time + "分钟,等级:" + level); time++; level++; try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } } }).start(); } /**来电话了,退出当前游戏 */ public void exitGame() { isExit = true; System.out.println("=====来电话了,退出当前游戏====="); System.out.println("游戏开始了:" + time + "分钟,等级:" + level); } /**保存获取当前游戏信息 */ public GameInfo saveGameInfo() { return new GameInfo(time, level); } /**重新加载游戏 */ public void resetGame(GameInfo gameInfo) { time = gameInfo.getTime(); level = gameInfo.getLevel(); System.out.println("=====恢复游戏====="); System.out.println("游戏开始了:" + time + "分钟,等级:" + level); } }
上面的例子中,我们可以看到,它提供了一种状态恢复的实现机制,使得用户可以方便地回到一个特定的历史步骤,当新的状态无效或者存在问题时,可以使用暂时存储起来的备忘录将状态复原。
(20)中介者模式
中介者模式,它的定义是用一个中介对象来封装一系列的对象交互,中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。
中介者模式的优点:
- 降低了类的复杂度,将一对多转化成了一对一。
- 各个类之间的解耦。
- 符合迪米特原则。
中介者模式的缺点:
- 中介者会庞大,变得复杂难以维护。
中介者模式的使用场景:
- 系统中对象之间存在比较复杂的引用关系,导致它们之间的依赖关系结构混乱而且难以复用该对象。
- 想通过一个中间类来封装多个类中的行为,而又不想生成太多的子类。
中介者模式的实现举例如下:
/**测试类 */ public class Test{ public static void main(String[] args) { User robert = new User("Robert"); User john = new User("John"); robert.sendMessage("Hi! John!"); john.sendMessage("Hello! Robert!"); } } /**用户实体类 */ class User { private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } public User(String name){ this.name = name; } public void sendMessage(String message){ ChatRoom.showMessage(this,message); } } /**中介类 */ class ChatRoom { public static void showMessage(User user, String message){ System.out.println(new Date().toString() + " [" + user.getName() +"] : " + message); } }
运行结果
Thu Jan 31 16:05:46 IST 2020 [Robert] : Hi! John!
Thu Jan 31 16:05:46 IST 2020 [John] : Hello! Robert!
上面的例子中,我们可以看到,中介者模式它会使代码更容易维护。它能够实现类之间的松散耦合。只有中介者这一个类知道所有的类,其他类只需要与中介者进行交互即可,当然更加集中的控制也会带来中枢的庞大,还是需要避免过度的集成。
(21)迭代器模式
迭代器模式,它的定义是提供一种方法顺序访问一个聚合对象中的各种元素,而又不暴露该对象的内部表示。
迭代器模式的优点:
- 它支持以不同的方式遍历一个聚合对象。
- 迭代器简化了聚合类。
- 在同一个聚合上可以有多个遍历。
- 在迭代器模式中,增加新的聚合类和迭代器类都很方便,无须修改原有代码。
迭代器模式的缺点:
- 由于迭代器模式将存储数据和遍历数据的职责分离,增加新的聚合类需要对应增加新的迭代器类,类的个数成对增加,这在一定程度上增加了系统的复杂性。
迭代器模式的使用场景:
- 访问一个聚合对象的内容而无须暴露它的内部表示。
- 需要为聚合对象提供多种遍历方式。
- 为遍历不同的聚合结构提供一个统一的接口。
迭代器模式的实现举例如下:
/**测试类 */ public class Test{ public static void main(String[] args) { NameRepository namesRepository = new NameRepository(); for(Iterator iter = namesRepository.getIterator(); iter.hasNext();){ String name = (String)iter.next(); System.out.println("Name : " + name); } } } /**迭代器接口 */ interface Iterator { public boolean hasNext();//是否存在下一个元素 public Object next();//下个元素 } /**返回迭代器接口 */ interface Container { public Iterator getIterator(); } /**迭代器实现类 */ class NameRepository implements Container { public String names[] = {"Robert" , "John" ,"Julie" , "Lora"}; @Override public Iterator getIterator() { return new NameIterator(); } private class NameIterator implements Iterator { int index; public boolean hasNext() { if(index < names.length){ return true; } return false; } public Object next() { if(this.hasNext()){ return names[index++]; } return null; } } }
运行结果
Name : Robert
Name : John
Name : Julie
Name : Lora
上面的例子中,我们可以看到,迭代器模式就是抽象一个迭代器类来分离了集合对象的遍历行为,这样既可以做到不暴露集合的内部结构,又可让外部代码透明地访问集合内部的数据。
(22)解释器模式
解释器模式,它的定义是给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子。
解释器模式的优点:
- 可扩展性比较好,灵活。
- 增加了新的解释表达式的方式。
- 易于实现简单文法。
解释器模式的缺点:
- 可利用场景比较少。
- 对于复杂的文法比较难维护。
- 解释器模式会引起类膨胀。
- 解释器模式采用递归调用方法。
解释器模式的使用场景:
- 可以将一个需要解释执行的语言中的句子表示为一个抽象语法树。
- 一些重复出现的问题可以用一种简单的语言来进行表达。
- 一个简单语法需要解释的场景。
解释器模式的实现举例如下:
/**测试类 */ public class Test{ public static void main(String[] args) { String expStr = "a + b - c"; System.out.println("输入表达式:"+expStr); HashMap<String, Integer> var = new HashMap<>(); var.put("a", 10); var.put("b", 5); var.put("c", 3); System.out.println("a的值:"+var.get("a")); System.out.println("b的值:"+var.get("b")); System.out.println("c的值:"+var.get("c")); Calculator calculator = new Calculator(expStr); System.out.println("运算结果:" + expStr + " = " + calculator.run(var)); } } /**表达式接口 */ interface Expression { //解析公式和数值,key是公式中的参数,value是具体的数值 public int interpreter(HashMap<String, Integer> var); } /**变量解析器 */ class VarExpression implements Expression { private String key; public VarExpression(String key) { this.key = key; } @Override public int interpreter(HashMap<String, Integer> var) { return var.get(this.key); } } /**运算符号解析器 */ class SymbolExpression implements Expression { protected Expression left; protected Expression right; public SymbolExpression(Expression left, Expression right) { this.left = left; this.right = right; } public int interpreter(HashMap<String, Integer> var) { return 0; } } /**加法解析器 */ class AddExpression extends SymbolExpression { public AddExpression(Expression left, Expression right) { super(left, right); } public int interpreter(HashMap<String, Integer> var) { return super.left.interpreter(var) + super.right.interpreter(var); } } /**减法解析器 */ class SubExpression extends SymbolExpression { public SubExpression(Expression left, Expression right) { super(left, right); } public int interpreter(HashMap<String, Integer> var) { return super.left.interpreter(var) - super.right.interpreter(var); } } /**解析器封装类 */ class Calculator { //定义表达式 private Expression expression; //构造函数传参,并解析 public Calculator(String expStr) { //安排运算先后顺序 Stack<Expression> stack = new Stack<>(); //表达式拆分为字符数组 char[] charArray = expStr.replace(" ", "").toCharArray(); Expression left = null; Expression right = null; for(int i=0; i<charArray.length; i++) { switch (charArray[i]) { case '+': //加法 left = stack.pop(); right = new VarExpression(String.valueOf(charArray[++i])); stack.push(new AddExpression(left, right)); break; case '-': //减法 left = stack.pop(); right = new VarExpression(String.valueOf(charArray[++i])); stack.push(new SubExpression(left, right)); break; default: //公式中的变量 stack.push(new VarExpression(String.valueOf(charArray[i]))); break; } } this.expression = stack.pop(); } //计算 public int run(HashMap<String, Integer> var) { return this.expression.interpreter(var); } }
运行结果
输入表达式:a + b - c
a的值:10
b的值:5
c的值:3
运算结果:a + b - c = 12
上面的例子中,我们可以看到,解释器模式的好处就在于,随时可以增加新的解释器而不会影响到现有的结构和类,便于扩展和维护。
(23)命令模式
命令模式,它的定义是将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化,对请求排队或记录请求日志。以及支持可撤销的操作。
命令模式的优点:
- 降低了系统耦合度。
- 新的命令可以很容易添加到系统中去。
命令模式的缺点:
- 使用命令模式可能会导致某些系统有过多的具体命令类。
命令模式的使用场景:
- 认为是命令的地方都可以使用命令模式,比如: 1、GUI 中每一个按钮都是一条命令。 2、模拟 CMD。
命令模式的实现举例如下:
/**测试类 */ public class Test{ public static void main(String[] args) { Light light = new Light();//灯泡 TV tv = new TV();//电视机 LoudspeakerBox loudspeakerBox = new LoudspeakerBox();//音响 Command lightOn = new LightOnCommand(light);//开启灯泡 Command lightOff = new LightOffCommand(light);//关闭灯泡 Command TVOn = new TVOnCommand(tv);//开启电视 Command TVOff = new TVOffCommand(tv);//关闭电视 Command LoudspeakerBoxOn = new LoudspeakerBoxOnCommand(loudspeakerBox);//开启音响 Command LoudspeakerBoxOff = new LoudspeakerBoxOffCommand(loudspeakerBox);//关闭音响 RemoteController remoteController = new RemoteController(3); remoteController.setCommand(0, lightOn, lightOff); remoteController.setCommand(1, TVOn, TVOff); remoteController.setCommand(2, LoudspeakerBoxOn, LoudspeakerBoxOff); remoteController.onButtonPressed(0); remoteController.offButtonPressed(0); remoteController.onButtonPressed(1); remoteController.offButtonPressed(1); remoteController.onButtonPressed(2); remoteController.offButtonPressed(2); } } /**命令接口 */ interface Command { void execute(); } /**灯泡实体类 */ class Light { public void on(){ System.out.println("打开电灯。。。"); } public void off(){ System.out.println("关闭电灯。。。"); } } /**电视实体类 */ class TV { public void on(){ System.out.println("打开电视。。。"); } public void off(){ System.out.println("关闭电视。。。"); } } /**音箱实体类 */ class LoudspeakerBox { public void on(){ System.out.println("打开音箱。。。"); } public void off(){ System.out.println("关闭音箱。。。"); } } /**灯泡开启命令实现类 */ class LightOnCommand implements Command{ Light light; public LightOnCommand(Light light){ this.light = light; } public void execute() { light.on(); } } /**灯泡关闭命令实现类 */ class LightOffCommand implements Command{ Light light; public LightOffCommand(Light light){ this.light = light; } public void execute() { light.off(); } } /**电视开启命令实现类 */ class TVOnCommand implements Command{ TV tv; public TVOnCommand(TV tv){ this.tv = tv; } public void execute() { tv.on(); } } /**电视关闭命令实现类 */ class TVOffCommand implements Command{ TV tv; public TVOffCommand(TV tv){ this.tv = tv; } public void execute() { tv.off(); } } /**音箱开启命令实现类 */ class LoudspeakerBoxOnCommand implements Command{ LoudspeakerBox loudspeakerBox; public LoudspeakerBoxOnCommand(LoudspeakerBox loudspeakerBox){ this.loudspeakerBox = loudspeakerBox; } public void execute() { loudspeakerBox.on(); } } /**音箱关闭命令实现类 */ class LoudspeakerBoxOffCommand implements Command{ LoudspeakerBox loudspeakerBox; public LoudspeakerBoxOffCommand(LoudspeakerBox loudspeakerBox){ this.loudspeakerBox = loudspeakerBox; } public void execute() { loudspeakerBox.off(); } } /**遥控器类 */ class RemoteController { Command[] onCommands; Command[] offCommands; public RemoteController(int commandSize) { this.onCommands = new Command[commandSize]; this.offCommands = new Command[commandSize]; } public void setCommand(int i, Command onCommand, Command offCommand) { onCommands[i] = onCommand; offCommands[i] = offCommand; } // 按下开按钮 public void onButtonPressed(int i) { onCommands[i].execute(); } // 按下关按钮 public void offButtonPressed(int i) { offCommands[i].execute(); } }
运行结果
打开电灯。。。
关闭电灯。。。
打开电视。。。
关闭电视。。。
打开音箱。。。
关闭音箱。。。
上面的例子中,我们可以看到,命令模式的简单应用,它允许我们将动作封装成命令对象,然后就可以随心所欲地存储、传递和调用它们了。通过命令对象实现调用者和执行者解耦,两者之间通过命令对象间接地进行沟通。
(24)责任链模式
责任链模式,它的定义是避免请求发送者与接收者耦合在一起,让多个对象都有可能接收请求,将这些对象连接成一条链,并且沿着这条链传递请求,直到有对象处理它为止。
责任链模式的优点:
- 降低耦合度。它将请求的发送者和接收者解耦。
- 简化了对象。使得对象不需要知道链的结构。
- 增强给对象指派职责的灵活性。通过改变链内的成员或者调动它们的次序,允许动态地新增或者删除责任。
- 增加新的请求处理类很方便。
责任链模式的缺点:
- 不能保证请求一定被接收。
- 系统性能将受到一定影响,而且在进行代码调试时不太方便,可能会造成循环调用。
- 可能不容易观察运行时的特征,有碍于除错。
责任链模式的使用场景:
- 有多个对象可以处理同一个请求,具体哪个对象处理该请求由运行时刻自动确定。
- 在不明确指定接收者的情况下,向多个对象中的一个提交一个请求。
- 可动态指定一组对象处理请求。
责任链模式的实现举例如下:
/**测试类 */ public class Test{ public static void main(String[] args) { ApprovalChainHandler h1 = new GroupLeaderChainHandler(); ApprovalChainHandler h2 = new ExecutiveChainHandler(); ApprovalChainHandler h3 = new CEOChainHandler(); h1.setNext(h2); h2.setNext(h3); String staff1 = "张三"; String staff2 = "李四"; String staff3 = "王五"; System.out.println(staff1+"请假结果:"+h1.handler(staff1)); System.out.println(staff2+"请假结果:"+h1.handler(staff2)); System.out.println(staff3+"请假结果:"+h1.handler(staff3)); } } /**请假审批任链抽象类 */ abstract class ApprovalChainHandler { private ApprovalChainHandler next; protected abstract boolean doHandler(String name); public boolean handler(String name) { boolean result = doHandler(name); if (isAccordWith(result) && next != null) { return next.handler(name); } else { return result; } } public void setNext(ApprovalChainHandler next){ this.next = next; } /**判断审批是否同意 */ protected boolean isAccordWith(boolean t) { return t; } } /**组长审批类 */ class GroupLeaderChainHandler extends ApprovalChainHandler { protected boolean doHandler(String name) { boolean result= new Random().nextBoolean(); System.out.println("组长审批["+ name +"]结果::"+result); return result; } } /**主管审批类 */ class ExecutiveChainHandler extends ApprovalChainHandler { protected boolean doHandler(String name) { boolean result= new Random().nextBoolean(); System.out.println("主管审批["+ name +"]结果: "+result); return result; } } /**老板审批类 */ class CEOChainHandler extends ApprovalChainHandler { protected boolean doHandler(String name) { boolean result= new Random().nextBoolean(); System.out.println("CEO审批["+ name +"]结果:: "+result); return result; } }
运行结果
组长审批[张三]结果::false
张三请假结果:false
组长审批[李四]结果::true
主管审批[李四]结果: false
李四请假结果:false
组长审批[王五]结果::true
主管审批[王五]结果: true
CEO审批[王五]结果:: false
王五请假结果:false
上面的例子中,我们可以看到,每一个职责都能被定义成一个处理对象, 使用责任链模式能够帮助我们在业务代码中更优雅地实现非互斥的多if判断,在需要扩展的时候也能更加轻松。