计模式的分类
总体来说设计模式分为三大类:
创建型模式,共五种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。
结构型模式,共七种:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。
行为型模式,共十一种:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。
一、创建型模式
1、单例模式
1.1、介绍
单例模式是指在内存中只会创建且仅创建一次对象的设计模式。在程序中多次使用同一个对象且作用相同时,为了防止频繁地创建对象使得内存飙升,单例模式可以让程序仅在内存中创建一个对象,让所有需要调用的地方都共享这一单例对象。
1.2、单例模式的类型
单例模式有两种类型:
懒汉式:在真正需要使用对象时才去创建该单例类对象
饿汉式:在类加载时已经创建好该单例对象,等待被程序使用
1.3、饿汉式创建单例对象
饿汉式在类加载时已经创建好该对象,在程序调用时直接返回该单例对象即可,即我们在编码时就已经指明了要马上创建这个对象,不需要等到被调用时再去创建。
public class Singleton{ private static final Singleton singleton = new Singleton(); private Singleton(){} public static Singleton getInstance() { return singleton; } }
1.4、懒汉式创建单例对象
我们再来回顾懒汉式的核心方法
public static Singleton getInstance() { if (singleton == null) { singleton = new Singleton(); } return singleton; }
这个方法其实是存在问题的,试想一下,如果两个线程同时判断singleton为空,那么它们都会去实例化一个Singleton对象,这就变成双例了。所以,我们要解决的是线程安全问题。
最容易想到的解决方法就是在方法上加锁,或者是对类对象加锁,程序就会变成下面这个样子
public static synchronized Singleton getInstance() { if (singleton == null) { singleton = new Singleton(); } return singleton; } // 或者 public static Singleton getInstance() { synchronized(Singleton.class) { if (singleton == null) { singleton = new Singleton(); } } return singleton; }
这样就规避了两个线程同时创建Singleton对象的风险,但是引来另外一个问题:每次去获取对象都需要先获取锁,并发性能非常地差,极端情况下,可能会出现卡顿现象。
接下来要做的就是优化性能,目标是:如果没有实例化对象则加锁创建,如果已经实例化了,则不需要加锁,直接获取实例。所以直接在方法上加锁的方式就被废掉了,因为这种方式无论如何都需要先获取锁
public static Singleton getInstance() { if (singleton == null) { // 线程A和线程B同时看到singleton = null,如果不为null,则直接返回singleton synchronized(Singleton.class) { // 线程A或线程B获得该锁进行初始化 if (singleton == null) { // 其中一个线程进入该分支,另外一个线程则不会进入该分支 singleton = new Singleton(); } } } return singleton; }
上面的代码已经完美地解决了并发安全+性能低效问题:
(1)第2行代码,如果singleton不为空,则直接返回对象,不需要获取锁;而如果多个线程发现singleton为空,则进入分支;
(2)第3行代码,多个线程尝试争抢同一个锁,只有一个线程争抢成功,第一个获取到锁的线程会再次判断singleton是否为空,因为singleton有可能已经被之前的线程实例化;
(3)其它之后获取到锁的线程在执行到第4行校验代码,发现singleton已经不为空了,则不会再new一个对象,直接返回对象即可;
(4)之后所有进入该方法的线程都不会去获取锁,在第一次判断singleton对象时已经不为空了;
因为需要两次判空,且对类对象加锁,该懒汉式写法也被称为:Double Check(双重校验) + Lock(加锁)
完整的代码如下所示:
public class Singleton { private static Singleton singleton; private Singleton(){} public static Singleton getInstance() { if (singleton == null) { // 线程A和线程B同时看到singleton = null,如果不为null,则直接返回singleton synchronized(Singleton.class) { // 线程A或线程B获得该锁进行初始化 if (singleton == null) { // 其中一个线程进入该分支,另外一个线程则不会进入该分支 singleton = new Singleton(); } } } return singleton; } }
上面这段代码已经近似完美了,但是还存在最后一个问题:指令重排
1.5、使用volatile防止指令重排
创建一个对象,在JVM中会经过三步:
(1)为singleton分配内存空间
(2)初始化singleton对象
(3)将singleton指向分配好的内存空间
指令重排序是指:JVM在保证最终结果正确的情况下,可以不按照程序编码的顺序执行语句,尽可能提高程序的性能
在这三步中,第2、3步有可能会发生指令重排现象,创建对象的顺序变为1-3-2,会导致多个线程获取对象时,有可能线程A创建对象的过程中,执行了1、3步骤,线程B判断singleton已经不为空,获取到未初始化的singleton对象,就会报NPE异常。文字较为晦涩,可以看流程图:
使用volatile关键字可以防止指令重排序,其原理较为复杂,这篇博客不打算展开,可以这样理解:使用volatile关键字修饰的变量,可以保证其指令执行的顺序与程序指明的顺序一致,不会发生顺序变换,这样在多线程环境下就不会发生NPE异常了。
volatile还有第二个作用:使用volatile关键字修饰的变量,可以保证其内存可见性,即每一时刻线程读取到该变量的值都是内存中最新的那个值,线程每次操作该变量都需要先读取该变量。
最终的代码如下所示:
public class Singleton { private static volatile Singleton singleton; private Singleton(){} public static Singleton getInstance() { if (singleton == null) { // 线程A和线程B同时看到singleton = null,如果不为null,则直接返回singleton synchronized(Singleton.class) { // 线程A或线程B获得该锁进行初始化 if (singleton == null) { // 其中一个线程进入该分支,另外一个线程则不会进入该分支 singleton = new Singleton(); } } } return singleton; } }
2、工厂模式
2.1、介绍
工厂模式是一种常用的类创建型设计模式,此模式的核心精神是封装类中变化的部分,提取其中个性化善变的部分为独立类,通过依赖注入以达到解耦、复用和方便后期维护拓展的目的。它的核心结构有四个角色,分别是抽象工厂、具体工厂、抽象产品、具体产品。
四个角色:
(1)抽象工厂(Abstract Factory):提供了创建产品的接口,调用者通过它访问具体工厂的工厂方法来创建产品。
(2)具体工厂(ConcreteFactory):主要是实现抽象工厂中的抽象方法,完成具体产品的创建。
(3)抽象产品(Product):定义了产品的规范,描述了产品的主要特性和功能。
(4)具体产品(ConcreteProduct):实现了抽象产品角色所定义的接口,由具体工厂来创建,它同具体工厂之间一一对应。
2.2、关系依赖图
2.3、代码实现
产品接口类
Shape
public interface Shape { void draw(); }
产品实现类
Rectangle
public class Rectangle implements Shape { @Override public void draw() { System.out.println("Rectangle:绘制长方形"); } }
Square
public class Square implements Shape { @Override public void draw() { System.out.println("Square:绘制正方形"); } }
Circle
public class Circle implements Shape { @Override public void draw() { System.out.println("Circle:绘制圆形"); } }
工厂类
ShapeFactory
public class ShapeFactory { public static Shape createShape(String shapeType) { if ("Rectangle".equalsIgnoreCase(shapeType)) { return new Rectangle(); } if ("Square".equalsIgnoreCase(shapeType)) { return new Square(); } if ("Circle".equalsIgnoreCase(shapeType)) { return new Circle(); } return null; } }
工厂类测试
ShapeFactoryTest
public class ShapeFactoryTest { public static void main(String[] args) { Shape rectangle = ShapeFactory.createShape("Rectangle"); rectangle.draw(); Shape square = ShapeFactory.createShape("Square"); square.draw(); Shape crcle = ShapeFactory.createShape("Circle"); crcle.draw(); } }
结果:
Rectangle:绘制长方形
Square:绘制正方形
Circle:绘制圆形
2.4、应用
最佳实践:java.util.Calendar
Calendar 类的相关代码如下所示,大部分代码都已经省略,我只给出了 getInstance() 工厂方法的代码实现。从代码中,我们可以看出,getInstance() 方法可以根据不同 TimeZone 和 Locale,创建不同的 Calendar 子类对象,比如 BuddhistCalendar、JapaneseImperialCalendar、GregorianCalendar,这些细节完全封装在工厂方法中,使用者只需要传递当前的时区和地址,就能够获得一个 Calendar 类对象来使用,而获得的对象具体是哪个 Calendar 子类的对象,使用者在使用的时候并不关心。
public abstract class Calendar implements Serializable, Cloneable, Comparable<Calendar> { //... public static Calendar getInstance(TimeZone zone, Locale aLocale){ return createCalendar(zone, aLocale); } private static Calendar createCalendar(TimeZone zone,Locale aLocale) { CalendarProvider provider = LocaleProviderAdapter.getAdapter( CalendarProvider.class, aLocale).getCalendarProvider(); if (provider != null) { try { return provider.getInstance(zone, aLocale); } catch (IllegalArgumentException iae) { // fall back to the default instantiation } } Calendar cal = null; if (aLocale.hasExtensions()) { String caltype = aLocale.getUnicodeLocaleType("ca"); if (caltype != null) { switch (caltype) { case "buddhist": cal = new BuddhistCalendar(zone, aLocale); break; case "japanese": cal = new JapaneseImperialCalendar(zone, aLocale); break; case "gregory": cal = new GregorianCalendar(zone, aLocale); break; } } } if (cal == null) { if (aLocale.getLanguage() == "th" && aLocale.getCountry() == "TH") { cal = new BuddhistCalendar(zone, aLocale); } else if (aLocale.getVariant() == "JP" && aLocale.getLanguage() == "ja" && aLocale.getCountry() == "JP") { cal = new JapaneseImperialCalendar(zone, aLocale); } else { cal = new GregorianCalendar(zone, aLocale); } } return cal; } }
3、抽象工厂模式
3.1、介绍
抽象工厂模式(Abstract Factory)是围绕一个超级工厂创建其他工厂。该超级工厂又称为其他工厂的工厂。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。在抽象工厂模式中,接口是负责创建一个相关对象的工厂,不需要显式指定它们的类,每个生成的工厂都能按照工厂模式提供对象。
3.2、关系依赖图
3.3、代码实现
图形产品接口类
Shape
public interface Shape { void draw(); }
图形产品实现类
Rectangle
public class Rectangle implements Shape { @Override public void draw() { System.out.println("Rectangle:绘制长方形"); } }
Square
public class Square implements Shape { @Override public void draw() { System.out.println("Square:绘制正方形"); } }
Circle
public class Circle implements Shape { @Override public void draw() { System.out.println("Circle:绘制圆形"); } }
图形工厂类编写
ShapeFactory
public class ShapeFactory extends AbstractFactory { @Override public Shape createShape(String shapeType) { if ("Rectangle".equalsIgnoreCase(shapeType)) { return new Rectangle(); } if ("Square".equalsIgnoreCase(shapeType)) { return new Square(); } if ("Circle".equalsIgnoreCase(shapeType)) { return new Circle(); } return null; } @Override public Color createColor(String color) { return null; } }
颜色产品接口类
Color
public interface Color { void fill(); }
颜色产品实现类
Red
public class Red implements Color { @Override public void fill() { System.out.println("Red:填充红色"); } }
Green
public class Green implements Color { @Override public void fill() { System.out.println("Green:填充绿色"); } }
Blue
public class Blue implements Color { @Override public void fill() { System.out.println("Blue:填充蓝色"); } }
颜色工厂类编写
ColorFactory
public class ColorFactory extends AbstractFactory { @Override public Shape createShape(String shapeType) { return null; } @Override public Color createColor(String colorType) { if ("Red".equalsIgnoreCase(colorType)) { return new Red(); } if ("Green".equalsIgnoreCase(colorType)) { return new Green(); } if ("Blue".equalsIgnoreCase(colorType)) { return new Blue(); } return null; } }
产品家族抽象类
AbstractFactory
public abstract class AbstractFactory { public abstract Shape createShape(String shapeType); public abstract Color createColor(String colorType); }
抽象类的工厂类
AbstractFactoryProducer
public class AbstractFactoryProducer { public static AbstractFactory createFactory(String choice) { if ("Shape".equalsIgnoreCase(choice)) { return new ShapeFactory(); } if ("Color".equalsIgnoreCase(choice)) { return new ColorFactory(); } return null; } }
抽象工厂类测试
AbstractFactoryProducerTest
public class AbstractFactoryProducerTest { public static void main(String[] args) { AbstractFactory shapeFactory = AbstractFactoryProducer.createFactory("Shape"); Shape rectangle = shapeFactory.createShape("Rectangle"); rectangle.draw(); Shape square = shapeFactory.createShape("Square"); square.draw(); Shape circle = shapeFactory.createShape("Circle"); circle.draw(); AbstractFactory colorFactory = AbstractFactoryProducer.createFactory("Color"); Color red = colorFactory.createColor("Red"); red.fill(); Color green = colorFactory.createColor("Green"); green.fill(); Color blue = colorFactory.createColor("Blue"); blue.fill(); } }
结果:
Rectangle:绘制长方形
Square:绘制正方形
Circle:绘制圆形
Red:填充红色
Green:填充绿色
Blue:填充蓝色
3.4、应用
最佳实践:java.sql.Connection
Connection接口源码如下所示,其定义了Statement、PreparedStatement、CallableStatement三个产品等级结构。
public interface Connection extends Wrapper, AutoCloseable { Statement createStatement() throws SQLException; PreparedStatement prepareStatement(String sql) throws SQLException; CallableStatement prepareCall(String sql) throws SQLException; }
4、原型模式
4.1、介绍
原型模式属于创建型设计模式。通过复制现有的实例来创建新的实例,无需知道相应类的信息。简单的讲就是当我需要创建一个指定的对象时,刚好现在就有这个对象,但又不能直接使用,所以简单的方式就是克隆一个一摸一样的对象来使用。
4.2、代码实现
(1)原型模式实现(浅克隆)
IdCard
//身份证对象 class IdCard { private String id; public IdCard(String id) { this.id = id; } @Override public String toString() { return id; } }
Person
//代表人对象 public class Person implements Cloneable { private String name; private int age; private IdCard idCard; public Person(String name, int age, IdCard idCard) { this.name = name; this.age = age; this.idCard = idCard; } public IdCard getIdCard() { return idCard; } public void setIdCard(IdCard idCard) { this.idCard = idCard; } @Override public String toString() { return "Person{" + "personHashCode=" + this.hashCode() + ", name='" + name + '\'' + ", age=" + age + ", idCard=" + idCard + ", idCardHashCode=" + idCard.hashCode() + '}'; } //浅克隆已经帮我们实现了 @Override protected Object clone() throws CloneNotSupportedException { return super.clone(); } }
PersonTest
public class PersonTest { public static void main(String[] args) throws CloneNotSupportedException { //创建一个对象 Person person = new Person("张三", 20, new IdCard("10086")); //克隆两个对象 Person person1 = (Person) person.clone(); Person person2 = (Person) person.clone(); //打印三人信息 System.out.println(person); System.out.println(person1); System.out.println(person2); } }
运行效果:
Person{personHashCode=460141958, name='张三', age=20, idCard=10086, idCardHashCode=1163157884}
Person{personHashCode=1956725890, name='张三', age=20, idCard=10086, idCardHashCode=1163157884}
Person{personHashCode=356573597, name='张三', age=20, idCard=10086, idCardHashCode=1163157884}
解释说明:
我们发现可以通过实现implements Cloneable来完成浅拷贝,基本变量是值传递克隆,而引用对象IdCard则是引用传递,这不符合我们面向对象思想,每一个Person应该都有一个独立的IdCard,而不是共用一个,而要解决这种问题,我们需要使用深克隆。
(2)原型模式实现(深克隆:第一种)
IdCard
//身份证对象 class IdCard implements Cloneable { private String id; public IdCard(String id) { this.id = id; } @Override public String toString() { return id; } @Override protected Object clone() throws CloneNotSupportedException { return super.clone(); } }
Person
//代表人对象 public class Person implements Cloneable { private String name; private int age; private IdCard idCard; public Person(String name, int age, IdCard idCard) { this.name = name; this.age = age; this.idCard = idCard; } public IdCard getIdCard() { return idCard; } public void setIdCard(IdCard idCard) { this.idCard = idCard; } @Override public String toString() { return "Person{" + "personHashCode=" + this.hashCode() + ", name='" + name + '\'' + ", age=" + age + ", idCard=" + idCard + ", idCardHashCode=" + idCard.hashCode() + '}'; } //深克隆我们需要自己手动实现,在引用对象中也需要实现clone方法 @Override protected Object clone() throws CloneNotSupportedException { //完成对当前基本数据类型 //注:String类型通过常量赋值时相当于基本数据类型,通过new关键字创建对象时便是引用数据类型 Object person = super.clone(); //对引用类型进行单独处理 Person p = (Person) person; IdCard idCard = (IdCard) p.getIdCard().clone();//实现自己的克隆 p.setIdCard(idCard); return p; } }
PersonTest
public class PersonTest { public static void main(String[] args) throws CloneNotSupportedException { //创建一个对象 Person person = new Person("张三", 20, new IdCard("10086")); //克隆两个对象 Person person1 = (Person) person.clone(); Person person2 = (Person) person.clone(); //打印三人信息 System.out.println(person); System.out.println(person1); System.out.println(person2); } }
运行效果:
Person{personHashCode=460141958, name='张三', age=20, idCard=10086, idCardHashCode=1163157884}
Person{personHashCode=1956725890, name='张三', age=20, idCard=10086, idCardHashCode=356573597}
Person{personHashCode=1735600054, name='张三', age=20, idCard=10086, idCardHashCode=21685669}
解释说明:
使用这种深克隆的方式,完美的解决了当数据类型为引用类型时,只是拷贝原引用对象地址而不是一个全新的引用对象的引用,但是这种实现有一个很大的弊端,需要在每一个对象中都实现clone方法,如果类全是你自己写的,那自然没问题,实现一下就行了,不过有点麻烦。但是,如果你引用的是第三方的一个类,无法修改源代码,这种方式,显然就无法实现深克隆了。
(3)原型模式实现(深克隆:第二种)
IdCard
//身份证对象 class IdCard implements Serializable { private String id; public IdCard(String id) { this.id = id; } @Override public String toString() { return id; } }
Person
//代表人对象 public class Person implements Serializable { private String name; private int age; private IdCard idCard; public Person(String name, int age, IdCard idCard) { this.name = name; this.age = age; this.idCard = idCard; } public IdCard getIdCard() { return idCard; } public void setIdCard(IdCard idCard) { this.idCard = idCard; } @Override public String toString() { return "Person{" + "personHashCode=" + this.hashCode() + ", name='" + name + '\'' + ", age=" + age + ", idCard=" + idCard + ", idCardHashCode=" + idCard.hashCode() + '}'; } //手动实现深克隆方法 public Person deepClone() { //创建流对象 ByteArrayOutputStream bos = null; ObjectOutputStream oos = null; ByteArrayInputStream bis = null; ObjectInputStream ois = null; try { //序列化 bos = new ByteArrayOutputStream(); oos = new ObjectOutputStream(bos); oos.writeObject(this); //反序列化 bis = new ByteArrayInputStream(bos.toByteArray()); ois = new ObjectInputStream(bis); return (Person) ois.readObject(); } catch (Exception e) { e.printStackTrace(); return null; } finally { //关闭流 try { ois.close(); bis.close(); oos.close(); bos.close(); } catch (Exception e) { e.printStackTrace(); } } } }
PersonTest
public class PersonTest { public static void main(String[] args) { //创建一个对象 Person person = new Person("张三", 20, new IdCard("10086")); //克隆两个对象 Person person1 = person.deepClone(); Person person2 = person.deepClone(); //打印三人信息 System.out.println(person); System.out.println(person1); System.out.println(person2); } }
运行效果:
Person{personHashCode=1300109446, name='张三', age=20, idCard=10086, idCardHashCode=1020371697}
Person{personHashCode=20132171, name='张三', age=20, idCard=10086, idCardHashCode=186370029}
Person{personHashCode=2094548358, name='张三', age=20, idCard=10086, idCardHashCode=51228289}
解释说明:
这种方式我们需要手动编写deepClone方法,使用Java流中的序列化与反序列化来实现深克隆,但是这种实现,需要在每一个类中都继承序列化Serializable接口,这种方式,如果你调用的是第三方类,也有可能第三方类上没有实现Serializable序列化接口,但是一般来说,大多都会实现,总的来说,这种比较推荐使用,而且效率也高,要是真的第三方类没有实现Serializable序列化接口,这里可以再提供一种思路,但是不具体实现了,可以借助第三方工具包fastjson先将对象转换为json字符串,然后再把json字符串转为对象,这样也是可以的。
4.3、应用
最佳实践:java.util.ArrayList
5、建造者模式
5.1、介绍
建造者模式(Builder Pattern)使用多个简单的对象一步一步构建成一个复杂的对象,这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
它是将一个复杂的对象分解为多个简单的对象,然后一步一步构建而成。它将变与不变相分离,即产品的组成部分是不变的,但每一部分是可以灵活选择的。
5.2、关系依赖图
我们都知道盖房子是一个模式化的过程:打地基 -》砌墙面 -》盖楼顶,只不过不同类型的房子,具体的要求也会不尽相同,比如地基大小、楼的高度等等,但是大致的过程都是相似的,HouseDirector是整个工程的指挥者,由他指挥VillaHouse(别墅建造者)、HighHouse(高楼建造者)来建筑不同类型的House产品。而使用端只需要和指挥者打交道就可以了,屏蔽了产品实现的细节,构建和表示分离,有利于系统的拓展和解耦。
5.3、代码实现
产品实现类
House
public class House { private String ground; private String wall; private String roofed; public String getGround() { return ground; } public void setGround(String ground) { this.ground = ground; } public String getWall() { return wall; } public void setWall(String wall) { this.wall = wall; } public String getRoofed() { return roofed; } public void setRoofed(String roofed) { this.roofed = roofed; } @Override public String toString() { return "House{" + "ground='" + ground + '\'' + ", wall='" + wall + '\'' + ", roofed='" + roofed + '\'' + '}'; } }
抽象建造者
HouseBuilder
public abstract class HouseBuilder { //创建产品对象 protected House house = new House(); //生产产品流程 public abstract void buildGround(); //1.打地基 public abstract void buildWall(); //2.砌墙面 public abstract void buildRoofed(); //3.盖楼顶 //返回产品对象 public House getResult() { return house; } }
工程指挥者
HouseDirector
public class HouseDirector { private HouseBuilder houseBuilder; public HouseDirector(HouseBuilder houseBuilder) { this.houseBuilder = houseBuilder; } public House build(){ houseBuilder.buildGround(); houseBuilder.buildWall(); houseBuilder.buildRoofed(); return houseBuilder.getResult(); } }
具体建造者:别墅
VillaHouse
public class VillaHouse extends HouseBuilder { @Override public void buildGround() { house.setGround("100平"); System.out.println("别墅:打地基"); } @Override public void buildWall() { house.setWall("每面墙高三米"); System.out.println("别墅:砌墙面"); } @Override public void buildRoofed() { house.setRoofed("欧式风格屋顶"); System.out.println("别墅:盖楼顶"); } }
具体建造者:高楼
HighHouse
public class HighHouse extends HouseBuilder{ @Override public void buildGround() { house.setGround("200平"); System.out.println("高楼:打地基"); } @Override public void buildWall() { house.setWall("每面墙高四米"); System.out.println("高楼:砌墙面"); } @Override public void buildRoofed() { house.setRoofed("中国复古屋顶"); System.out.println("高楼:盖楼顶"); } }
产品使用端
Client
public class Client { public static void main(String[] args) { House house1 = new HouseDirector(new VillaHouse()).build(); System.out.println(house1); System.out.println("===================="); House house2 = new HouseDirector(new HighHouse()).build(); System.out.println(house2); } }
结果:
别墅:打地基
别墅:砌墙面
别墅:盖楼顶
House{ground='100平', wall='每面墙高三米', roofed='欧式风格屋顶'}
====================
高楼:打地基
高楼:砌墙面
高楼:盖楼顶
House{ground='200平', wall='每面墙高四米', roofed='中国复古屋顶'}
5.4、应用
最佳实践:org.apache.ibatis.builder.xml.XMLConfigBuilder(mybatis 3.5.6框架源码)
产品实现类
org.apache.ibatis.session.Configuration
public class Configuration { protected Environment environment; protected boolean safeRowBoundsEnabled; protected boolean safeResultHandlerEnabled; protected boolean mapUnderscoreToCamelCase; protected boolean aggressiveLazyLoading; protected boolean multipleResultSetsEnabled; protected boolean useGeneratedKeys; protected boolean useColumnLabel; protected boolean cacheEnabled; protected boolean callSettersOnNulls; protected boolean useActualParamName; protected boolean returnInstanceForEmptyRow; protected boolean shrinkWhitespacesInSql; protected String logPrefix; protected Class<? extends Log> logImpl; protected Class<? extends VFS> vfsImpl; protected Class<?> defaultSqlProviderType; protected LocalCacheScope localCacheScope; protected JdbcType jdbcTypeForNull; protected Set<String> lazyLoadTriggerMethods; protected Integer defaultStatementTimeout; protected Integer defaultFetchSize; protected ResultSetType defaultResultSetType; protected ExecutorType defaultExecutorType; protected AutoMappingBehavior autoMappingBehavior; protected AutoMappingUnknownColumnBehavior autoMappingUnknownColumnBehavior; protected Properties variables; protected ReflectorFactory reflectorFactory; protected ObjectFactory objectFactory; protected ObjectWrapperFactory objectWrapperFactory; protected boolean lazyLoadingEnabled; protected ProxyFactory proxyFactory; protected String databaseId; protected Class<?> configurationFactory; protected final MapperRegistry mapperRegistry; protected final InterceptorChain interceptorChain; protected final TypeHandlerRegistry typeHandlerRegistry; protected final TypeAliasRegistry typeAliasRegistry; protected final LanguageDriverRegistry languageRegistry; protected final Map<String, MappedStatement> mappedStatements; protected final Map<String, Cache> caches; protected final Map<String, ResultMap> resultMaps; protected final Map<String, ParameterMap> parameterMaps; protected final Map<String, KeyGenerator> keyGenerators; protected final Set<String> loadedResources; protected final Map<String, XNode> sqlFragments; protected final Collection<XMLStatementBuilder> incompleteStatements; protected final Collection<CacheRefResolver> incompleteCacheRefs; protected final Collection<ResultMapResolver> incompleteResultMaps; protected final Collection<MethodResolver> incompleteMethods; protected final Map<String, String> cacheRefMap; //...此处省略很多代码 }
抽象建造者
org.apache.ibatis.builder.BaseBuilder
public abstract class BaseBuilder { protected final Configuration configuration; protected final TypeAliasRegistry typeAliasRegistry; protected final TypeHandlerRegistry typeHandlerRegistry; public BaseBuilder(Configuration configuration) { this.configuration = configuration; this.typeAliasRegistry = this.configuration.getTypeAliasRegistry(); this.typeHandlerRegistry = this.configuration.getTypeHandlerRegistry(); } public Configuration getConfiguration() { return this.configuration; } //...此处省略很多代码 }
具体建造者
org.apache.ibatis.builder.xml.XMLConfigBuilder
public class XMLConfigBuilder extends BaseBuilder { private boolean parsed; private final XPathParser parser; private String environment; private final ReflectorFactory localReflectorFactory; //...此处省略很多代码 public Configuration parse() { if (this.parsed) { throw new BuilderException("Each XMLConfigBuilder can only be used once."); } else { this.parsed = true; this.parseConfiguration(this.parser.evalNode("/configuration")); return this.configuration; } } private void parseConfiguration(XNode root) { try { this.propertiesElement(root.evalNode("properties")); Properties settings = this.settingsAsProperties(root.evalNode("settings")); this.loadCustomVfs(settings); this.loadCustomLogImpl(settings); this.typeAliasesElement(root.evalNode("typeAliases")); this.pluginElement(root.evalNode("plugins")); this.objectFactoryElement(root.evalNode("objectFactory")); this.objectWrapperFactoryElement(root.evalNode("objectWrapperFactory")); this.reflectorFactoryElement(root.evalNode("reflectorFactory")); this.settingsElement(settings); this.environmentsElement(root.evalNode("environments")); this.databaseIdProviderElement(root.evalNode("databaseIdProvider")); this.typeHandlerElement(root.evalNode("typeHandlers")); this.mapperElement(root.evalNode("mappers")); } catch (Exception var3) { throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + var3, var3); } } //...此处省略很多代码 }
parse方法最终要返回一个Configuration对象,构建Configuration对象的建造过程都在parseConfiguration方法中,这也就是mybatis解析XML配置文件 来构建Configuration对象的主要过程,所以XMLConfigBuilder是具体建造者,复杂产品对象是Configuration,至于指挥者这里好像并没有用到。
二、结构型模式
1、适配器模式
1.1、介绍
适配器模式(Adapter Pattern)是作为两个不兼容接口之间的桥梁,这种类型的设计模式属于结构型模式。一些书籍也称适配器模式为缺省适配器模式(Default Adapter Pattern)。适配器模式主要分为三类:类适配器模式、对象适配器模式、接口适配器模式。以生活中手机充电为例来讲解适配器模式,手机本身并不能直接用220V交流电,需要将220V的交流电转换为5V的直流电,在这个过程中,充电器本身相当于Adapter(适配器),220V交流电相当于Adaptee (适配者),5V直流电则是我们的Target(目标)
1.2、关系依赖图
适配器模式实现(类适配器)
适配器模式实现(对象适配器)
1.3、代码实现
(1)适配器模式实现(类适配器)
创建交流电
Voltage220V
public class Voltage220V { public int output220V() { System.out.println("Voltage220V output220V ..."); return 220; } }
定义直流电
Voltage5V
public interface Voltage5V { //定义一个标准,充电器来实现 public int output5V(); }
创建充电器
VoltageAdapter
public class VoltageAdapter extends Voltage220V implements Voltage5V { @Override public int output5V() { //获取220V交流电 int output220V = output220V(); //转换为5V直流电 int output5V = output220V / 44; System.out.println("VoltageAdapter output5V ..."); return output5V; } }
创建手机类
Phone
public class Phone { //充电 public void charging(Voltage5V voltage5V) { if (voltage5V.output5V() == 5) { System.out.println("电压刚好5V, 可以充电"); } else if (voltage5V.output5V() > 5) { System.out.println("电压大于5V, 不能充电"); } } }
测试充电器
PhoneTest
public class PhoneTest { public static void main(String[] args) { //创建一部手机 Phone phone = new Phone(); //用充电器充电 phone.charging(new VoltageAdapter()); } }
结果:
Voltage220V output220V ...
VoltageAdapter output5V ...
电压刚好5V, 可以充电
点评:
Java是单继承机制,所以类适配器需要继承适配者(Adaptee,指Voltage220V)类,这点算是一个缺点,除此之外还必须要求目标(Target,指Voltage5V)必须是接口,有一定局限性;
适配者Voltage220V类的方法在适配器VoltageAdapter类中都会暴露出来,也增加了使用的成本。但是由于其继承了适配者Voltage220V类,所以它可以根据需求重写该类的方法,使得适配器VoltageAdapter类的灵活性增强了。
(2)适配器模式实现(对象适配器)
创建交流电
Voltage220V
public class Voltage220V { public int output220V() { System.out.println("Voltage220V output220V ..."); return 220; } }
定义直流电
Voltage5V
public interface Voltage5V { //定义一个标准,充电器来实现 public int output5V(); }
创建充电器
VoltageAdapter
public class VoltageAdapter implements Voltage5V { private Voltage220V voltage220V; public VoltageAdapter(Voltage220V voltage220V) { this.voltage220V = voltage220V; } @Override public int output5V() { //获取220V交流电 int output220V = voltage220V.output220V(); //转换为5V直流电 int output5V = output220V / 44; System.out.println("VoltageAdapter output5V ..."); return output5V; } }
创建手机类
Phone
public class Phone { //充电 public void charging(Voltage5V voltage5V) { if (voltage5V.output5V() == 5) { System.out.println("电压刚好5V, 可以充电"); } else if (voltage5V.output5V() > 5) { System.out.println("电压大于5V, 不能充电"); } } }
测试充电器
PhoneTest
public class PhoneTest { public static void main(String[] args) { //创建一部手机 Phone phone = new Phone(); //用充电器充电(传入220V交流电,传出5V直流电) phone.charging(new VoltageAdapter(new Voltage220V())); } }
结果:
Voltage220V output220V ...
VoltageAdapter output5V ...
电压刚好5V, 可以充电
点评:
对象适配器和类适配器其实算是同一种思想,只不过实现方式不同。
根据合成复用原则,使用组合替代继承, 所以它解决了类适配器中VoltageAdapter必须继承Voltage220V的局限性问题,也不再强制要求Voltage5V必须是接口。使用成本更低,更灵活。因此,对象适配器模式是适配器模式常用的一种。
1.4、应用
最佳实践:适配器模式在 SpringMVC 框架应用
由于Controller的类型不同,有多重实现方式,那么调用方式就不是确定的,如果需要直接调用Controller方法,需要调用的时候就得不断是使用if else来进行判断是哪一种子类然后执行。那么如果后面要扩展Controller,就得修改原来的代码,这样违背了 OCP 原则。
因此SpringMVC定义了一个适配接口HandlerAdapter,使得每一种Controller有一种对应的适配器实现类, 让适配器代替Controller执行相应的方法。这样在扩展Controller时,只需要增加一个适配器类就完成了SpringMVC的扩展了。
org.springframework.web.servlet.HandlerAdapter,相当于适配器模式中的目标(Target)
public interface HandlerAdapter { boolean supports(Object handler); @Nullable ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception; long getLastModified(HttpServletRequest request, Object handler); }
org.springframework.web.servlet.DispatcherServlet
public class DispatcherServlet extends FrameworkServlet { protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception { //通过request可以得到请求对应的控制器handler mappedHandler = getHandler(processedRequest); //根据handler得到对应的适配器 HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler()); //通过适配器去调用对应controller的方法,并返回ModelAndView mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); } }
getHandlerAdapter:
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException { if (this.handlerAdapters != null) { for (HandlerAdapter adapter : this.handlerAdapters) { if (adapter.supports(handler)) { return adapter; } } } throw new ServletException("No adapter for handler [" + handler + "]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler"); }
我们随便找一个HandlerAdapter的实现类,相当于适配器模式中的适配器(Adapter),其中Object handler为适配者(Adaptee)
public class HttpRequestHandlerAdapter implements HandlerAdapter { @Override public boolean supports(Object handler) { return (handler instanceof HttpRequestHandler); } @Override @Nullable public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { ((HttpRequestHandler) handler).handleRequest(request, response); return null; } @Override public long getLastModified(HttpServletRequest request, Object handler) { if (handler instanceof LastModified) { return ((LastModified) handler).getLastModified(request); } return -1L; } }
2、桥接模式
2.1、介绍
在现实生活中,某些类具有两个或多个维度的变化,如图形既可按形状分,又可按颜色分。如何设计类似于 Photoshop 这样的软件,能画不同形状和不同颜色的图形呢?如果用继承方式,m 种形状和 n 种颜色的图形就有 m×n 种,不但对应的子类很多,而且扩展困难。
当然,这样的例子还有很多,如不同颜色和字体的文字、不同品牌和功率的汽车,如果用桥接模式就能很好地解决这些问题,那么桥接模式到底是什么呢?
桥接模式(Bridge Pattern)的定义如下:将抽象与实现分离,使它们可以独立变化。它是用组合关系代替继承关系来实现,从而降低了抽象和实现这两个可变维度的耦合度。桥接模式遵循了里氏替换原则和依赖倒置原则,最终实现了开闭原则,对修改关闭,对扩展开放。
2.2、关系依赖图
2.3、代码实现
业务逻辑场景 : 开发跨平台的视频播放器 , 平台有 Android , iOS , Windows , Linux , Mac , 播放器支持的格式有 MP4 , AVI , RMVB , FLV 格式 ; 这种情况下 , 适合使用桥接模式 ;
平台 : 播放器支持 Linux , Android 平台 ;
视频格式 : 播放器支持 MP4 , FLV 视频格式 ;
这里分为两块 , 一块是平台 , 一块是视频格式 ;
平台可以不断的扩展 , 如 : Windows , iOS , MAC , 嵌入式平台 ;
视频类型也可以不断的扩展 , 如 : H264 , H265 , MPEG 等 ;
二者可以随着自己的层级进行扩展 ;
桥接模式 最重要的是 将 抽象 与 实现 解耦 , 通过组合 在 抽象 与 实现 之间搭建桥梁 ;
视频格式抽象
package bridge; public interface Vedio { /** * 打开视频 * 可以打开 MP4 / FLV 格式视频 * 视频 ( 格式 ) 是个 抽象 * @return */ Vedio openVedio(); /** * 查看视频信息 */ void showVedio(); }
FLV 视频格式实现
package bridge; /** * FLV 视频格式 */ public class FlvVedio implements Vedio { @Override public Vedio openVedio() { System.out.println("打开 FLV 格式视频"); return new FlvVedio(); } @Override public void showVedio() { System.out.println("当前视频格式是 FLV"); } }
MP4 视频格式实现
package bridge; /** * MP4 格式 */ public class MP4Vedio implements Vedio { @Override public Vedio openVedio() { System.out.println("打开 MP4 格式视频"); return new MP4Vedio(); } @Override public void showVedio() { System.out.println("当前视频格式是 MP4"); } }
系统平台抽象
package bridge; /** * 平台操作系统 抽象类 */ public abstract class Platform { /** * 这是桥接模式最核心的代码 * 在 Platform 中通过组合方式关联 Vedio * Platform 的子类也可以关联 Vedio 子类 */ protected Vedio account; public Platform(Vedio account) { this.account = account; } /** * 该方法与 Vedio 中的方法名相同 * 方法名相同不是强制的 * @return */ abstract Vedio openVedio(); }
Linux 系统平台实现
package bridge; public class LinuxPlatform extends Platform { public LinuxPlatform(Vedio account) { super(account); } @Override Vedio openVedio() { System.out.println("在 Linux 平台播放视频"); return account; } }
Android 系统平台实现
package bridge; public class AndroidPlatform extends Platform { public AndroidPlatform(Vedio account) { super(account); } @Override Vedio openVedio() { System.out.println("在 Android 平台打开视频"); return account; } }
测试类
package bridge; public class Main { public static void main(String[] args) { // 在 Linux 平台打开 FLV 视频 Platform linuxPlatform = new LinuxPlatform(new FlvVedio()); Vedio linuxFlv = linuxPlatform.openVedio(); linuxFlv.showVedio(); System.out.println(); // 在 Android 平台打开 MP4 视频 Platform androidPlatform = new AndroidPlatform(new MP4Vedio()); Vedio androidMp4 = androidPlatform.openVedio(); androidMp4.showVedio(); } }
执行结果:
在 Linux 平台播放视频
当前视频格式是 FLV
在 Android 平台打开视频
当前视频格式是 MP4
3、装饰器模式
3.1、介绍
在软件开发过程中,有时想用一些现存的组件,这些组件可能只是完成了一些核心功能,但在不改变其结构的情况下,可以动态地扩展其功能,所有这些都可以釆用装饰器模式来实现。装饰器模式(Decorator Pattern)的定义:指在不改变现有对象结构的情况下,动态地给该对象增加一些职责(即增加其额外功能)的模式,它属于对象结构型模式。
3.2、关系依赖图
3.3、代码实现
图形接口类
Shape
public interface Shape { void draw(); }
图形实现类
Circle
public class Circle implements Shape { @Override public void draw() { System.out.println("Shape: Circle"); } }
Rectangle
public class Rectangle implements Shape { @Override public void draw() { System.out.println("Shape: Rectangle"); } }
图形修饰者接口类
ShapeDecorator
public abstract class ShapeDecorator implements Shape { protected Shape decoratedShape; public ShapeDecorator(Shape decoratedShape) { this.decoratedShape = decoratedShape; } public void draw() { decoratedShape.draw(); } }
图形修饰者实现类
RedShapeDecorator
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("Add Shape Border Color: Red"); } }
最终测试类
DecoratorPatternDemo
public class DecoratorPatternDemo { public static void main(String[] args) { Shape circle = new Circle(); circle.draw(); System.out.println("===================="); ShapeDecorator redCircle = new RedShapeDecorator(new Circle()); redCircle.draw(); System.out.println("===================="); Shape rectangle = new Rectangle(); rectangle.draw(); System.out.println("===================="); ShapeDecorator redRectangle = new RedShapeDecorator(new Rectangle()); redRectangle.draw(); } }
结果:
Shape: Circle
====================
Shape: Circle
Add Shape Border Color: Red
====================
Shape: Rectangle
====================
Shape: Rectangle
Add Shape Border Color: Red
3.4、应用
在java.io包中的FilterInputStream就是一个装饰者类,InputStream in即被装饰者。
public class FilterInputStream extends InputStream { protected volatile InputStream in; protected FilterInputStream(InputStream in) { this.in = in; } //... }
DataInputStream是FilterInputStream子类,则是具体的装饰者类。
public class DataInputStream extends FilterInputStream implements DataInput { public DataInputStream(InputStream in) { super(in); } //... }
4、组合模式
4.1、介绍
组合模式(Composite Pattern)又叫“整体-部分”模式,是用于把一组相似的对象当作一个单一的对象。它是一种将对象组合成树状的层次结构的模式,用来表示“整体-部分”的关系,使用户对单个对象和组合对象具有一致的访问性,属于结构型设计模式。组合模式分为透明式的组合模式和安全式的组合模式。
组合模式一般用来描述整体与部分的关系,它将对象组织到树形结构中,顶层的节点被称为根节点,根节点下面可以包含树枝节点和叶子节点,树枝节点下面又可以包含树枝节点和叶子节点,树形结构图如下:
由上图可以看出,其实根节点和树枝节点本质上属于同一种数据类型,可以作为容器使用,而叶子节点与树枝节点在语义上不属于同一种类型。但是在组合模式中,会把树枝节点和叶子节点看作属于同一种数据类型(用统一接口定义),让它们具备一致行为。
4.2、代码实现
(1)组合模式实现(透明式)
在该方式中,由于抽象构件声明了所有子类中的全部方法,所以客户端无须区别树叶对象和树枝对象,对客户端来说是透明的。但其缺点是:树叶构件本来没有 Add()、Remove() 及 GetChild() 方法,却要实现它们(空实现或抛异常),这样会带来一些安全性问题。其结构如下图:
抽象构件
Component
public interface Component { public void add(Component c); public void remove(Component c); public Component getChild(int i); public void operation(); }
树叶构件
Leaf
public class Leaf implements Component { private String name; public Leaf(String name) { this.name = name; } public void add(Component c) { } public void remove(Component c) { } public Component getChild(int i) { return null; } public void operation() { System.out.println("树叶" + name + ":被访问!"); } }
树枝构件
Composite
public class Composite implements Component { private ArrayList<Component> children = new ArrayList<>(); public void add(Component c) { children.add(c); } public void remove(Component c) { children.remove(c); } public Component getChild(int i) { return children.get(i); } public void operation() { for (Object obj : children) { ((Component) obj).operation(); } } }
客户端测试
CompositePattern
public class CompositePattern { public static void main(String[] args) { Component c0 = new Composite(); Component c1 = new Composite(); Component leaf1 = new Leaf("1"); Component leaf2 = new Leaf("2"); Component leaf3 = new Leaf("3"); c0.add(leaf1); c0.add(c1); c1.add(leaf2); c1.add(leaf3); c0.operation(); } }
(2)组合模式实现(安全式)
在该方式中,将管理子构件的方法移到树枝构件中,抽象构件和树叶构件没有对子对象的管理方法,这样就避免了上一种方式的安全性问题,但由于叶子和分支有不同的接口,客户端在调用时要知道树叶对象和树枝对象的存在,所以失去了透明性。其结构如下图:
抽象构件
Component
public interface Component { public void operation(); }
树叶构件
Leaf
public class Leaf implements Component { private String name; public Leaf(String name) { this.name = name; } public void operation() { System.out.println("树叶" + name + ":被访问!"); } }
树枝构件
Composite
public class Composite implements Component { private ArrayList<Component> children = new ArrayList<>(); public void add(Component c) { children.add(c); } public void remove(Component c) { children.remove(c); } public Component getChild(int i) { return children.get(i); } public void operation() { for (Object obj : children) { ((Component) obj).operation(); } } }
客户端测试
CompositePattern
public class CompositePattern { public static void main(String[] args) { Composite c0 = new Composite(); Composite c1 = new Composite(); Component leaf1 = new Leaf("1"); Component leaf2 = new Leaf("2"); Component leaf3 = new Leaf("3"); c0.add(leaf1); c0.add(c1); c1.add(leaf2); c1.add(leaf3); c0.operation(); } }
4.3、应用
HashMap中有一个putAll方法,参数是一个Map,这就是一种组合模式的体现:
ArrayList中有一个addAll方法,参数是一个Collection,这就是一种组合模式的体现:
5、外观模式
5.1、介绍
在现实生活中,常常存在办事较复杂的例子,如办房产证或注册一家公司,有时要同多个部门联系,这时要是有一个综合部门能解决一切手续问题就好了。
软件设计也是这样,当一个系统的功能越来越强,子系统会越来越多,客户对系统的访问也变得越来越复杂。这时如果系统内部发生改变,客户端也要跟着改变,这违背了“开闭原则”,也违背了“迪米特法则”,所以有必要为多个子系统提供一个统一的接口,从而降低系统的耦合度,这就是外观模式的目标。
外观模式(Facade Pattern)隐藏系统的复杂性,并向客户端提供了一个客户端可以访问系统的接口。这种类型的设计模式属于结构型模式,它向现有的系统添加一个接口,来隐藏系统的复杂性。
5.2、关系依赖图
5.3、代码实现
图形接口类
Shape
public interface Shape { void draw(); }
图形实现类
Circle
public class Circle implements Shape { @Override public void draw() { System.out.println("Circle::draw()"); } }
Rectangle
public class Rectangle implements Shape { @Override public void draw() { System.out.println("Rectangle::draw()"); } }
Square
public class Square implements Shape { @Override public void draw() { System.out.println("Square::draw()"); } }
外观角色类
ShapeMaker
public class ShapeMaker { private Shape circle; private Shape rectangle; private Shape square; public ShapeMaker() { circle = new Circle(); rectangle = new Rectangle(); square = new Square(); } public void drawCircle() { circle.draw(); } public void drawRectangle() { rectangle.draw(); } public void drawSquare() { square.draw(); } }
最终测试类
FacadePatternDemo
public class FacadePatternDemo { public static void main(String[] args) { ShapeMaker shapeMaker = new ShapeMaker(); shapeMaker.drawCircle(); shapeMaker.drawRectangle(); shapeMaker.drawSquare(); } }
结果:
Circle::draw()
Rectangle::draw()
Square::draw()
5.4、应用
在Mybatis源码org.apache.ibatis.session.Configuration类中以 new 开头的方法,下图以newExecutor方法为例:
6、享元模式
6.1、介绍
在面向对象程序设计过程中,有时会面临要创建大量相同或相似对象实例的问题。创建那么多的对象将会耗费很多的系统资源,它是系统性能提高的一个瓶颈。例如,围棋和五子棋中的黑白棋子,教室里的桌子和凳子等。这些对象有很多相似的地方,如果能把它们相同的部分提取出来共享,则能节省大量的系统资源,这就是享元模式的产生背景。
享元模式(Flyweight Pattern)也叫蝇量模式,主要用于减少创建对象的数量,以减少内存占用和提高性能。这种类型的设计模式属于结构型模式,它提供了减少对象数量从而改善应用所需的对象结构的方式。享元模式的定义提出了两个要求,细粒度和共享对象。因为要求细粒度,所以不可避免地会使对象数量多且性质相近,此时我们就将这些对象的信息分为两个部分:内部状态和外部状态。
内部状态指对象共享出来的信息,存储在享元信息内部,并且不会随环境的改变而改变。例如围棋的棋子颜色:黑色、白色。
外部状态指对象得以依赖的一个标记,随环境的改变而改变,不可共享。例如围棋的棋子位置:x横坐标、y纵坐标。
6.2、关系依赖图
6.3、代码实现
非享元对象
Point
public class Point { private int x; //外部状态:代表棋子横坐标 private int y; //外部状态:代表棋子纵坐标 public Point(int x, int y) { this.x = x; this.y = y; } public int getX() { return x; } public void setX(int x) { this.x = x; } public int getY() { return y; } public void setY(int y) { this.y = y; } }
抽象享元对象
ChessPieces
public interface ChessPieces { void downPieces(Point p); //落子方法 }
具体享元对象
WhiteChessPieces
public class WhiteChessPieces implements ChessPieces { private String color; //内部状态:代表棋子颜色 public WhiteChessPieces() { this.color = "白"; } @Override public void downPieces(Point p) { System.out.println(color + "子落入横坐标" + p.getX() + ",纵坐标" + p.getY()); } }
BlackChessPieces
public class BlackChessPieces implements ChessPieces { private String color; //内部状态:代表棋子颜色 public BlackChessPieces() { this.color = "黑"; } @Override public void downPieces(Point p) { System.out.println(color + "子落入横坐标" + p.getX() + ",纵坐标" + p.getY()); } }
享原工厂对象
ChessPiecesFactory
public class ChessPiecesFactory { private HashMap<String, ChessPieces> pools = new HashMap<>(); public ChessPieces getChessPieces(String color) { if (!pools.containsKey(color)) { if ("白".equals(color)) { pools.put(color, new WhiteChessPieces()); } if ("黑".equals(color)) { pools.put(color, new BlackChessPieces()); } } return pools.get(color); } }
最终测试类
Client
public class Client { public static void main(String[] args) { ChessPiecesFactory chessPiecesFactory = new ChessPiecesFactory(); ChessPieces cp1 = chessPiecesFactory.getChessPieces("白"); ChessPieces cp2 = chessPiecesFactory.getChessPieces("白"); ChessPieces cp3 = chessPiecesFactory.getChessPieces("黑"); ChessPieces cp4 = chessPiecesFactory.getChessPieces("黑"); Point p1 = new Point(1, 1); Point p2 = new Point(2, 2); Point p3 = new Point(3, 3); Point p4 = new Point(4, 4); cp1.downPieces(p1); cp2.downPieces(p2); cp3.downPieces(p3); cp4.downPieces(p4); System.out.println("===================="); System.out.println(cp1 == cp2); System.out.println(cp3 == cp4); } }
6.4、应用
如果 Integer.valueOf(x) 中的x在 -128 ~ 127 之间,就是使用享元模式返回,如果不在该范围,则仍然 new。
我们可以单独使用一个测试类来进行测试:
Integer a1 = Integer.valueOf(12); Integer a2 = Integer.valueOf(12); System.out.println(a1 == a2); Integer b1 = Integer.valueOf(128); Integer b2 = Integer.valueOf(128); System.out.println(b1 == b2);
结果:
true
false
7、代理模式
7.1、介绍
在有些情况下,一个客户不能或者不想直接访问另一个对象,这时需要找一个中介帮忙完成某项任务,这个中介就是代理对象。例如,购买火车票不一定要去火车站买,可以通过 12306 网站购买。
代理模式(Proxy Pattern)中,由于某些原因需要给某对象提供一个代理以控制对该对象的访问。这时,访问对象不适合或者不能直接引用目标对象,代理对象作为访问对象和目标对象之间的中介。这种类型的设计模式属于结构型模式。
根据代理的创建时期,代理模式分为静态代理和动态代理。
静态:由程序员创建代理类或特定工具自动生成源代码再对其编译,在程序运行前代理类的 .class 文件就已经存在了。
动态:在程序运行时,运用反射机制动态创建而成。
7.2、关系依赖图
7.3、代码实现
7.3.1、代理模式实现(静态代理)
抽象主题类
UserDao
public interface UserDao { public void save(); public void delete(); public void update(); public void find(); }
真实主题类
UserDaoImpl
public class UserDaoImpl implements UserDao { @Override public void save() { System.out.println("UserDaoImpl save ..."); } @Override public void delete() { System.out.println("UserDaoImpl delete ..."); } @Override public void update() { System.out.println("UserDaoImpl update ..."); } @Override public void find() { System.out.println("UserDaoImpl find ..."); } }
代理类
UserDaoProxy
public class UserDaoProxy implements UserDao { private UserDao userDao; public UserDaoProxy(UserDao userDao) { this.userDao = userDao; } @Override public void save() { userDao.save(); } @Override public void delete() { System.out.println("UserDaoProxy 权限校验 ..."); userDao.delete(); System.out.println("UserDaoProxy 日志记录 ..."); } @Override public void update() { userDao.update(); } @Override public void find() { userDao.find(); } }
测试类
Client
public class Client { public static void main(String[] args) { UserDao userDao = new UserDaoImpl(); UserDao userDaoProxy = new UserDaoProxy(userDao); userDaoProxy.save(); userDaoProxy.delete(); userDaoProxy.update(); userDaoProxy.find(); } }
结果:
UserDaoImpl save ...
UserDaoProxy 权限校验 ...
UserDaoImpl delete ...
UserDaoProxy 日志记录 ...
UserDaoImpl update ...
UserDaoImpl find ...
7.3.2、代理模式实现(JDK动态代理)
抽象主题类
UserDao
public interface UserDao { public void save(); public void delete(); public void update(); public void find(); }
真实主题类
UserDaoImpl
public class UserDaoImpl implements UserDao { @Override public void save() { System.out.println("UserDaoImpl save ..."); } @Override public void delete() { System.out.println("UserDaoImpl delete ..."); } @Override public void update() { System.out.println("UserDaoImpl update ..."); } @Override public void find() { System.out.println("UserDaoImpl find ..."); } }
代理类
UserDaoProxy
public class UserDaoProxy { private UserDao userDao; public UserDaoProxy(UserDao userDao) { this.userDao = userDao; } public UserDao createUserDao() { Class<? extends UserDao> clazz = userDao.getClass(); UserDao ud = (UserDao) Proxy.newProxyInstance( clazz.getClassLoader(), clazz.getInterfaces(), new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Object result; if ("delete".equals(method.getName())) { System.out.println("UserDaoProxy 权限校验 ..."); result = method.invoke(userDao, args); System.out.println("UserDaoProxy 日志记录 ..."); } else { result = method.invoke(userDao, args); } return result; } }); return ud; } }
测试类
Client
public class Client { public static void main(String[] args) { UserDao userDao = new UserDaoImpl(); UserDao userDaoProxy = new UserDaoProxy(userDao).createUserDao(); userDaoProxy.save(); userDaoProxy.delete(); userDaoProxy.update(); userDaoProxy.find(); } }
结果:
UserDaoImpl save ...
UserDaoProxy 权限校验 ...
UserDaoImpl delete ...
UserDaoProxy 日志记录 ...
UserDaoImpl update ...
UserDaoImpl find ...
7.3.3、代理模式实现(CGLIB动态代理)CGLib 动态代理方式可以无需使用抽象主题类。
真实主题类
UserDaoImpl
public class UserDaoImpl { public void save() { System.out.println("UserDaoImpl save ..."); } public void delete() { System.out.println("UserDaoImpl delete ..."); } public void update() { System.out.println("UserDaoImpl update ..."); } public void find() { System.out.println("UserDaoImpl find ..."); } }
代理类
UserDaoImplProxy
public class UserDaoImplProxy { public UserDaoImpl createUserDaoImpl() { //创建增强对象 Enhancer enhancer = new Enhancer(); //设置父类对象 enhancer.setSuperclass(UserDaoImpl.class); //设置回调方法 enhancer.setCallback(new MethodInterceptor() { @Override public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { Object result; if ("delete".equals(method.getName())) { System.out.println("UserDaoImplProxy 权限校验 ..."); result = methodProxy.invokeSuper(proxy, args); System.out.println("UserDaoImplProxy 日志记录 ..."); } else { result = methodProxy.invokeSuper(proxy, args); } return result; } }); //返回代理对象 return (UserDaoImpl) enhancer.create(); } }
测试类
Client
public class Client { public static void main(String[] args) { UserDaoImpl userDaoImplProxy = new UserDaoImplProxy().createUserDaoImpl(); userDaoImplProxy.save(); userDaoImplProxy.delete(); userDaoImplProxy.update(); userDaoImplProxy.find(); } }
结果:
UserDaoImpl save ...
UserDaoProxy 权限校验 ...
UserDaoImpl delete ...
UserDaoProxy 日志记录 ...
UserDaoImpl update ...
UserDaoImpl find ...
7.4、应用
Spring 使用动态代理实现 AOP 时有两个非常重要的类,即 JdkDynamicAopProxy 类和 CglibAopProxy 类,其类图如下:
Spring 中的代理选择如下:
当 Bean 有实现接口时,Spring 会用 JDK 动态代理方式。
当 Bean 没有实现接口时,Spring 会选择 CGLib 动态代理方式。
Spring 可以通过配置强制使用 CGLib 动态代理,只需在 Spring 的配置文件中加入如下代码即可。
<aop:aspectj-autoproxy proxy-target-class="true"/>
三、行为模式
1、模板模式
1.1、介绍
在面向对象程序设计过程中,程序员常常会遇到这种情况:设计一个系统时知道了算法所需的关键步骤,而且确定了这些步骤的执行顺序,但某些步骤的具体实现还未知,或者说某些步骤的实现与具体的环境相关。
例如,去银行办理业务一般要经过以下4个流程:取号、排队、办理具体业务、对银行工作人员进行评分等,其中取号、排队和对银行工作人员进行评分的业务对每个客户是一样的,可以在父类中实现,但是办理具体业务却因人而异,它可能是存款、取款或者转账等,可以延迟到子类中实现。
这样的例子在生活中还有很多,例如,一个人每天会起床、吃饭、做事、睡觉等,其中“做事”的内容每天可能不同。我们把这些规定了流程或格式的实例定义成模板,允许使用者根据自己的需求去更新它,例如,简历模板、论文模板、Word 中模板文件等。
在模板模式(Template Pattern)中,一个抽象类公开定义了执行它的方法的方式/模板,它的子类可以按需要重写方法实现,但调用将以抽象类中定义的方式进行。这种类型的设计模式属于行为型模式。
1.2、关系依赖图
1.3、代码实现
抽象父类
Game
public abstract class Game { abstract void initialize(); abstract void startPlay(); abstract void endPlay(); //模板 public final void play() { //初始化游戏 initialize(); //开始游戏 startPlay(); //结束游戏 endPlay(); } }
具体子类
Football
public class Football extends Game { @Override void initialize() { System.out.println("Football Game Initialized."); } @Override void startPlay() { System.out.println("Football Game Started."); } @Override void endPlay() { System.out.println("Football Game Finished."); } }
Basketball
public class Basketball extends Game { @Override void initialize() { System.out.println("Basketball Game Initialized."); } @Override void startPlay() { System.out.println("Basketball Game Started."); } @Override void endPlay() { System.out.println("Basketball Game Finished."); } }
测试类
Client
public class Client { public static void main(String[] args) { Game g1 = new Football(); g1.play(); Game g2 = new Basketball(); g2.play(); } }
结果:
Football Game Initialized.
Football Game Started.
Football Game Finished.
Basketball Game Initialized.
Basketball Game Started.
Basketball Game Finished.
1.4、应用
在 Servlet 中,每一个 Servlet 类都必须要实现 HttpServlet 接口。GenericServlet 是个通用的、不特定于任何协议的 Servlet,它实现了 Servlet 接口。而 HttpServlet 继承于 GenericServlet,为 Servlet 接口提供了处理 HTTP 协议的通用实现,所以我们定义 Servlet 时只需要继承 HttpServlet 即可。
public abstract class HttpServlet extends GenericServlet { //以上代码部分省略... protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //省略... } protected void doHead(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //省略... } protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //省略... } protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //省略... } protected void doDelete(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //省略... } protected void doOptions(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //省略... } protected void doTrace(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //省略... } protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String method = req.getMethod(); if (method.equals(METHOD_GET)) { long lastModified = getLastModified(req); if (lastModified == -1) { // servlet doesn't support if-modified-since, no reason // to go through further expensive logic doGet(req, resp); } else { long ifModifiedSince; try { ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE); } catch (IllegalArgumentException iae) { // Invalid date header - proceed as if none was set ifModifiedSince = -1; } if (ifModifiedSince < (lastModified / 1000 * 1000)) { // If the servlet mod time is later, call doGet() // Round down to the nearest second for a proper compare // A ifModifiedSince of -1 will always be less maybeSetLastModified(resp, lastModified); doGet(req, resp); } else { resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED); } } } else if (method.equals(METHOD_HEAD)) { long lastModified = getLastModified(req); maybeSetLastModified(resp, lastModified); doHead(req, resp); } else if (method.equals(METHOD_POST)) { doPost(req, resp); } else if (method.equals(METHOD_PUT)) { doPut(req, resp); } else if (method.equals(METHOD_DELETE)) { doDelete(req, resp); } else if (method.equals(METHOD_OPTIONS)) { doOptions(req,resp); } else if (method.equals(METHOD_TRACE)) { doTrace(req,resp); } else { // // Note that this means NO servlet supports whatever // method was requested, anywhere on this server. // String errMsg = lStrings.getString("http.method_not_implemented"); Object[] errArgs = new Object[1]; errArgs[0] = method; errMsg = MessageFormat.format(errMsg, errArgs); resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg); } } //以下部分代码省略... }
在 HttpServlet 的 service 方法中,首先获取请求的方法名,然后根据方法名调用对应的 doXXX 方法,比如说请求方法为 GET,那么就去调用 doGet 方法;请求方法为 POST,那么就去调用 doPost 方法。
这里,HttpServlet 就相当于定义了一套处理 HTTP 请求的模板。service 方法为模板方法,定义了处理 HTTP 请求的基本流程;doXXX 等方法为基本方法,根据请求方法做相应的处理,子类可重写这些方法。HttpServletRequest 中的 Method 则起到钩子方法的作用。
比如实现一个输出 Hello World 的简单 Servlet,代码如下:
public class HelloWorld extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType("text/html"); PrintWriter out = response.getWriter(); out.println("<h1>Hello World!</h1>"); } }
2、命令模式
2.1、介绍
在软件开发系统中,“方法的请求者”与“方法的实现者”之间经常存在紧密的耦合关系,这不利于软件功能的扩展与维护。例如,想对方法进行“撤销、重做、记录”等处理都很不方便,因此“如何将方法的请求者与实现者解耦”变得很重要,命令模式就能很好地解决这个问题。
在现实生活中,命令模式的例子也很多。比如看电视时,我们只需要轻轻一按遥控器就能完成频道的切换,这就是命令模式,将换台请求和换台处理完全解耦了。电视机遥控器(命令发送者)通过按钮(具体命令)来遥控电视机(命令接收者)。
命令模式(Command Pattern)是一种数据驱动的设计模式,它属于行为型模式。请求以命令的形式包裹在对象中,并传给调用对象。调用对象寻找可以处理该命令的合适的对象,并把该命令传给相应的对象,该对象执行命令。
2.2、关系依赖图
2.3、代码实现
抽象命令类
Command
public interface Command { //执行动作(操作) public void execute(); //撤销动作(操作) public void undo(); }
具体命令类
NoCommand
public class NoCommand implements Command { @Override public void execute() { } @Override public void undo() { } }
TVOnCommand
public class TVOnCommand implements Command { TVReceiver tv; public TVOnCommand(TVReceiver tv) { this.tv = tv; } @Override public void execute() { tv.on(); } @Override public void undo() { tv.off(); } }
TVOffCommand
public class TVOffCommand implements Command { TVReceiver tv; public TVOffCommand(TVReceiver tv) { this.tv = tv; } @Override public void execute() { tv.off(); } @Override public void undo() { tv.on(); } }
命令接收者
TVReceiver
public class TVReceiver { public void on() { System.out.println(" 电视机打开了... "); } public void off() { System.out.println(" 电视机关闭了... "); } }
命令请求者
RemoteController
public class RemoteController { Command[] onCommands; //开命令集合 Command[] offCommands; //关命令集合 Command undoCommand; //撤销命令 public RemoteController() { onCommands = new Command[5]; offCommands = new Command[5]; for (int i = 0; i < 5; i++) { onCommands[i] = new NoCommand(); offCommands[i] = new NoCommand(); } } //设置命令按钮 public void setCommand(int no, Command onCommand, Command offCommand) { onCommands[no] = onCommand; offCommands[no] = offCommand; } //按下开按钮 public void onButtonWasPushed(int no) { //找到你按下的开的按钮,并调用对应方法 onCommands[no].execute(); //记录这次的操作,用于撤销 undoCommand = onCommands[no]; } //按下关按钮 public void offButtonWasPushed(int no) { //找到你按下的关的按钮,并调用对应方法 offCommands[no].execute(); //记录这次的操作,用于撤销 undoCommand = offCommands[no]; } //按下撤销按钮 public void undoButtonWasPushed() { undoCommand.undo(); } }
最终测试类
Client
public class Client { public static void main(String[] args) { //创建远程遥控器 RemoteController remoteController = new RemoteController(); //创建电视机接收者 TVReceiver tvReceiver = new TVReceiver(); TVOffCommand tvOffCommand = new TVOffCommand(tvReceiver); TVOnCommand tvOnCommand = new TVOnCommand(tvReceiver); remoteController.setCommand(0, tvOnCommand, tvOffCommand); //调用电视机的操作 remoteController.onButtonWasPushed(0); remoteController.offButtonWasPushed(0); remoteController.undoButtonWasPushed(); } }
结果:
电视机打开了...
电视机关闭了...
电视机打开了...
2.4、应用
Spring 框架的 JdbcTemplate 就使用到了命令模式,不过并不是命令模式的标准实现,只是用到了命令模式的思想。
StatementCallback充当了抽象命令类。
@FunctionalInterface public interface StatementCallback<T> { @Nullable T doInStatement(Statement stmt) throws SQLException, DataAccessException; }
以下图片中是实现了StatementCallback的所有具体命令类。
这里QueryStatementCallback使用到了匿名内部类而不是单独写成一个类文件,它实现了命令接口, 同时也充当命令接收者。
@Nullable public <T> T query(final String sql, final ResultSetExtractor<T> rse) throws DataAccessException { Assert.notNull(sql, "SQL must not be null"); Assert.notNull(rse, "ResultSetExtractor must not be null"); if (this.logger.isDebugEnabled()) { this.logger.debug("Executing SQL query [" + sql + "]"); } class QueryStatementCallback implements StatementCallback<T>, SqlProvider { QueryStatementCallback() {} @Nullable public T doInStatement(Statement stmt) throws SQLException { ResultSet rs = null; Object var3; try { rs = stmt.executeQuery(sql); var3 = rse.extractData(rs); } finally { JdbcUtils.closeResultSet(rs); } return var3; } public String getSql() { return sql; } } return this.execute(new QueryStatementCallback(), true); }
命令调用者是 JdbcTemplate,其中 execute(StatementCallback<T> action, boolean closeResources) 方法中,调用 action.doInStatement(stmt) 方法,不同的实现 StatementCallback 接口的对象,对应不同的 doInStatemnt 实现逻辑。
@Nullable private <T> T execute(StatementCallback<T> action, boolean closeResources) throws DataAccessException { Assert.notNull(action, "Callback object must not be null"); Connection con = DataSourceUtils.getConnection(this.obtainDataSource()); Statement stmt = null; Object var12; try { stmt = con.createStatement(); this.applyStatementSettings(stmt); T result = action.doInStatement(stmt); this.handleWarnings(stmt); var12 = result; } catch (SQLException var10) { String sql = getSql(action); JdbcUtils.closeStatement(stmt); stmt = null; DataSourceUtils.releaseConnection(con, this.getDataSource()); con = null; throw this.translateException("StatementCallback", sql, var10); } finally { if (closeResources) { JdbcUtils.closeStatement(stmt); DataSourceUtils.releaseConnection(con, this.getDataSource()); } } return var12; }
3、访问者模式
3.1、介绍
在现实生活中,有些集合对象存在多种不同的元素,且每种元素也存在多种不同的访问者和处理方式。例如,公园中存在多个景点,也存在多个游客,不同的游客对同一个景点的评价可能不同;医院医生开的处方单中包含多种药元素,査看它的划价员和药房工作人员对它的处理方式也不同,划价员根据处方单上面的药品名和数量进行划价,药房工作人员根据处方单的内容进行抓药。
访问者模式(Visitor Pattern)主要将数据结构与数据操作分离,我们使用了一个访问者类,它改变了元素类的执行算法。通过这种方式,元素的执行算法可以随着访问者改变而改变。这种类型的设计模式属于行为型模式。根据模式,元素对象已接受访问者对象,这样访问者对象就可以处理元素对象上的操作。
3.2、关系依赖图
3.3、代码实现
抽象访问者类
ComputerPartVisitor
public interface ComputerPartVisitor { void visit(Mouse mouse); void visit(Keyboard keyboard); }
具体访问者类
MIComputerPartVisitor
public class MIComputerPartVisitor implements ComputerPartVisitor { @Override public void visit(Mouse mouse) { System.out.println("Displaying XiaoMi Mouse ..."); } @Override public void visit(Keyboard keyboard) { System.out.println("Displaying XiaoMi Keyboard ..."); } }
HPComputerPartVisitor
public class HPComputerPartVisitor implements ComputerPartVisitor { @Override public void visit(Mouse mouse) { System.out.println("Displaying HuiPu Mouse ..."); } @Override public void visit(Keyboard keyboard) { System.out.println("Displaying HuiPu Keyboard ..."); } }
抽象元素类
ComputerPart
public interface ComputerPart { void accept(ComputerPartVisitor computerPartVisitor); }
具体元素类
Mouse
public class Mouse implements ComputerPart { @Override public void accept(ComputerPartVisitor computerPartVisitor) { computerPartVisitor.visit(this); } }
Keyboard
public class Keyboard implements ComputerPart { @Override public void accept(ComputerPartVisitor computerPartVisitor) { computerPartVisitor.visit(this); } }
对象结构类
ObjectStructure
public class ObjectStructure { private List<ComputerPart> computerParts = new ArrayList<>(); public void attach(ComputerPart computerPart) { computerParts.add(computerPart); } public void detach(ComputerPart computerPart) { computerParts.remove(computerPart); } public void display(ComputerPartVisitor computerPartVisitor) { for (ComputerPart computerPart : computerParts) { computerPart.accept(computerPartVisitor); } } }
最终测试类
Client
public class Client { public static void main(String[] args) { ObjectStructure os = new ObjectStructure(); os.attach(new Mouse()); os.attach(new Keyboard()); os.display(new MIComputerPartVisitor()); os.display(new HPComputerPartVisitor()); } }
结果:
Displaying XiaoMi Mouse ...
Displaying XiaoMi Keyboard ...
Displaying HuiPu Mouse ...
Displaying HuiPu Keyboard ...
3.4、应用
在早期的 Java 版本中,如果要对指定目录下的文件进行遍历,必须用递归的方式来实现,这种方法复杂且灵活性不高。
Java 7 版本后,Files 类提供了 walkFileTree() 方法,该方法可以很容易的对目录下的所有文件进行遍历,需要 Path、FileVisitor 两个参数。其中,Path 是要遍历文件的路径,FileVisitor 则可以看成一个文件访问器。源码如下:
public final class Files { //以上代码部分省略... public static Path walkFileTree(Path start, FileVisitor<? super Path> visitor) throws IOException { return walkFileTree(start, EnumSet.noneOf(FileVisitOption.class), Integer.MAX_VALUE, visitor); } //以下代码部分省略... }
FileVisitor 提供了递归遍历文件树的支持,这个接口的方法表示了遍历过程中的关键过程,允许在文件被访问、目录将被访问、目录已被访问、发生错误等过程中进行控制。换句话说,这个接口在文件被访问前、访问中和访问后,以及产生错误的时候都有相应的钩子程序进行处理。FileVisitor 主要提供了 4 个方法,且返回结果的都是 FileVisitResult 对象值,用于决定当前操作完成后接下来该如何处理。FileVisitResult 是一个枚举类,代表返回之后的一些后续操作。源码如下:
public interface FileVisitor<T> { FileVisitResult preVisitDirectory(T dir, BasicFileAttributes attrs) throws IOException; FileVisitResult visitFile(T file, BasicFileAttributes attrs) throws IOException; FileVisitResult visitFileFailed(T file, IOException exc) throws IOException; FileVisitResult postVisitDirectory(T dir, IOException exc) throws IOException; }
FileVisitResult 主要包含 4 个常见的操作:
FileVisitResult.CONTINUE:这个访问结果表示当前的遍历过程将会继续。
FileVisitResult.SKIP_SIBLINGS:这个访问结果表示当前的遍历过程将会继续,但是要忽略当前文件/目录的兄弟节点。
FileVisitResult.SKIP_SUBTREE:这个访问结果表示当前的遍历过程将会继续,但是要忽略当前目录下的所有节点。
FileVisitResult.TERMINATE:这个访问结果表示当前的遍历过程将会停止。
public enum FileVisitResult { CONTINUE, TERMINATE, SKIP_SUBTREE, SKIP_SIBLINGS; }
通过访问者去遍历文件树会比较方便,比如查找文件夹内符合某个条件的文件或者某一天内所创建的文件,这个类中都提供了相对应的方法。它的实现也非常简单,代码如下:
public class SimpleFileVisitor<T> implements FileVisitor<T> { protected SimpleFileVisitor() {} @Override public FileVisitResult preVisitDirectory(T dir, BasicFileAttributes attrs) throws IOException { Objects.requireNonNull(dir); Objects.requireNonNull(attrs); return FileVisitResult.CONTINUE; } @Override public FileVisitResult visitFile(T file, BasicFileAttributes attrs) throws IOException { Objects.requireNonNull(file); Objects.requireNonNull(attrs); return FileVisitResult.CONTINUE; } @Override public FileVisitResult visitFileFailed(T file, IOException exc) throws IOException { Objects.requireNonNull(file); throw exc; } @Override public FileVisitResult postVisitDirectory(T dir, IOException exc) throws IOException { Objects.requireNonNull(dir); if (exc != null) throw exc; return FileVisitResult.CONTINUE; } }
4、迭代器模式
4.1、介绍
在现实生活以及程序设计中,经常要访问一个聚合对象中的各个元素,如“数据结构”中的链表遍历,通常的做法是将链表的创建和遍历都放在同一个类中,但这种方式不利于程序的扩展,如果要更换遍历方法就必须修改程序源代码,这违背了“开闭原则”。
迭代器模式(Iterator Pattern)是 Java 和 .Net 编程环境中非常常用的设计模式。这种模式用于顺序访问集合对象的元素,不需要知道集合对象的底层表示。迭代器模式属于行为型模式。
4.2、关系依赖图
4.3、代码实现
抽象迭代器
Iterator
public interface Iterator { Object first(); Object next(); boolean hasNext(); }
具体迭代器
ConcreteIterator
public class ConcreteIterator implements Iterator { private List<Object> list; private int index = -1; public ConcreteIterator(List<Object> list) { this.list = list; } @Override public boolean hasNext() { if (index < list.size() - 1) { return true; } else { return false; } } @Override public Object first() { index = 0; Object obj = list.get(index); return obj; } @Override public Object next() { Object obj = null; if (this.hasNext()) { obj = list.get(++index); } return obj; } }
抽象聚合类
Aggregate
public interface Aggregate { void add(Object obj); void remove(Object obj); Iterator getIterator(); }
具体聚合类
ConcreteAggregate
public class ConcreteAggregate implements Aggregate { private List<Object> list = new ArrayList<>(); public void add(Object obj) { list.add(obj); } public void remove(Object obj) { list.remove(obj); } public Iterator getIterator() { return new ConcreteIterator(list); } }
最终测试类
Client
public class Client { public static void main(String[] args) { Aggregate ag = new ConcreteAggregate(); ag.add("张三"); ag.add("李四"); ag.add("王五"); Iterator it = ag.getIterator(); while (it.hasNext()) { Object object = it.next(); System.out.println(object.toString()); } } }
结果:
张三
李四
王五
4.4、应用
JDK 的 ArrayList 集合中就使用了迭代器模式。
public class ArrayListTest { public static void main(String[] args) { List<String> list = new ArrayList<>(); list.add("张三"); list.add("李四"); list.add("王五"); Iterator<String> it = list.iterator(); while (it.hasNext()) { Object object = it.next(); System.out.println(object); } } }
结果:
张三
李四
王五
5、观察者模式
5.1、介绍
气象站可以将每天测量到的温度、湿度、气压等信息,可以用公告的形式发布出去,供给自己的网站或者第三方网站使用,当温度、湿度、气压数据更新的时候,需要实时的通知给自己的网站或者第三方网站,这时候需要使用观察者模式。
观察者模式(Observer Pattern)是指多个对象间存在一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。这种模式有时又称作发布-订阅模式、模型-视图模式,它是对象行为型模式。
5.2、关系依赖图
5.3、代码实现
抽象主题类
Subject
public class Subject { private boolean changed = false; private List<Observer> observers = new ArrayList<>(); //添加一个观察者 public void addObserver(Observer o) { observers.add(o); } //删除一个观察者 public void deleteObserver(Observer o) { observers.remove(o); } //通知所有观察者 public void notifyObservers(Object arg) { //如果数据没改变,那就不需要通知 if (changed == false) { return; } //通知所有的观察者来进行数据更新 for (Observer observer : observers) { ((Observer) observer).update(this, arg); } //更新完成以后,清除当前主题状态 this.clearChanged(); } public void setChanged() { changed = true; } public void clearChanged() { changed = false; } }
具体主题类
Weather
public class Weather { private float temperature; //温度 private float humidity; //湿度 private float pressure; //压力 public Weather() {} public Weather(float temperature, float humidity, float pressure) { this.temperature = temperature; this.humidity = humidity; this.pressure = pressure; } public float getTemperature() { return temperature; } public void setTemperature(float temperature) { this.temperature = temperature; } public float getHumidity() { return humidity; } public void setHumidity(float humidity) { this.humidity = humidity; } public float getPressure() { return pressure; } public void setPressure(float pressure) { this.pressure = pressure; } }
WeatherSubject
public class WeatherSubject extends Subject { private Weather weather; public Weather getWeather() { return weather; } public void setWeather(Weather weather) { this.weather = weather; setChanged(); notifyObservers(weather); } }
抽象观察者
Observer
public interface Observer { void update(Subject s, Object arg); }
具体观察者
WebSite1
public class WebSite1 implements Observer { private Weather weather; public void display() { System.out.println("***** WebSite1 *****"); System.out.println("温度:" + weather.getTemperature()); System.out.println("湿度:" + weather.getHumidity()); System.out.println("压力:" + weather.getPressure()); System.out.println("********************"); } @Override public void update(Subject s, Object arg) { this.weather = (Weather) arg; display(); } }
WebSite2
public class WebSite2 implements Observer { private Weather weather; public void display() { System.out.println("***** WebSite2 *****"); System.out.println("温度:" + weather.getTemperature()); System.out.println("湿度:" + weather.getHumidity()); System.out.println("压力:" + weather.getPressure()); System.out.println("********************"); } @Override public void update(Subject s, Object arg) { this.weather = (Weather) arg; display(); } }
最终测试类
Client
public class Client { public static void main(String[] args) { WeatherSubject ws = new WeatherSubject(); ws.addObserver(new WebSite1()); ws.addObserver(new WebSite2()); ws.setWeather(new Weather(10f, 100f, 30.3f)); } }
结果:
***** WebSite1 *****
温度:10.0
湿度:100.0
压力:30.3
********************
***** WebSite2 *****
温度:10.0
湿度:100.0
压力:30.3
********************
5.4、应用
在 Java 中,通过 java.util.Observable 类和 java.util.Observer 接口定义了观察者模式,只要实现它们就可以编写观察者模式实例。
具体主题类
Weather
public class Weather { private float temperature; //温度 private float humidity; //湿度 private float pressure; //压力 public Weather() {} public Weather(float temperature, float humidity, float pressure) { this.temperature = temperature; this.humidity = humidity; this.pressure = pressure; } public float getTemperature() { return temperature; } public void setTemperature(float temperature) { this.temperature = temperature; } public float getHumidity() { return humidity; } public void setHumidity(float humidity) { this.humidity = humidity; } public float getPressure() { return pressure; } public void setPressure(float pressure) { this.pressure = pressure; } }
WeatherObservable
import java.util.Observable; public class WeatherObservable extends Observable { private Weather weather; public Weather getWeather() { return weather; } public void setWeather(Weather weather) { this.weather = weather; setChanged(); notifyObservers(weather); } }
具体观察者
WebSite1
import java.util.Observable; import java.util.Observer; public class WebSite1 implements Observer { private Weather weather; public void display() { System.out.println("***** WebSite1 *****"); System.out.println("温度:" + weather.getTemperature()); System.out.println("湿度:" + weather.getHumidity()); System.out.println("压力:" + weather.getPressure()); System.out.println("********************"); } @Override public void update(Observable o, Object arg) { this.weather = (Weather) arg; display(); } }
WebSite2
import java.util.Observable; import java.util.Observer; public class WebSite2 implements Observer { private Weather weather; public void display() { System.out.println("***** WebSite2 *****"); System.out.println("温度:" + weather.getTemperature()); System.out.println("湿度:" + weather.getHumidity()); System.out.println("压力:" + weather.getPressure()); System.out.println("********************"); } @Override public void update(Observable o, Object arg) { this.weather = (Weather) arg; display(); } }
最终测试类
Client
public class Client { public static void main(String[] args) { WeatherObservable wo = new WeatherObservable(); wo.addObserver(new WebSite1()); wo.addObserver(new WebSite2()); wo.setWeather(new Weather(10f, 100f, 30.3f)); } }
结果:
***** WebSite2 *****
温度:10.0
湿度:100.0
压力:30.3
********************
***** WebSite1 *****
温度:10.0
湿度:100.0
压力:30.3
********************
6、中介者模式
6.1、介绍
在现实生活中,常常会出现好多对象之间存在复杂的交互关系,这种交互关系常常是“网状结构”,它要求每个对象都必须知道它需要交互的对象。例如,每个人必须记住他(她)所有朋友的电话;而且朋友中如果有人的电话修改了,他(她)必须让其他所有的朋友一起修改,这叫作“牵一发而动全身”,非常复杂。
如果把这种“网状结构”改为“星形结构”的话,将大大降低它们之间的“耦合性”,这时只要找一个“中介者”就可以了。如前面所说的“每个人必须记住所有朋友电话”的问题,只要在网上建立一个每个朋友都可以访问的“通信录”就解决了。这样的例子还有很多,例如,你刚刚参加工作想租房,可以找“房屋中介”;或者,自己刚刚到一个陌生城市找工作,可以找“人才交流中心”帮忙。
中介者模式(Mediator Pattern)是用来降低多个对象和类之间的通信复杂性。这种模式提供了一个中介类,该类通常处理不同类之间的通信,并支持松耦合,使代码易于维护。中介者模式属于行为型模式。
6.2、关系依赖图
6.3、代码实现
抽象中介者
Mediator
public abstract class Mediator { public abstract void register(Colleague colleague); public abstract void relay(Colleague colleague); }
具体中介者
ConcreteMediator
public class ConcreteMediator extends Mediator { private List<Colleague> colleagues = new ArrayList<>(); public void register(Colleague colleague) { if (!colleagues.contains(colleague)) { colleagues.add(colleague); colleague.setMediator(this); } } public void relay(Colleague colleague) { for (Colleague c : colleagues) { if (!c.equals(colleague)) { ((Colleague) c).receive(); } } } }
抽象同事类
Colleague
public abstract class Colleague { protected Mediator mediator; public void setMediator(Mediator mediator) { this.mediator = mediator; } public abstract void receive(); public abstract void send(); }
具体同事类
ConcreteColleague1
public class ConcreteColleague1 extends Colleague { public void receive() { System.out.println("具体同事类1收到请求..."); } public void send() { System.out.println("具体同事类1发出请求..."); mediator.relay(this); //请中介者转发 } }
ConcreteColleague2
public class ConcreteColleague2 extends Colleague { public void receive() { System.out.println("具体同事类2收到请求..."); } public void send() { System.out.println("具体同事类2发出请求..."); mediator.relay(this); //请中介者转发 } }
最终测试类
Client
public class Client { public static void main(String[] args) { Mediator mediator = new ConcreteMediator(); Colleague c1 = new ConcreteColleague1(); Colleague c2 = new ConcreteColleague2(); mediator.register(c1); mediator.register(c2); c1.send(); c2.send(); } }
结果:
具体同事类1发出请求...
具体同事类2收到请求...
具体同事类2发出请求...
具体同事类1收到请求...
6.4、应用
打开 JDK 中的 Timer 类,查看 Timer 类的结构,会发现 Timer 类中有很多 schedule() 重载方法,如下图所示:
任意点开其中一个方法,会发现所有方法最终都调用了私有的 sched() 方法,源码如下:
不论是什么样的任务加入到队列中,我们都把这个队列中的对象称为“同事”。查看 sched() 方法的源码可以看出,所有的任务(task)都放到了 Timer 类维护的 task 队列中,同事之间的通信都是通过 Timer 来协调完成的,所以,Timer 承担了中介者的角色,而 task 队列内的任务就是具体同事对象。
7、备忘录模式
7.1、介绍
每个人都有犯错误的时候,都希望有种“后悔药”能弥补自己的过失,让自己重新开始,但现实是残酷的。在计算机应用中,客户同样会常常犯错误,能否提供“后悔药”给他们呢?当然是可以的,而且是有必要的。这个功能由“备忘录模式”来实现。
很多应用软件都提供了这项功能,如Word、Notepad、Photoshop等软件在编辑时按Ctrl+Z组合键时能撤销当前操作,使文档恢复到之前的状态;还有在 IE 中的后退键、数据库事务管理中的回滚操作、玩游戏时的中间结果存档功能、数据库与操作系统的备份操作、棋类游戏中的悔棋功能等都属于这类。备忘录模式能记录一个对象的内部状态,当用户后悔时能撤销当前操作,使数据恢复到它原先的状态。
备忘录模式(Memento Pattern)保存一个对象的某个状态,以便在适当的时候恢复对象。备忘录模式属于行为型模式。
7.2、关系依赖图
7.3、代码实现
发起人
Originator
public class Originator { private String state; public String getState() { return state; } public void setState(String state) { this.state = state; } public Memento createMemento() { return new Memento(state); } public void restoreMemento(Memento memento) { this.state = memento.getState(); } }
备忘录
Memento
public class Memento { private String state; public Memento(String state) { this.state = state; } public String getState() { return state; } public void setState(String state) { this.state = state; } }
管理者
Caretaker
public class Caretaker { private List<Memento> mementoList = new ArrayList<>(); public void add(Memento memento) { mementoList.add(memento); } public Memento get(int index) { return mementoList.get(index); } }
测试类
Client
public class Client { public static void main(String[] args) { Originator originator = new Originator(); Caretaker caretaker = new Caretaker(); originator.setState(" 状态#1 攻击力 300 "); caretaker.add(originator.createMemento()); originator.setState(" 状态#2 攻击力 200 "); caretaker.add(originator.createMemento()); originator.setState(" 状态#3 攻击力 100 "); caretaker.add(originator.createMemento()); System.out.println("当前的状态是:" + originator.getState()); originator.restoreMemento(caretaker.get(0)); System.out.println("当前的状态是:" + originator.getState()); } }
结果:
当前的状态是: 状态#3 攻击力 100
当前的状态是: 状态#1 攻击力 300
7.4、应用
由于 JDK、Spring、Mybatis 中很少有备忘录模式,所以该设计模式不做典型应用源码分析。
8、解释器模式
8.1、介绍
在软件开发中,会遇到有些问题多次重复出现,而且有一定的相似性和规律性。如果将它们归纳成一种简单的语言,那么这些问题实例将是该语言的一些句子,这样就可以用“编译原理”中的解释器模式来实现了。
在“编译原理”中,一个算术表达式通过词法分析器形成词法单元,而后这些词法单元再通过语法分析器构建语法分析树,最终形成一颗抽象的语法分析树,这里的词法分析器和语法分析器都可以看做是解释器。
解释器模式(Interpreter Pattern)提供了评估语言的语法或表达式的方式,它属于行为型模式。这种模式实现了一个表达式接口,该接口解释一个特定的上下文。这种模式被用在 SQL 解析、符号处理引擎等。
解释器模式常用于对简单语言的编译或分析实例中,为了掌握好它的结构与实现,必须先了解编译原理中的“文法、句子、语法树”等相关概念。
8.2、关系依赖图
8.3、代码实现
抽象表达式
Expression
public abstract class Expression { public abstract int interpreter(HashMap<String, Integer> var); }
终结符表达式
VarExpression
public class VarExpression extends Expression { private String key; public VarExpression(String key) { this.key = key; } @Override public int interpreter(HashMap<String, Integer> var) { return var.get(this.key); } }
非终结符表达式
SymbolExpression
public class SymbolExpression extends Expression { protected Expression left; protected Expression right; public SymbolExpression(Expression left, Expression right) { this.left = left; this.right = right; } @Override public int interpreter(HashMap<String, Integer> var) { return 0; } }
环境类
Calculator
public class Calculator { private Expression expression; private Expression left; private Expression right; public Calculator(String expStr) { Stack<Expression> stack = new Stack<>(); char[] charArray = expStr.toCharArray(); 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); } }
测试类
Client
public class Client { public static void main(String[] args) throws IOException { String expStr = getExpStr(); HashMap<String, Integer> expVal = getValue(expStr); System.out.println("最终运算结果:" + expStr + "=" + new Calculator(expStr).run(expVal)); } public static String getExpStr() throws IOException { System.out.print("请输入表达式:"); return (new BufferedReader(new InputStreamReader(System.in))).readLine(); } public static HashMap<String, Integer> getValue(String expStr) throws IOException { HashMap<String, Integer> map = new HashMap<>(); for (char ch : expStr.toCharArray()) { if (ch != '+' && ch != '-') { if (!map.containsKey(String.valueOf(ch))) { System.out.print("请输入" + String.valueOf(ch) + "的值:"); String value = (new BufferedReader(new InputStreamReader(System.in))).readLine(); map.put(String.valueOf(ch), Integer.valueOf(value)); } } } return map; } }
结果:
请输入表达式:a+b+c-d
请输入a的值:5
请输入b的值:4
请输入c的值:3
请输入d的值:2
最终运算结果:a+b+c-d=10
8.4、应用
由于 JDK、Spring、Mybatis 中很少有备忘录模式,所以该设计模式不做典型应用源码分析。
9、状态模式
9.1、介绍
在软件开发过程中,应用程序中的部分对象可能会根据不同的情况做出不同的行为,我们把这种对象称为有状态的对象,而把影响对象行为的一个或多个动态变化的属性称为状态。当有状态的对象与外部事件产生互动时,其内部状态就会发生改变,从而使其行为也发生改变。如人都有高兴和伤心的时候,不同的情绪有不同的行为,当然外界也会影响其情绪变化。
对这种有状态的对象编程,传统的解决方案是:将这些所有可能发生的情况全都考虑到,然后使用 if-else 或 switch-case 语句来做状态判断,再进行不同情况的处理。但是显然这种做法对复杂的状态判断存在天然弊端,条件判断语句会过于臃肿,可读性差,且不具备扩展性,维护难度也大。且增加新的状态时要添加新的 if-else 语句,这违背了“开闭原则”,不利于程序的扩展。
以上问题如果采用“状态模式”就能很好地得到解决。在状态模式(State Pattern)中,类的行为是基于它的状态改变的。这种类型的设计模式属于行为型模式。在状态模式中,我们创建表示各种状态的对象和一个行为随着状态对象改变而改变的 context 对象。
9.2、关系依赖图
9.3、代码实现
环境类
Context
public class Context { private State state; public Context() { this.state = new ConcreteStateA(); } //设置状态 public void setState(State state) { this.state = state; } //读取状态 public State getState() { return (state); } //请求处理 public void Handle() { state.Handle(this); } }
抽象状态类
State
public abstract class State { public abstract void Handle(Context context); }
具体状态类
ConcreteStateA
public class ConcreteStateA extends State { public void Handle(Context context) { System.out.println("当前状态是 A."); context.setState(new ConcreteStateB()); } }
ConcreteStateB
public class ConcreteStateB extends State { public void Handle(Context context) { System.out.println("当前状态是 B."); context.setState(new ConcreteStateA()); } }
测试类
Client
public class Client { public static void main(String[] args) { Context context = new Context(); context.Handle(); context.Handle(); context.Handle(); context.Handle(); } }
结果:
当前状态是 A.
当前状态是 B.
当前状态是 A.
当前状态是 B.
9.4、应用
多线程存在 5 种状态,分别为新建状态、就绪状态、运行状态、阻塞状态和死亡状态,各个状态当遇到相关方法调用或事件触发时会转换到其它状态,其状态转换规律如下图所示:
现在先定义一个抽象状态类(TheadState),然后为上图所示的每个状态设计一个具体状态类,它们是新建状态(New)、就绪状态(Runnable )、运行状态(Running)、阻塞状态(Blocked)和死亡状态(Dead),每个状态中有触发它们转变状态的方法,环境类(ThreadContext)中先生成一个初始状态(New),并提供相关触发方法,下线程状态转换程序的结构如下图所示。
环境类
ThreadContext
public class ThreadContext { private ThreadState state; ThreadContext() { state = new New(); } public void setState(ThreadState state) { this.state = state; } public ThreadState getState() { return state; } public void start() { ((New) state).start(this); } public void getCPU() { ((Runnable) state).getCPU(this); } public void suspend() { ((Running) state).suspend(this); } public void stop() { ((Running) state).stop(this); } public void resume() { ((Blocked) state).resume(this); } }
抽象状态类
ThreadState
public abstract class ThreadState { protected String stateName; }
具体状态类
New
public class New extends ThreadState { public New() { stateName = "新建状态"; System.out.println("当前线程处于:新建状态."); } public void start(ThreadContext hj) { System.out.print("调用start()方法-->"); if (stateName.equals("新建状态")) { hj.setState(new Runnable()); } else { System.out.println("当前线程不是新建状态,不能调用start()方法."); } } }
Runnable
public class Runnable extends ThreadState { public Runnable() { stateName = "就绪状态"; System.out.println("当前线程处于:就绪状态."); } public void getCPU(ThreadContext hj) { System.out.print("获得CPU时间-->"); if (stateName.equals("就绪状态")) { hj.setState(new Running()); } else { System.out.println("当前线程不是就绪状态,不能获取CPU."); } } }
Running
public class Running extends ThreadState { public Running() { stateName = "运行状态"; System.out.println("当前线程处于:运行状态."); } public void suspend(ThreadContext hj) { System.out.print("调用suspend()方法-->"); if (stateName.equals("运行状态")) { hj.setState(new Blocked()); } else { System.out.println("当前线程不是运行状态,不能调用suspend()方法."); } } public void stop(ThreadContext hj) { System.out.print("调用stop()方法-->"); if (stateName.equals("运行状态")) { hj.setState(new Dead()); } else { System.out.println("当前线程不是运行状态,不能调用stop()方法."); } } }
Blocked
public class Blocked extends ThreadState { public Blocked() { stateName = "阻塞状态"; System.out.println("当前线程处于:阻塞状态."); } public void resume(ThreadContext hj) { System.out.print("调用resume()方法-->"); if (stateName.equals("阻塞状态")) { hj.setState(new Runnable()); } else { System.out.println("当前线程不是阻塞状态,不能调用resume()方法."); } } }
Dead
public class Dead extends ThreadState { public Dead() { stateName = "死亡状态"; System.out.println("当前线程处于:死亡状态."); } }
测试类
Client
public class Client { public static void main(String[] args) { ThreadContext context = new ThreadContext(); context.start(); context.getCPU(); context.suspend(); context.resume(); context.getCPU(); context.stop(); } }
结果:
当前线程处于:新建状态.
调用start()方法-->当前线程处于:就绪状态.
获得CPU时间-->当前线程处于:运行状态.
调用suspend()方法-->当前线程处于:阻塞状态.
调用resume()方法-->当前线程处于:就绪状态.
获得CPU时间-->当前线程处于:运行状态.
调用stop()方法-->当前线程处于:死亡状态.
10、责任链模式
10.1、介绍
在现实生活中,一个事件需要经过多个对象处理是很常见的场景。例如,采购审批流程、请假流程等。公司员工请假,可批假的领导有部门负责人、副总经理、总经理等,但每个领导能批准的天数不同,员工必须根据需要请假的天数去找不同的领导签名,也就是说员工必须记住每个领导的姓名、电话和地址等信息,这无疑增加了难度。
责任链模式(Chain of Responsibility Pattern)为请求创建了一个接收者对象的链。这种模式给予请求的类型,对请求的发送者和接收者进行解耦。这种类型的设计模式属于行为型模式。在这种模式中,通常每个接收者都包含对另一个接收者的引用。如果一个对象不能处理该请求,那么它会把相同的请求传给下一个接收者,依此类推。
10.2、关系依赖图
10.3、代码实现
采购员采购教学器材,请设计程序完成采购审批项目。
如果金额小于等于 5000,由主任审批 (0<=x<=5000)
如果金额小于等于 10000,由院长审批(5000<x<=10000)
如果金额小于等于 30000,由副校长审批(10000<x<=30000)
如果金额超过 30000 以上,有校长审批(30000<x)
抽象处理者
Approver
public abstract class Approver { protected Approver approver; protected String name; public Approver(String name) { this.name = name; } public Approver getNext() { return approver; } public void setNext(Approver approver) { this.approver = approver; } public abstract void handleRequest(PurchaseRequest purchaseRequest); }
具体处理者
DepartmentApprover
//主任审批 public class DepartmentApprover extends Approver { public DepartmentApprover(String name) { super(name); } @Override public void handleRequest(PurchaseRequest purchaseRequest) { if (purchaseRequest.getPrice() < 0 && purchaseRequest.getPrice() <= 5000) { System.out.println("本次采购请求被 " + this.name + " 处理"); } else { if (this.approver != null) { approver.handleRequest(purchaseRequest); } } } }
DeanApprover
//院长审批 public class DeanApprover extends Approver { public DeanApprover(String name) { super(name); } @Override public void handleRequest(PurchaseRequest purchaseRequest) { if (purchaseRequest.getPrice() < 5000 && purchaseRequest.getPrice() <= 10000) { System.out.println("本次采购请求被 " + this.name + " 处理"); } else { if (this.approver != null) { approver.handleRequest(purchaseRequest); } } } }
ViceSchoolMasterApprover
//副校长审批 public class ViceSchoolMasterApprover extends Approver { public ViceSchoolMasterApprover(String name) { super(name); } @Override public void handleRequest(PurchaseRequest purchaseRequest) { if (purchaseRequest.getPrice() < 10000 && purchaseRequest.getPrice() <= 30000) { System.out.println("本次采购请求被 " + this.name + " 处理"); } else { if (this.approver != null) { approver.handleRequest(purchaseRequest); } } } }
SchoolMasterApprover
//校长审批 public class SchoolMasterApprover extends Approver { public SchoolMasterApprover(String name) { super(name); } @Override public void handleRequest(PurchaseRequest purchaseRequest) { if (purchaseRequest.getPrice() > 30000) { System.out.println("本次采购请求被 " + this.name + " 处理"); } else { if (this.approver != null) { approver.handleRequest(purchaseRequest); } } } }
最终测试类
Client
public class Client { public static void main(String[] args) { //创建一个请求 PurchaseRequest purchaseRequest = new PurchaseRequest(31000); //创建审批人员 DepartmentApprover departmentApprover = new DepartmentApprover("张主任"); DeanApprover deanApprover = new DeanApprover("李院长"); ViceSchoolMasterApprover viceSchoolMasterApprover = new ViceSchoolMasterApprover("王副校"); SchoolMasterApprover schoolMasterApprover = new SchoolMasterApprover("赵校长"); //设置审批链 departmentApprover.setNext(deanApprover); deanApprover.setNext(viceSchoolMasterApprover); viceSchoolMasterApprover.setNext(schoolMasterApprover); //开始处理请求 departmentApprover.handleRequest(purchaseRequest); } }
本次采购请求被 赵校长 处理
10.4、应用
在Java中,Servlet容器是责任链模式的经典应用。如在tomcat的包中,带有Filter、FilterChain结尾的类都是使用的是责任链模式。
javax.servlet.Filter类使用的就是责任链模式来实现对请求的过滤,执行任一请求到资源过滤任务。源码如下:
public interface Filter { public default void init(FilterConfig filterConfig) throws ServletException {} public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException; public default void destroy() {} }
javax.Servlet.FilterChain类也是通过责任链模式来进行过滤链中过滤器的调用。源码如下:
public interface FilterChain { public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException; }
FilterChain是Servlet容器提供给开发人员的对象,它提供了对资源的已过滤请求的调用链的视图。过滤器使用FilterChain调用链中的下一个过滤器,或者调用过滤器是链中的最后一个过滤器。
11、策略模式
11.1、介绍
策略模式的核心思想是对算法进行封装,委派给不同对象来管理。这样,我们就可以定义一系列算法,将每个算法封装到具有公共接口的一系列具体策略类中,从而使它们可以灵活替换,并让算法可以在不影响到客户端的情况下发生变化。同时,策略模式仅仅封装算法(包括添加、删除),但其并不决定在何时使用何种算法,算法的选择由客户端来决定。
比如,我们旅游时可以选择的出行策略有很多种:自行车、汽车、火车、飞机,每种出行策略都有各自的使用方法,只要能到目的地,我们可以随意更换各种策略。再比如我们去逛商场,商场会有很多促销活动:满减、返利等,这些促销方式本质上都是一些算法,而算法本身也是一种策略,随时都可以互相替换,针对同一件商品,今天满500减50、明天满300返100购物券,这些策略之间同样可以互换。
11.2、关系依赖图
11.3、代码实现
除了双11购物狂欢节,每年都会打造很多其他的促销活动。试想一下,如果每种大促活动都使用一种促销模式,未免太过枯燥,于用户、商家、平台而言都不友好。因此,为了提升用户购买体验、突出商家营销特点,需要面向不同大促活动使用不同的策略进行促销。这里以促销策略为例,简单分析策略模式如何使用。
定义抽象策略角色(Strategy):所有促销活动的共同接口
public interface Strategy { void show(); }
定义具体策略角色(Concrete Strategy):不同类型的具体促销策略
618大促活动 A
public class ConcreteStrategyA implements Strategy { @Override public void show() { System.out.println("618大促"); } }
99大促活动 B
public class ConcreteStrategyB implements Strategy { @Override public void show() { System.out.println("99大促"); } }
双11大促活动 C
public class ConcreteStrategyC implements Strategy { @Override public void show() { System.out.println("双11大促"); } }
定义环境角色(Context):把促销活动推送给用户,这里可理解为淘宝平台
public class Context{ //持有抽象策略的引用 private Strategy myStrategy; //生成构造方法,让平台根据传入的参数(type)选择促销活动 public Context(Strategy strategyType) { this.myStrategy = strategyType; } //向用户展示促销活动 public void taoPlatformShow(String time) { System.out.println(time + "的促销策略是:"); myStrategy.show(); } }
客户端调用,需事先明确所有每种策略类如何使用
public class StrategyPattern{ public static void main(String[] args){ Context_TaoPlatform context; String time1 = "9月"; Strategy strategyB = new ConcreteStrategyB(); context = new Context(strategyB); context.taoPlatformShow(time1); String time2 = "11月"; Strategy strategyC = new ConcreteStrategyC(); context = new Context(strategyC); context.taoPlatformShow(time2); String time3 = "6月"; Strategy strategyA = new ConcreteStrategyA(); context = new Context(strategyA); context.taoPlatformShow(time3); } }
结果:
9月的促销策略是:
99大促
11月的促销策略是:
双11大促
6月的促销策略是:
618大促
11.4、应用
这里以Comparator比较器为例,通过分析其源码实现来深入理策略模式。
在JDK中,我们调用数组工具类Arrays的一个排序方法sort()时,可以使用默认的排序规则(升序),也可以自定义一种排序的规则,即自定义实现升序或降序的排序。源码如下:
public class Arrays{ public static <T> void sort(T[] a, Comparator<? super T> c) { if (c == null) { //若没有传入Comparator接口的实现类对象,调用默认的升序排序方法 sort(a); } else { if (LegacyMergeSort.userRequested) //jdk5及之前的传统归并排序,新版本中LegacyMergeSort.userRequested默认false legacyMergeSort(a, c); else //改进后的归并排序 TimSort.sort(a, 0, a.length, c, null, 0, 0); } } }
此时我们需要传入两个参数:一个是待排序的数组,另一个则是Comparator接口的实现类对象。其中,Comparator接口是一种函数式接口,该接口中定义了一个抽象方法int compare(T o1, T o2),用于定义具体的排序规则。这里Comparator接口就是策略模式中的抽象策略接口,它定义了一个排序算法,而具体策略(具体的排序算法)将由用户来定义,那么Arrays就是一个环境类,sort() 方法可以传入一个策略c ,让Arrays根据这个策略进行排序任务。
public class demo { public static void main(String[] args) { Integer[] data = {12, 2, 3, 2, 4, 5, 1}; // 实现降序排序 Arrays.sort(data, new Comparator<Integer>() { // 排序策略 降序 public int compare(Integer o1, Integer o2) { return o2 - o1; } }); System.out.println(Arrays.toString(data)); //[12, 5, 4, 3, 2, 2, 1] } }
在上面这个测试类中,我们在调用Arrays.sort()方法时,第二个参数传递的是Comparator接口的子实现类对象。由此可见,Comparator充当的是抽象策略角色,而具体的子实现类充当的是具体策略角色,环境角色类 Arrays应该持有抽象策略的引用来调用。那么,Arrays.sort()方法究竟有没有使用Comparator子实现类中的compare()方法?下面再看看TimSort.sort()方法,源码如下:
class TimSort<T> { static <T> void sort(T[] a, int lo, int hi, Comparator<? super T> c, T[] work, int workBase, int workLen) { assert c != null && a != null && lo >= 0 && lo <= hi && hi <= a.length; int nRemaining = hi - lo; if (nRemaining < 2) return; // Arrays of size 0 and 1 are always sorted // If array is small, do a "mini-TimSort" with no merges if (nRemaining < MIN_MERGE) { int initRunLen = countRunAndMakeAscending(a, lo, hi, c); binarySort(a, lo, hi, lo + initRunLen, c); return; } ... } private static <T> int countRunAndMakeAscending(T[] a, int lo, int hi,Comparator<? super T> c) { assert lo < hi; int runHi = lo + 1; if (runHi == hi) return 1; // Find end of run, and reverse range if descending if (c.compare(a[runHi++], a[lo]) < 0) { // Descending while (runHi < hi && c.compare(a[runHi], a[runHi - 1]) < 0) runHi++; reverseRange(a, lo, runHi); } else { // Ascending while (runHi < hi && c.compare(a[runHi], a[runHi - 1]) >= 0) runHi++; } return runHi - lo; } }
上面的代码最后会执行到countRunAndMakeAscending()方法中,在执行判断语句时调用了compare()方法。那么如果只用了compare()方法,在调用Arrays.sort()方法时只要传具体compare()重写方法的类对象。