Java设计模式

1,561 阅读26分钟

Java设计模式

概述

什么是设计模式

设计模式是代码开发经验的总结,用于提高代码的可复用性、可维护性、可读性、稳健性和安全性。由GOF合作设计,java一共设计了23种设计模式。设计模式的本质是面向对象设计原则的实际运用,是对类的封装、继承、多态以及类的关联关系和组合关系的充分理解。

软件设计七大原则

  • 开闭原则:对扩展开放,对修改关闭,也就是说扩展的代码尽量不影响原来的代码,尽量做到独立。
  • 里氏替换原则:继承必须确保超类所拥有的属性在子类中仍然成立,也就是说子类要在父类的基础上扩展添加新东西,而不是修改父类原有的方法。
  • 依赖倒置原则:面向接口编程,不要面向实现。
  • 单一职责原则:控制类的粒度,将对象解耦,提高内聚。就是说一个对象或一个方法不应该承担太多职责,否则会有很多冗余,粒度太粗。
  • 接口隔离原则:要为每个类建立他们需要的专用接口。
  • 迪米特法则:只和直接相关联的对象通信,而不和其他对象通信。
  • 合成复用原则:尽量先使用组合、聚合这种关联关系来实现,其次才考虑继承关系实现

分类

设计模式一共23种,按照分类分为三大类:

  • 行为型设计模式:责任链模式、命令模式、解释器模式、迭代器模式、中介者模式、备忘录模式、观察者模式、状态模式、策略模式、模板方法模式、访问模式(用于描述类或对象怎样交互和怎样分配职责)
  • 创建型设计模式:抽象工厂模式、工厂模式、建造者模式、单例模式、原型模式(主要用于创建对象)
  • 结构型设计模式:适配器模式、桥接模式、组合模式、装饰模式、外观模式、享元模式、代理模式(用于处理类或对象的组合)

创建型模式

单例模式

确保一个类只有一个实例,并且提供一个全局访问点

饿汉式

类加载时就实例化

public class HungryDemo{
  //私有的构造函数,不能通过构造函数实例化
    private HungryDemo() {
      
    }
    private final static HungryDemo hungryDemo = new HungryDemo();
    public static HungryDemo getInstance() {
        return hungryDemo;
    }
}
懒汉式

调用的时候才初始化

public class LazyDemo{
    //构造函数私有,不能通过构造函数实例化
    private LazyDemo() {
        
    }
    private static LazyDemo lazyDemo = null;
  //非线程安全
    public static LazyDemo getInstance() {
      if (lazyDemo==null) {
        lazyDemo = new LazyDemo();
      }
      return lazyDemo;
    }
}
双重检验锁
public class DoubleCheckDemo{
    private DoubleCheckDemo() {
      
    }
    //voliatile此处是禁止指令重排序的作用
    private volatile static DoubleCheckDemo doubleCheckDemo = null;
    public static DoubleCheckDemo getInstance() {
        if (doubleCheckDemo==null) {
          synchronized(DoubleCheckDemo.class) {
            if (doubleCheckDemo==null) {
              doubleCheckDemo = new DoubleCheckDemo();
            }
          } 
        }
      return doubleCheckDemo;
    }
}

这是线程安全的单例。除了使用synchronized之外,还要加volatile修饰符。因为DCL本身存在一个缺陷就是重排序可能导致多线程获取到一个未初始化的对象。doubleCheckDemo = new DoubleCheckDemo()在JVM中分为三步:(1)为对象分配空间(2)初始化对象(3)将doubleCheckDemo的引用指向第一步中分配的内存地址。如果线程A执行第2步和第3步发生重排序,此时线程B进来后发现doubleCheckDemo!=null成立就直接返回了一个未初始化的对象。volatile可以保证多线程环境下变量的可见性和禁止指令重排序。

静态内部类
public class StaticInnerClassDemo{
​
  private StaticInnerClassDemo() {
  
  }
  
  public static StaticInnerClassDemo getInstance() {
    return InnerClass.staticInnerClassDemo;
  }
  
  public static class InnerClass{
      private static final StaticInnerClassDemo staticInnerClassDemo = new StaticInnerClassDemo();
  }
}
枚举类
public enum EnumDemo {
  INSTANCE;
  public EnumDemo getInstance() {
    return INSTANCE;
  }
}

简单工厂模式

public class 简单工厂模式 {
​
    public static Food makeFood(String name) {
        if (name.equals("noodle")) {
            return new HeNanNoodle();
        } else if (name.equals("chicken")) {
            return new HuangMenChicken();
        } else {
            return null;
        }
    }
    
    public static abstract class Food {
​
    }
​
    public static class HeNanNoodle extends Food{
​
    }
​
    public static class HuangMenChicken extends Food{
​
    }
}

简单说,简单工厂模式就是只有一个工厂类,里面有一个静态方法,根据我们不同的参数,返回不同的派生自同一个父类的实例对象。

工厂模式

简单工厂模式很简单,如果能够满足需要就不需要工厂模式了。之所以引入工厂模式,是因为需要使用两个或两个以上的工厂。

先创建实体类

public abstract class Food {
​
    private String country;
​
    private String name;
​
    public Food(){}
​
    public Food(String country, String name){
        this.country = country;
        this.name = name;
    }
}
public abstract class ChineseFood extends Food{
​
}
public abstract class EnglishFood extends Food{
}
public class HeNanNoodle extends ChineseFood{
​
}
public class HuangMenChicken extends ChineseFood{
​
}
public class EnglishChicken extends EnglishFood{
​
}

然后建立抽象工厂和两个具体工厂

public interface FoodFactory {
    Food makeFood(String name);
}
public class ChineseFoodFactory implements FoodFactory{
​
    @Override
    public Food makeFood(String name) {
        if (name.equals("HeNanNoodle")) {
            return new HeNanNoodle();
        } else if (name.equals("HuangMenChicken")) {
            return new HuangMenChicken();
        } else {
            return null;
        }
    }
}
public class EnglishFoodFactory implements FoodFactory{
​
    @Override
    public Food makeFood(String name) {
        if (name.equals("EnglishChicken")) {
            return new EnglishChicken();
        } else {
            return null;
        }
    }
}

整个测试类:

public class 工厂模式 {
    public static void main(String [] args) {
        Scanner scanner = new Scanner(System.in);
        String country = scanner.nextLine();
        FoodFactory foodFactory = null;
        if ("China".equals(country)) {
            foodFactory = new ChineseFoodFactory();
        } else if ("England".equals(country)) {
            foodFactory = new EnglishFoodFactory();
        } else {
            throw new RuntimeException("参数错误");
        }
        String name = scanner.nextLine();
        Food food = foodFactory.makeFood(name);
        System.out.println(food.getClass());
    }
}

抽象工厂模式

当涉及到产品族时需要引入抽象工厂模式。一个经典例子是造一台电脑,我们先不引入抽象工厂模式,先将CPU和主板等进行抽象,然后CPU由CPUFactory生产,主板由MainBoardFactory生产,然后我们再将CPU和主板搭配组合在一起。我们说的产品族的概念代表了组成某个产品的一系列产品的一系列附件的集合。当涉及到产品族问题时,就需要抽象工厂模式的支持,我们不再定义CPU工厂、主板工厂、显示器工厂了,直接定义电脑工厂,每个电脑工厂负责生产所有设备,这样能保证肯定不存在兼容问题。

public static void main(String[] args) {
    // 第一步就要选定一个“大厂”
    ComputerFactory cf = new AmdFactory();
    // 从这个大厂造 CPU
    CPU cpu = cf.makeCPU();
    // 从这个大厂造主板
    MainBoard board = cf.makeMainBoard();
    // 从这个大厂造硬盘
    HardDisk hardDisk = cf.makeHardDisk();
​
    // 将同一个厂子出来的 CPU、主板、硬盘组装在一起
    Computer result = new Computer(cpu, board, hardDisk);
}
​

抽象工厂的问题就是当我们要加个组件时,就需要修改所有的工厂,给所有工厂都加上制造组件的方法。这点违反了对修改关闭,对扩展开放的开闭原则。

建造者模式

建造者模式其实有很多变种,但是对客户端来说,我们的使用通常都是一个模式。套路就是先new一个Builder,然后可以链式调用一堆设置属性的方法,最后再调用一次build()方法构造需要的对象。

public class User {
​
    private String name;
​
    private String password;
​
    private String nickName;
​
    private Integer age;
​
    private User(String name, String password, String nickName, Integer age) {
        this.name = name;
        this.password = password;
        this.nickName = nickName;
        this.age = age;
    }
​
    public static UserBuilder builder() {
        return new UserBuilder();
    }
​
    public static class UserBuilder{
        //跟User一模一样的属性
        private String name;
​
        private String password;
​
        private String nickName;
​
        private Integer age;
​
        private UserBuilder() {
​
        }
​
        /**
         * 链式调用设置各个属性值,返回this。即UserBuilder
         * @param name
         * @return
         */
        public UserBuilder name(String name) {
            this.name = name;
            return this;
        }
​
        public UserBuilder passWord(String password) {
            this.password = password;
            return this;
        }
​
        public UserBuilder nickName(String nickName) {
            this.nickName = nickName;
            return this;
        }
​
        public UserBuilder age(Integer age) {
            this.age = age;
            return this;
        }
​
        /**
         * 负责将UserBuilder设置好的属性复制到User中
         * @return
         */
        public User build() {
            return new User(name, password, nickName, age);
        }
    }
}
    public static void main(String [] args) {
        User user = User.builder().name("ZhangSan")
                .passWord("pd123")
                .nickName("ZS123")
                .age(20).build();
    }

原型模式

原型模式很简单:有一个原型实例,基于这个原型实例产生新的实例,也就是“克隆”。Object类有一个Clone()方法,它用于生成一个新的对象,当然如果要调用这个方法,必须先实现Cloneable接口,此接口没有定义任何方法,但是不这么做的话,在clone()的时候就会抛出CloneNotSupportedException异常。java的克隆是浅拷贝,克隆出来的对象和原对象的引用指向同一个对象。通常实现深拷贝的方法是先将对象序列化,然后再进行反序列化。

创建型模式总结

总体上比较简单,作用就是为了产生实例对象,因为我们是面向对象的代码,所以第一步就是需要创建一个对象。简单工厂模式最简单;工厂模式在简单工厂模式的基础上增加了选择工厂的难度,需要第一步选择合适的工厂;抽象工厂模式有产品族的概念,如果各个产品是存在兼容性问题的,就要用抽象工厂模式。单例模式为了全局使用同一个对象,一方面是安全,一方面为了节省资源。建造者模式专门应对属性很多的那种类,为了让代码更优美。原型模式用的最少,主要是Object类的clone。

结构型模式

代理模式

代理模式是最常使用的模式之一,用一个代理隐藏具体实现类的实现细节,通常还用于在真实的实现前后添加一部分逻辑。代理就是对客户端隐藏真实实现,由代理负责客户端的所有请求。

静态代理
  public interface UserService {
    void addUser();
    void updateUser();
}
public class UserServiceImpl implements UserService{
    @Override
    public void addUser() {
        System.out.println("添加一个用户");
    }
    @Override
    public void updateUser() {
        System.out.println("更新一个用户");
    }
}

现在我们需要对用户信息进行增加、删除和更新操作的时候加一下日志,用静态代理方式,我们需要创建一个静态代理类,将被代理对象传入,然后创建需要增强的方法。

public class UserStaticProxy {
​
    private UserService userService;
    
    public UserStaticProxy(UserService userService) {
        this.userService=userService;
    }
    public void addUser() {
        userService.addUser();
        System.out.println("打印一条日志");
    }
}  

使用

public class StaticProxyTest {
    public static void main(String [] args) {
        UserService userService = new UserServiceImpl();
        UserStaticProxy staticProxy = new UserStaticProxy(userService);
        staticProxy.addUser();
    }
}

静态代理的缺点:

  • 接口增加方法,代理类需要同步维护
  • 接口越多,需要创建的代理类越多。比如以后我们有了TeacherService,StudentService,就需要创建TeacherStaticProxy等,增加了工作量。
JDK动态代理

动态代理和静态代理本质是一样的,最终程序运行时都需要生成一个代理对象实例。只不过静态代理是采用硬编码的方式在程序运行之前就创建好代理类,而动态代理是运行时动态生成的方式。JDK帮我们实现了动态代理,使用的是newProxyInstance方法。

public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)

该方法中接收三个参数:

  • ClassLoader loader:指定当前目标对象使用的类加载器
  • Class<?>[] interfaces:代理类需要实现的接口列表
  • InvocationHandler h:调用处理程序,将目标对象的方法分派到该调用处理程序。

代码示例:

public class DynamicProxy implements InvocationHandler{
​
    private Object target;//目标对象
​
    public Object bind(Object target) {
        this.target=target;
        return Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),this);
    }
​
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Exception{
        //执行目标对象的方法
        Object result = method.invoke(target,args);
        //实现扩展功能
        System.out.println("打印一下日志");
        return result;
    }
}
​

bind方法简单封账jdk的newProxyInstance(),并返回目标接口对象。invoke()方法负责增强目标对象的方法,实现扩展功能。

测试类:

public class DynamicProxyTest {
​
    public static void main(String [] args) {
        DynamicProxy dynamicProxy = new DynamicProxy();
        UserService userService = (UserService) dynamicProxy.bind(new UserServiceImpl());
        userService.addUser();
        userService.updateUser();
    }
}

可见,动态代理解决了静态代理的两个缺点。

CGLib动态代理

上面两种方式,目标对象都实现了一个接口,如果只是一个普通类,没有实现任何接口,就需要CGLib动态代理,它是在内存中构建一个子类对象从而实现对目标对象功能的扩展。

public class CGLibProxy implements MethodInterceptor {
​
    private Object target;
​
    public Object bind(Object target) {
        this.target=target;
        Enhancer enhancer = new Enhancer();
        //设置父类
        enhancer.setSuperclass(this.target.getClass());
        //设置回调函数
        enhancer.setCallback(this);
        //创建并返回子类对象
        return enhancer.create();
    }
​
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable{
        Object object = methodProxy.invokeSuper(o,objects);
        System.out.println("打印一下日志");
        return object;
    }
}

测试类:

public class CGLibProxyTest {
​
    public static void main(String [] args) {
        CGLibProxy cgLibProxy = new CGLibProxy();
        UserService userService = (UserService) cgLibProxy.bind(new UserServiceImpl());
        userService.addUser();
        userService.updateUser();
    }
}

使用CGLib有两个注意点:

  • 目标对象不能被final修饰,因为被final修饰的对象是不可继承的。
  • 目标对象的方法如果是final/static的,那么该方法是不会被拦截的,即不会执行目标对象额外的业务方法。

适配器模式

将一个类的接口转换成客户希望的另一个接口。Adapter模式使得原本由于接口兼容性不能一起工作的那些类可以在一起工作。

  • 目标接口:客户期待的接口,目标可以是具体的类或抽象类,也可以是接口。
  • 需要适配的类:需要适配的类或适配器类
  • 适配器:通过包装一个需要适配的对象,把原接口转换成目标对象。
默认适配器模式

我们用Appache commons-io 包中的 FileAlterationListener为例,此接口定义了很多方法,用于对文件或文件夹进行监控,一旦发生了对应操作,就会触发相应的方法。

public interface FileAlterationListener {
    void onStart(final FileAlterationObserver observer);
    void onDirectoryCreate(final File directory);
    void onDirectoryChange(final File directory);
    void onDirectoryDelete(final File directory);
    void onFileCreate(final File file);
    void onFileChange(final File file);
    void onFileDelete(final File file);
    void onStop(final FileAlterationObserver observer);
}

此接口一大问题是抽象方法太多了,如果我们要用这个接口就要实现每一个抽象方法,所以我们需要一个适配器,用于实现上面的接口,但是所有方法都是孔方法。

public class FileAlterationListenerAdaptor implements FileAlterationListener {
​
    public void onStart(final FileAlterationObserver observer) {
    }
​
    public void onDirectoryCreate(final File directory) {
    }
​
    public void onDirectoryChange(final File directory) {
    }
​
    public void onDirectoryDelete(final File directory) {
    }
​
    public void onFileCreate(final File file) {
    }
​
    public void onFileChange(final File file) {
    }
​
    public void onFileDelete(final File file) {
    }
​
    public void onStop(final FileAlterationObserver observer) {
    }
}

这时我们定义自己的类,实现我们想实现的方法即可。

public class FileMonitor extends FileAlterationListenerAdaptor {
    public void onFileCreate(final File file) {
        // 文件创建
        doSomething();
    }
​
    public void onFileDelete(final File file) {
        // 文件删除
        doSomething();
    }
}
对象适配器模式

来看一个例子,将鸡适配成鸭。

public interface Duck {
    public void quack(); // 鸭的呱呱叫
      public void fly(); // 飞
}
​
public interface Cock {
    public void gobble(); // 鸡的咕咕叫
      public void fly(); // 飞
}
​
public class WildCock implements Cock {
    public void gobble() {
        System.out.println("咕咕叫");
    }
      public void fly() {
        System.out.println("鸡也会飞哦");
    }
}

鸭接口有fly()和quare()两个方法,鸡Cock如果要冒充鸭,fly方法是现成的,但是鸡不会鸭的呱呱叫,没有quack()方法,这个时候就需要适配了。

// 毫无疑问,首先,这个适配器肯定需要 implements Duck,这样才能当做鸭来用
public class CockAdapter implements Duck {
​
    Cock cock;
    // 构造方法中需要一个鸡的实例,此类就是将这只鸡适配成鸭来用
      public CockAdapter(Cock cock) {
        this.cock = cock;
    }
​
    // 实现鸭的呱呱叫方法
      @Override
      public void quack() {
        // 内部其实是一只鸡的咕咕叫
        cock.gobble();
    }
​
      @Override
      public void fly() {
        cock.fly();
    }
}
​

客户端调用就很简单了

public static void main(String[] args) {
    // 有一只野鸡
      Cock wildCock = new WildCock();
      // 成功将野鸡适配成鸭
      Duck duck = new CockAdapter(wildCock);
      ...
}

image.png

类适配器模式

image.png

适配器模式总结:

  • 类适配器和对象适配的异同:类适配器采用继承,对象适配器采用组合。类适配器属于静态实现,对象适配属于组合的动态实现,对象适配需要多实例化一个对象。
  • 适配器模式和代理模式的异同:目的不一样,代理模式做的是增强原方法的活,适配器做的是适配的活。

桥接模式

桥接模式就是代码抽象和解耦。

我们首先需要一个桥梁,它是一个接口。创建一个产品的属性接口

public interface Brand {
    void info();
}

然后是一系列的实现类

public class Apple implements Brand{
    @Override
    public void info() {
        System.out.println("苹果品牌");
    }
}
​
public class Lenovo implements Brand{
    @Override
    public void info() {
        System.out.println("联想");
    }
}

然后定义一个抽象类(产品),这个抽象类的每个实现类都需要使用Brand

public abstract class Computer {
​
    protected Brand brand;
​
    public Computer() {}
​
    public Computer(Brand brand) {
        this.brand = brand;
    }
    public void info() {
        brand.info();
    }
​
    static class DeskTop extends Computer {
        public DeskTop(Brand brand) {
            super(brand);
        }
​
        @Override
        public void info() {
            super.info();
            System.out.println("台式机");
        }
    }
​
    static class LapTop extends Computer {
        public LapTop(Brand brand) {
            super(brand);
        }
​
        @Override
        public void info() {
            super.info();
            System.out.println("笔记本");
        }
    }
}

客户端调用

public class BridgeTest {
​
    public static void main(String [] args) {
        //苹果笔记本
        Computer computer1 = new Computer.LapTop(new Apple());
        computer1.info();
        //苹果台式机
        Computer computer2 = new Computer.DeskTop(new Apple());
        computer2.info();
        //联想笔记本
        Computer computer3 = new Computer.LapTop(new Lenovo());
        computer3.info();
        //联想台式机
        Computer computer4 = new Computer.DeskTop(new Lenovo());
        computer4.info();
    }
}

桥接模式好处与缺点

  • 好处:解耦,方便扩展,在两个变化维度中任意扩展一个维度,都不需要修改原有系统,符合开闭原则(对扩展开放,对修改关闭)
  • 缺点:由于聚合关系建立在抽象层(比如品牌与产品),要求开发者识别系统中两个独立变化的维度,谁是谁的聚合,因此使用范围有一定局限性。

装饰模式

装饰者模式动态的将责任附加到对象上。若要扩展功能,装饰者提供了比继承更有弹性的替代方法(Head First设计模式中说到)。也就是说,装饰者模式是对原有功能增加一些新的功能,单独使用继承或组合会产生大量的子类,装饰者模式可以动态的同时增加多个功能行为,同时避免产生大量的子类。

  • 装饰者和被装饰者继承同一个基类,因为装饰者必须能够取代被装饰者,这里利用继承达到类型匹配,而不是用继承获取行为。
  • 装饰者拥有一个与被装饰者相同的基类类型属性。
  • 可以用一个或多个装饰者类包装同一个对象。
  • 装饰者可以在多委托被装饰者的行为之前或行为之后加上自己的行为

首先,我们有需要被装饰的基类和子类

public abstract class Person {
    abstract String getDescription();//描述装备信息
    abstract void attack(); //攻击行为
}
​
public class Alice extends Person {
​
    @Override
    String getDescription() {
        return "Alice: ";
    }
​
    @Override
    void attack() {
        System.out.print("Alice attacking with: ");
    }
}
​
public class Bob extends Person{
​
    @Override
    String getDescription() {
        return "Bob: ";
    }
​
    @Override
    void attack() {
        System.out.print("Bob attacking with: ");
    }
}

然后我们需要写出装饰者基类,需要继承被装饰基类,同时拥有装饰基类作为自己的一个属性

/**
 * 装饰者基类,需要继承Person类,其他装饰类继承此类,此类拥有Person类作为一个属性,这样就能对具体的Person的子类进行额外的封装,加上额外的功能,以达到目的
 **/
public abstract class WeaponDecorator extends Person{
​
    protected Person person;
​
    public WeaponDecorator(){}
​
    public WeaponDecorator(Person person) {
        this.person = person;
    }
​
    @Override
    String getDescription() {
        return person.getDescription() + "[no weapons!] ";
    }
​
    @Override
    void attack() {
        person.attack();
        System.out.println("[bare arms]");
    }
}

然后实现装饰者子类,对被装饰的子类进行功能的增强

public class Gun extends WeaponDecorator{
​
    public Gun(Person person) {
        super(person);
    }
​
    @Override
    String getDescription() {
        return person.getDescription()+"[gun]";
    }
​
    @Override
    void attack() {
        person.attack();
        System.out.println("[gun]");
    }
}
public class Knife extends WeaponDecorator{
​
    public Knife(Person person) {
        super(person);
    }
​
    @Override
    String getDescription() {
        return person.getDescription()+"[knife]";
    }
​
    @Override
    void attack() {
        person.attack();
        System.out.println("[knife]");
    }
}

客户端调用

public class DecoratorTest {
​
    public static void main(String [] args) {
        Person alice = new Alice();
        Person bob = new Bob();
​
        //Alice装备了一把刀
        alice = new Knife(alice);
        System.out.println(alice.getDescription());
​
        //Bob装备了一支枪
        bob = new Gun(bob);
        System.out.println(bob.getDescription());
​
        //Alice用现有装备攻击了Bob
        alice.attack();
        //Bob用现有装备反击
        bob.attack();
    }
}
/*
输出结果
Alice: [knife]
Bob: [gun]
Alice attacking with: [knife]
Bob attacking with: [gun]
*/

外观模式

外观模式又叫门面模式,为子系统中提供一个统一的接口。就是将多个相似的功能封装在一个外观类中。

首先我们定义一个接口

public interface Shape {
   void draw();
}

定义几个实现类

public class Circle implements Shape {
​
   @Override
   public void draw() {
      System.out.println("Circle::draw()");
   }
}
​
public class Rectangle implements Shape {
​
   @Override
   public void draw() {
      System.out.println("Rectangle::draw()");
   }
}
​

我们用门面模式让客户端调用更友好

首先定义一个门面

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();
   }
}

然后客户端调用就变成门面类的调用

public static void main(String[] args) {
  ShapeMaker shapeMaker = new ShapeMaker();
​
  // 客户端调用现在更加清晰了
  shapeMaker.drawCircle();
  shapeMaker.drawRectangle();
  shapeMaker.drawSquare();        
}

门面模式的优点很明显,客户端不再需要关注实例化应该使用哪个实现类,直接调用门面提供的方法就可以了。

组合模式

组合模式用于表示具有层次结构的数据,使得我们对单个对象和组合对象的访问具有一致性。例如:每个员工都有姓名、部门、薪水这些属性,同时还有下属员工集合,而下属员工和自己的结构是一样的,也有姓名、部门这些属性,同时也有他们的下属员工集合。

public class Employee {
   private String name;
   private String dept;
   private int salary;
   private List<Employee> subordinates; // 下属
​
   public Employee(String name,String dept, int sal) {
      this.name = name;
      this.dept = dept;
      this.salary = sal;
      subordinates = new ArrayList<Employee>();
   }
​
   public void add(Employee e) {
      subordinates.add(e);
   }
​
   public void remove(Employee e) {
      subordinates.remove(e);
   }
​
   public List<Employee> getSubordinates(){
     return subordinates;
   }
​
   public String toString(){
      return ("Employee :[ Name : " + name + ", dept : " + dept + ", salary :" + salary+" ]");
   }   
}

通常,这种类需要定义add(node)、remove(node)和getChildren()这些方法。

享元模式

享元分开来说就是共享元器件,也就是复用已经生成的对象,这种做法当然也就是轻量级的了。复用对象最简单的方式是用一个HashMap来存放每次新生成的对象,每次需要一个对象的时候先到HashMap中看看有没有,如果没有再生成新的对象,然后将这个对象放入HashMap中。

结构型模式总结

我们依次学习了代理模式、适配器模式、桥梁模式、装饰模式、门面模式、组合模式和享元模式。代理模式是做方法增强的,适配器模式是把一个接口包装成另一个接口适配的,桥梁模式做到了很好的解耦,装饰模式适用于装饰类或者增强类的场景,门面模式的优点是客户端不需要关心实例化过程,只需要使用门面类的方法即可,组合模式用于描述具有层次结构的数据,享元模式是为了缓存已经创建的对象提高性能。

行为型模式

行为型模式关注的是各个类之间的相互作用,将职责划分清楚,使得代码更加清晰。

策略模式

针对一组算法,将每个算法封装到具有共同接口的独立的类中,从而使得他们可以相互替换。其实就是对算法的包装,把使用算法的责任和算法本身分割开委派给不同的对象管理。策略模式通常是把很多算法包装到一系列的策略类里面,我们在使用的时候根据类型和需要创建具体的策略类,然后执行业务逻辑。

策略模式有三个角色:

  • Context:上下文角色,持有一个Strategy的引用
  • Strategy:抽象策略角色,这是一个抽象角色,通常是一个接口或抽象类。此角色给出所有具体策略所需要实现的公共接口。
  • ConcreteStrategy:具体策略角色,包装了每个策略具体的算法。
//抽象处理类
public abstract class AbstractSolver {
​
    abstract void solve();
​
}
//具体处理类
public class OrderChangeWarehouseSolver extends AbstractSolver{
​
    @Override
    void solve() {
        System.out.println("订单执行转仓逻辑");
    }
}
​
//具体处理类
public class OrderAddGoodsSolver extends AbstractSolver {
​
    @Override
    void solve() {
        System.out.println("订单执行添加商品逻辑");
    }
}
//上下文环境,保存策略和策略对应的类型
public class SolverChooser {
​
    private static Map<String, AbstractSolver> map = new ConcurrentHashMap<>();
​
    public static AbstractSolver getSolver(String type) {
        return map.get(type);
    }
​
  
    public static void registerSolver(String type, AbstractSolver solver) {
        map.put(type, solver);
    }
}
public class StrategyTest {
​
    public static void main(String [] args) {
        //模拟初始化
        AbstractSolver orderChangeWarehouseSolver = new OrderChangeWarehouseSolver();
        AbstractSolver orderAddGoodsSolver = new OrderAddGoodsSolver();
        SolverChooser.registerSolver("CHANGE_WAREHOUSE", orderChangeWarehouseSolver);
        SolverChooser.registerSolver("ADD_GOODS", orderAddGoodsSolver);
​
        //模拟订单处理
        String type = "CHANGE_WAREHOUSE";
        AbstractSolver solver = SolverChooser.getSolver(type);
        solver.solve();
        type = "ADD_GOODS";
        solver = SolverChooser.getSolver(type);
        solver.solve();
    }
}
/*
输出结果:
订单执行转仓逻辑
订单执行添加商品逻辑
*/

观察者模式

观察者模式就是在对象之间存在一对多的依赖关系,当主对象的状态发生改变时,依赖它的所有对象都能接收到改变的消息并作出处理。比如微信公众号和订阅号,消息队列的生产消费模型。观察者模式的实质就是发布订阅模式,发布者发布消息,订阅者订阅消息,订阅了就能收到消息,没订阅就收不到消息。

观察者模式有四个角色:

  • 抽象被观察者:就是一个抽象的主题,它将所有观察者放到一个集合里,这个主题定义了添加观察者,移除观察者以及通知观察者等方法。
  • 抽象观察者:定义了一个接收通知的接口
  • 具体被观察者:对应一个具体的主题,在自身主题发生变化时,通知集合中的所有观察者。
  • 具体观察者:实现接口通知的接口。
/**
 * 抽象观察者接口,定义接收通知方法
 */
public interface Observer {
​
    void updateMessage(String message);
}
/**
 * 抽象被观察者,定义添加,移除和通知观察者的接口
 */
public interface Observable {
​
    public void registerObserver(Observer observer);
​
    public void removeObserver(Observer observer);
​
    public void notice(String message);
}
/**
* 具体被观察者,微信公众号,实现被观察者接口
 **/
public class WechatServer implements Observable{
​
    private List<Observer> observers;
​
    private String message;
​
    public WechatServer() {
        observers = new ArrayList<>();
    }
​
    @Override
    public void registerObserver(Observer observer) {
        if (observers == null) {
            observers = new ArrayList<>();
        }
        observers.add(observer);
     }
​
    @Override
    public void removeObserver(Observer observer) {
        if (observers == null || observers.isEmpty()) {
            return;
        }
        observers.remove(observer);
    }
​
    /**
     * 通知所有注册了的观察者
     */
    @Override
    public void notice(String message) {
        if (observers == null) {
            return;
        }
        if (StringUtils.isBlank(message)) {
            throw new IllegalArgumentException("不能发布空消息");
        }
        this.message = message;
        System.out.println("微信公众号发布了消息:"+message);
        for (Observer observer : observers) {
            observer.updateMessage(message);
        }
    }
}
//具体观察者,微信公众号关注用户
public class WechatUser implements Observer{
​
    private String name;
​
    public WechatUser(String name) {
        this.name = name;
    }
​
    @Override
    public void updateMessage(String message) {
        System.out.println(String.format("用户:%s收到了一条消息:%s", name, message));
    }
}

我们看下客户端调用

public class ObserverTest {
​
    public static void main(String [] args) {
        //定义被观察者
        Observable observable = new WechatServer();
​
        //定义三个观察者
        Observer user1 = new WechatUser("乔峰");
        Observer user2 = new WechatUser("杨过");
        Observer user3 = new WechatUser("令狐冲");
        observable.registerObserver(user1);
        observable.registerObserver(user2);
        observable.registerObserver(user3);
​
        //发布消息
        observable.notice("独孤九剑是最厉害的武功绝学");
        //乔峰和杨过取消了订阅
        observable.removeObserver(user1);
        observable.removeObserver(user2);
        observable.notice("上面的消息发错了,我撤回");
    }
}
/*
输出结果:
微信公众号发布了消息:独孤九剑是最厉害的武功绝学
用户:乔峰收到了一条消息:独孤九剑是最厉害的武功绝学
用户:杨过收到了一条消息:独孤九剑是最厉害的武功绝学
用户:令狐冲收到了一条消息:独孤九剑是最厉害的武功绝学
微信公众号发布了消息:上面的消息发错了,我撤回
用户:令狐冲收到了一条消息:上面的消息发错了,我撤回
*/

责任链模式

责任链通常需要建立一个单向链表,然后调用方只需要调用头部节点就可以了,后面会自动流转下去,比如流程审批就是一个很好的例子,只要终端用户提交申请,根据申请的内容,自动创建一条责任链,然后就可以开始流转了。

责任链模式包含以下几个角色:

  • Handler:抽象处理者,定义一个处理请求的接口,如果需要,接口可以定义出一个方法用来设定和返回对下家的引用。
  • ConcreteHandler:具体处理者,具体处理者收到请求后,可以选择将请求处理掉返回,或者将请求传递到下一级,由于具体处理者持有对下家的引用,因此如果需要,具体处理者可以访问下家。
  • Client:客户类。

举个例子:请假一天,组长就能处理。请假1-3天,项目经理就能处理。请假3-7天,需要老板处理。请假超过7天不能处理。

//抽象处理类
public abstract class AbstractHandler {
​
    //下一个处理者
    private AbstractHandler next;
​
    //能处理的请假天数上限
    private int handlerDayUp;
​
    public AbstractHandler() {}
​
    public AbstractHandler(int handlerDayUp) {
        this.handlerDayUp = handlerDayUp;
    }
​
    public void setNext(AbstractHandler next) {
        this.next = next;
    }
​
    public final String handleLeave(int leaveDay) {
        if (this.handlerDayUp >= leaveDay) {
            //如果请假天数在处理范围内,就处理了
            return this.handle();
        } else {
            //有下一个处理者交给下一个处理者处理,没有下一个处理者就返回错误
            if (Objects.nonNull(next)) {
                return next.handleLeave(leaveDay);
            } else {
                return "你的请求无法处理";
            }
        }
    }
    
    //具体的处理者处理请假
    abstract String handle();
}

下面是具体的处理者

//项目组长处理类
public class PLHandler extends AbstractHandler{
​
    public PLHandler(int handlerDayUp) {
        super(handlerDayUp);
    }
​
    @Override
    String handle() {
        return "你的请假被项目组长处理了";
    }
}
//项目经理处理类
public class PMHandler extends AbstractHandler{
​
    public PMHandler(int handlerDayUp) {
        super(handlerDayUp);
    }
​
    @Override
    String handle() {
        return "你的请求被项目经理处理了";
    }
}
//CEO处理类
public class CEOHandler extends AbstractHandler{
​
    public CEOHandler(int handlerDayUp) {
        super(handlerDayUp);
    }
​
    @Override
    String handle() {
        return "你的请求被CEO处理了";
    }
}

看下客户端调用

//业务使用
public class HandlerTest {
​
    public static void main(String [] args) {
        AbstractHandler plHandler = new PLHandler(1);
        AbstractHandler pmHandler = new PMHandler(3);
        AbstractHandler ceoHandler = new CEOHandler(7);
        plHandler.setNext(pmHandler);
        pmHandler.setNext(ceoHandler);
        int leaveDay = 1;
        System.out.println("请假1天,"+plHandler.handleLeave(leaveDay));
        leaveDay = 3;
        System.out.println("请假3天,"+plHandler.handleLeave(leaveDay));
        leaveDay = 6;
        System.out.println("请假6天,"+plHandler.handleLeave(leaveDay));
        leaveDay = 8;
        System.out.println("请假8天,"+plHandler.handleLeave(leaveDay));
    }
}
/*
输出结果:
请假1天,你的请假被项目组长处理了
请假3天,你的请求被项目经理处理了
请假6天,你的请求被CEO处理了
请假8天,你的请求无法处理
*/

模板方法模式

定义一个算法的框架,将一些步骤延迟到子类中实现,使得子类可以不改变一个算法的执行步骤可以重新定义算法的主要实现逻辑。模板方法主要体现在继承中,有三个方法

public abstract class AbstractTemplate {
    // 这就是模板方法
      public void templateMethod(){
        init();
        apply(); // 这个是重点
        end(); // 可以作为钩子方法
    }
    protected void init() {
        System.out.println("init 抽象层已经实现,子类也可以选择覆写");
    }
      // 留给子类实现
    protected abstract void apply();
    protected void end() {
    }
}

模板方法中调用了3个方法,其中init()是具体方法,在模板方法中是固定的,apply()是抽象方法,子类必须实现它,其实模板方法中有几个抽象方法完全是自由的,我们可以将三个方法都定义成抽象方法,让子类来实现。换句话说,模板方法只定义第一步,第二步和第三步应该做什么,怎么做由子类来实现。

public class ConcreteTemplate extends AbstractTemplate {
    public void apply() {
        System.out.println("子类实现抽象方法 apply");
    }
      public void end() {
        System.out.println("我们可以把 method3 当做钩子方法来使用,需要的时候覆写就可以了");
    }
}

状态模式

当一个对象内在状态发生改变时允许其改变行为,这个对象看起来像改变了其类。状态模式建议为对象的所有可能状态创建新的类,并将所有只存在于特定状态的行为提取到这些类中。核心是封装,状态的变更引起了行为的变更,从外部看起来好像这个对象对应的类发生了改变一样。

  • State:抽象状态角色,负责对象的状态定义,并且封装环境角色以实现状态切换。
  • ConcreteState:具体状态角色,每一个具体状态必须完成两个职责:本状态的行为管理以及趋向状态管理,简单说就是本状态下要做的事以及本状态如何过渡到其他状态。
  • Context:环境角色,定义客户端需要的接口,并且负责状态的切换。

例如:一个电梯,有开启,关闭,运行,停止四种状态,并且相互之间的状态可以切换。

先定义抽象状态角色

//抽象电梯
public abstract class LiftState {
​
    protected Context context;
​
    public void setContext(Context context) {
        this.context = context;
    }
​
    public abstract void open();
    public abstract void close();
    public abstract void run();
    public abstract void stop();
}

然后定义四个具体状态

//开启状态类
public class OpenningState extends LiftState{
​
    @Override
    public void open() {
        System.out.println("电梯门开启...");
    }
​
    /**
     * 电梯门开了再关上
     */
    @Override
    public void close() {
        //状态修改
        super.context.setLiftState(Context.CLOSING_STATE);
        //动作委托给CloseState执行
        super.context.getLiftState().close();
    }
​
    @Override
    public void run() {
​
    }
​
    @Override
    public void stop() {
​
    }
}
//关门状态类
public class ClosingState extends LiftState{
​
    /**
     * 电梯门关了再打开
     */
    @Override
    public void open() {
        super.context.setLiftState(Context.OPENNING_STATE);
        super.context.getLiftState().open();
    }
​
    @Override
    public void close() {
        System.out.println("电梯门关闭...");
    }
​
    /**
     * 电梯门关了再运行,这是正常的
     */
    @Override
    public void run() {
        super.context.setLiftState(Context.RUNNING_STATE);
        super.context.getLiftState().run();
    }
​
    /**
     * 电梯门关了,我就不按楼层,电梯停在原地
     */
    @Override
    public void stop() {
        super.context.setLiftState(Context.STOPPING_STATE);
        super.context.getLiftState().stop();
    }
}
//运行状态类
public class RunningState extends LiftState{
​
​
    @Override
    public void open() {
​
    }
​
    @Override
    public void close() {
​
    }
​
    /**
     * 电梯运行中到达指定楼层停止,是正常的
     */
    @Override
    public void stop() {
        super.context.setLiftState(Context.STOPPING_STATE);
        super.context.getLiftState().stop();
    }
​
    @Override
    public void run() {
        System.out.println("电梯开始运行...");
    }
}
//停止状态类
public class StoppingState extends LiftState{
​
    /**
     * 电梯停止了,开门是正常的
     */
    @Override
    public void open() {
        super.context.setLiftState(Context.OPENNING_STATE);
        super.context.getLiftState().open();
    }
​
    @Override
    public void close() {
​
    }
​
    /**
     * 电梯停止了,可能故障了,不开门继续运行
     */
    @Override
    public void run() {
        super.context.setLiftState(Context.RUNNING_STATE);
        super.context.getLiftState().run();
    }
​
    @Override
    public void stop() {
        System.out.println("电梯停止了...");
    }
}
//上下文类
public class Context {
​
    public final static OpenningState OPENNING_STATE = new OpenningState();
    public final static ClosingState CLOSING_STATE = new ClosingState();
    public final static RunningState RUNNING_STATE = new RunningState();
    public final static StoppingState STOPPING_STATE = new StoppingState();
​
    //定义一个电梯状态
    private LiftState liftState;
​
    public LiftState getLiftState() {
        return liftState;
    }
​
    public void setLiftState(LiftState liftState) {
        this.liftState = liftState;
        //将当前的环境通知到各个实现类中
        this.liftState.setContext(this);
    }
​
    public void open() {
        this.liftState.open();
    }
​
    public void close() {
        this.liftState.close();
    }
​
    public void run() {
        this.liftState.run();
    }
​
    public void stop() {
        this.liftState.stop();
    }
}

最后看下客户端调用

public class StateTest {
​
    public static void main(String [] args) {
        Context context = new Context();
        context.setLiftState(new ClosingState());
        context.open();
        context.close();
        context.run();
        context.stop();
    }
}

行为型模式总结

行为型模式包含策略模式、观察者模式、责任链模式、模板方法模式以及状态模式,还有没讲到的备忘录模式、命令模式、中介者模式、迭代器模式等。