Anwen

别人的看法都是狗屁,你是谁只有自己说了算。若命运不公,就和他斗到底。
——《哪吒之魔童降世》

自己的命自己扛,不要连累别人,不要留有遗憾~
  menu
70 文章
2 评论
2513 浏览
3 当前访客
ღゝ◡╹)ノ❤️

图解设计模式

暨上文介绍了PlantUML之后,谨以此文记录学习《图解设计模式》过程中的心得体会,欢迎留言一起交流想法~

Todo List

  •  Adapt to Design Pattern——适应设计模式

    •  Iterator模式——一个一个遍历
    •  Adaptor模式——加个“适配器”以便复用

  •  Left to subclass——交给子类

    •  Template Method——将具体处理交给子类
    •  Factory Method——将实例的生成交给子类

  •  Generating Instance——生成实例

    •  Singleton模式——只有一个实例
    •  Builder模式——组装复杂的实例
    •  Abstract Factory——将关联零件组装成产品

  •  Consider Individualy——分开考虑

    •  Bridge模式——将类的功能层次结构与实现层次结构分离
    •  Strategy模式——整体地替换算法

  •  Consistency——一致性

    •  Composite模式——容器与内容的一致性
    •  Decorator模式——装饰边框与被装饰物的一致性

  •  Access Data Structure——访问数据结构

    •  Visitor模式——访问数据结构并处理数据
    •  Chain of Responsibility模式——推卸责任

  •  Simplify——简单化

    •  Facade模式——简单窗口
    •  Mediator模式——只有一个仲裁者

  •  Manage Status——管理状态

    •  Observer模式——发送状态变化通知
    •  Memento模式——保存对象状态
    •  State模式——用类表示状态

  •  Avoid wasting

    •  Flyweight模式——共享对象,避免浪费
    •  Proxy模式——只在必要时生成对象

  •  Represent with Class——用类来表现

    •  Command模式——命令也是类
    •  Interpreter模式——语法规则也是类

约定

由于每个人的行文风格不同,因此表情达意的方式也不同,在本文中有着如下约定:

  • 每个设计模式的示例代码中,Client类都表示业务类,即脱离于设计模式之外的,将设计模式应用于业务代码的测试类。
  • 本文每个类图都是使用IDEA插件PlantUML所画,可参考《遇见PantUML~》一文
  • 本文中的类通常指广义上的类,包含抽象类和接口
  • 本书中的所有实例代码完整版已托管到码云:https://gitee.com/zhenganwen/code-demo

Principle

通常,优良的代码设计需要遵循以下原则

Single Responsibility

每个类的存在应该都只是为了满足一个特定的需求,例如Collection类中的方法应该都是为了维护内部元素的结构组织而存在,而应该将如何遍历Collection中元素的职责交给Iterator

单一职责保证专业的事交给专业的人来做,这样每个类发生修改的原因只会有一个(因为每个类的责任只有一个),这样就保证了后续若有需求变更只会导致负责解决该需求的类发生改变,而其他的类均不会受到影响。改变越少,系统发生BUG的几率就会越小。

Open/Closed Principle

系统要对修改关闭,对扩展开放。代码设计要尽量避免对现有代码的修改,因为一旦修改一处就可能导致依赖该类的其他类发生改变,一旦改变,就有可能引入新的潜在的BUG。如果需求变更,代码设计应该通过新增类(多为实现类)的方式来满足新的需求,而客户端代码(依赖该类的其他类)应该无需修改或只需少量修改。

Liskov Substitution Principle

里氏代换原则依托于OOP的多态性。在运行时,客户端依赖的对象可被其他“同源”的(有相同的父类或接口)对象替换而客户端的调用逻辑不受任何影响,这要求我们在声明对象的外观类型(声明类型)时尽量选择高层次一些的类(类的层次结构)。

Interface Segregation Principle

接口隔离原则要求我们将接口方法按照接口功能分开定义在不同的接口中,例如createItertor()应该定义在Iterable中,fly()应该定义在Flyable之中,这样能够减轻实现类的负担,避免实现类被捆绑着要求实现不必要的接口方法。

同时,Java8之后,接口方法如果有通用实现应该定义为default

Dependency Inversion Principle

依赖倒置原则要求我们尽量消除点对点的“强”依赖,而应该使两者依赖抽象,例如Controller依赖AbstractService中的抽象方法进行声明式编程,XxxServiceImpl则对AbstractService的抽象方法进行实现,这样就实现了控制器和具体业务处理类之间的解耦,一旦后续业务变更,我们只需要新增一个XxxServiceImpl2并借助多态就能够轻松实现业务处理的切换。

Adapt to Design Pattern

本章将以Iterator模式和Adaptor模式两个较为简单的作为设计模式的入门,切身体会设计模式存在的价值和软件开发中应遵循的一些原则。

Iterator模式

Before

Iterator中译“迭代器”,起逐个访问集合中的元素的作用。在数据结构中组合元素的方式有很多,如数组、连表、哈希表、二叉树等,根据集合的不同的组织形式,我们遍历访问集合元素的方式也是不一样的。这时我们的业务代码中的访问逻辑(即遍历到当前元素时需要干什么)和遍历逻辑(需要知道集合内部结构)是耦合在一起的,一旦集合换一种组织形式,那么我们的业务代码也需要跟着改变。

例如,如下书架BookShelf通过数组的形式组织了一些书Book

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Book {

    private String name;

}



public class BookShelfWithArr {

    private final Book[] books;
    private final int size;
    private int index;

    public BookShelfWithArr(int size) {
        this.size = size;
        this.books = new Book[size];
        this.index = 0;
    }

    public void put(Book book) {
        if (book == null) {
            throw new IllegalArgumentException("book can't be null");
        }
        if (index == size) {
            throw new RuntimeException("the bookshelf is full");
        }
        books[index++] = book;
    }

    public Book get(int index) {
        if (index < 0 || index >= size) {
            throw new IllegalArgumentException("index should be equal or big than 0 but less than " + size);
        }
        return books[index];
    }

    public int size() {
        return this.size;
    }

}

如果不使用Iterator模式,那么你的业务代码可能如下所示:

public class Client {

    public static void main(String[] args) {
        BookShelfWithArr bookShelf = new BookShelfWithArr(4);
        bookShelf.put(new Book("Java"));
        bookShelf.put(new Book("C"));
        bookShelf.put(new Book("php"));
        bookShelf.put(new Book("python"));

        for (int i = 0; i < bookShelf.size(); i++) {
            System.out.println(bookShelf.get(i));
        }
    }
}

这是我们初学Java集合章节时司空见惯的代码。现在我们来考虑一个问题,假设这个书架被打造成可伸缩的,即可以根据我们所放书籍数量而变大变小,此时我们该怎么办?

BookShelf好说,根据可扩容的特性,我们可以应用ArrayList来代替数组

@Data
public class BookShelf {

    private ArrayList<Book> bookList;

    public BookShelf() {
        this.bookList = new ArrayList<>();
    }

    public BookShelf(ArrayList<Book> bookList) {
        this.bookList = bookList;
    }

}

如此的话,我们的遍历访问逻辑就要做出相应调整

BookShelf bookShelf2 = new BookShelf();
 bookShelf2.getBookList().add(new Book("Java"));
bookShelf2.getBookList().add(new Book("C"));
bookShelf2.getBookList().add(new Book("php"));
bookShelf2.getBookList().add(new Book("python"));
for (int i = 0; i < bookShelf2.getBookList().size(); i++) {
     System.out.println(bookShelf2.getBookList().get(i));
}

这里一旦集合改变组织元素的方式,任何其他存在遍历该集合对象的代码(相对于该集合来说,这些代码称为客户端代码)都需要跟着改变。意味着,客户端代码和集合是紧耦合的,客户端代码不应该关心集合内部是如何组织元素的,而只应该关心遍历该集合拿到元素之后应该做什么。

于是我们用Iterator模式来改造一下

After

首先无论集合如何组织元素,它都应该是可遍历的,因此需要抽象出两个方法:

  • boolean hasNext——集合中是否还有未遍历的元素
  • E next()——取出下一个未遍历的元素
public interface Iterator<E> {

    boolean hasNext();

    E next();
}

集合应该只关注如何组织元素,因此应该将上述遍历逻辑交由他人Iterator来做

public interface Iterable<E> {

    Iterator<E> iterator();
}

通过调用BookShelfiterator方法我们可以获取BookShelf的迭代器,通过使用该迭代器的方法可以实现对BookShelf中元素的遍历访问:

public class BookShelfIterator implements Iterator<Book> {

    private int index;
    private BookShelf bookShelf;

    public BookShelfIterator(BookShelf bookShelf) {
        this.index = 0;
        this.bookShelf = bookShelf;
    }

    @Override
    public boolean hasNext() {
        return index < bookShelf.getBookList().size();
    }

    @Override
    public Book next() {
        if (!hasNext()) {
             throw new RuntimeException("you have arrived the end")
        }
        return bookShelf.getBookList().get(index++);
    }
}

@Data
public class BookShelf implements Iterable<Book> {

    private ArrayList<Book> bookList;

    public BookShelf() {
        this.bookList = new ArrayList<>();
    }

    public BookShelf(ArrayList<Book> bookList) {
        this.bookList = bookList;
    }

    @Override
    public Iterator<Book> iterator() {
        return new BookShelfIterator(this);
    }
    
}

客户端代码:

public class IteratorClient {

    public static void main(String[] args) {
        BookShelf bookShelf = new BookShelf();
        bookShelf.getBookList().add(new Book("Java"));
        bookShelf.getBookList().add(new Book("C"));
        bookShelf.getBookList().add(new Book("php"));
        bookShelf.getBookList().add(new Book("python"));

        Iterator<Book> iterator = bookShelf.iterator();
        while (iterator.hasNext()) {
            System.out.println(iterator.next());
        }
    }
}

如此,如果BookShelf换用Map来组织元素,我们只需新增一个BookShelfMapIterator即可,而客户端代码无需任何改动:

@Data
public class MapBookShelf implements Iterable{

    /**
     * book's name -> book
     */
    private Map<String, Book> bookMap = new HashMap<>();

    @Override
    public Iterator iterator() {
        return new MapBookShelfIterator(this);
    }
}

public class MapBookShelfIterator implements Iterator {

    private final MapBookShelf mapBookShelf;
    private final Object[] keys;
    private int index;

    public MapBookShelfIterator(MapBookShelf mapBookShelf) {
        this.mapBookShelf = mapBookShelf;
        this.keys = mapBookShelf.getBookMap().keySet().toArray();
        this.index = 0;
    }

    @Override
    public boolean hasNext() {
        return index < keys.length;
    }

    @Override
    public Object next() {
        if (!hasNext()) {
            throw new RuntimeException("you have arrived the end");
        }
        return mapBookShelf.getBookMap().get(keys[index++]);
    }

}
MapBookShelf bookShelf = new MapBookShelf();
bookShelf.getBookMap().put("Java", new Book("Java"));
bookShelf.getBookMap().put("C",new Book("C"));
bookShelf.getBookMap().put("PHP",new Book("php"));
bookShelf.getBookMap().put("Python", new Book("python"));

Iterator iterator = bookShelf.iterator();
while (iterator.hasNext()) {
      System.out.println(iterator.next());
}

虽然上述客户端代码也发生了改变,如getBookMapput(这些改变可以通过抽象出一个AbstractBookShelf来避免),但是遍历访问逻辑没变,即首先取得集合的Iterator实例,然后调用接口方法hasNextnext进行遍历,hasNext是对遍历界限的一个控制,其本身不做任何事,仅判断当前位置是否有元素;而next则做两件事:返回当前位置上的元素,将遍历指针后移。

UML & Summarize

完整的Iterator模式可表述如下

image.png

其中IterableIterator对应,ConcreteIterableConcreteIterator对应,客户端仅知道IteratorIterable中的3个方法,可用此实现集合的遍历而不管集合内部组织形式,不同的集合实例则将其对应的ConcreteIterator实现隐藏在了createIterator方法中

Roles

  • Iterator,迭代器,专门负责迭代集合,符合单一职责
    • boolean hasNext()
    • E next()
  • Iterable,集合需要实现该接口,将遍历责任委托给具体的迭代器实例
    • Iterator createIterator()
  • Collection,集合
  • ConcreteIterator,具体的迭代器,和具体的集合实例之间是相互依赖的关系

Adapter模式

Adapter是为了将已有的实现适应不同的接口而存在。生活中的典型例子是,为了能使两个插销插头查到三个插孔的插座上,通常会在两者之间加上一个插口转换器,这个转换器承担的角色就是本设计模式的用意。

为了使已有的方法适应新的接口(例如已有方法健壮没有毛病,针对相同功能的新接口我们又不像写重复代码),我们通常会编写一个Adapter,它仅仅起着一个转换器的作用。

Adapter可以通过继承委托两种方式实现

Extends or Delegates

例如,系统中遗留着他人已写好的字符串打印类Banner

public class Banner {

    public void printWithBracket(String s) {
        System.out.println("(" + s + ")");
    }

    public void printWithStar(String s) {
        System.out.println("*" + s + "*");
    }
}

现在你正在对系统迭代,需要为新的接口Print编写实现类

public interface Print {

    void printWeak(String s);

    void printStrengthen(String s);
}

而你的实现逻辑和Banner中的两个已有方法不谋而和,于是你可通过继承旧类、实现新接口的方式,既能避免重复代码的编写,又对新接口有所交代

public class PrintBanner extends Banner implements Print {
    @Override
    public void printWeak(String s) {
        this.printWithBracket(s);
    }

    @Override
    public void printStrengthen(String s) {
        this.printWithStar(s);

    }
}

此种方式的缺点是,若目标类(这里指Print)不是接口,那么受限于单继承机制就只能采取聚合的方式

你还可以通过聚合的方式,将实现逻辑委托给旧类:

public class CustomPrint implements Print {

    private Banner banner;

    public CustomPrint(Banner banner) {
        this.banner = banner;
    }

    @Override
    public void printWeak(String s) {
        banner.printWithBracket(s);
    }

    @Override
    public void printStrengthen(String s) {
        banner.printWithStar(s);
    }
}

UML & Summarize

以下是两种方式的类图

image.png

其中方式1受目标类必须是接口的限制。

Roles

  • Adaptee,被适配方,通常为系统中的旧类,且类中存在对某功能A的实现
  • Target,目标类,通常为新添加到系统中的类,包含需要实现某功能A的抽象方法
  • Adapter,适配类,作为被适配方和目标类之间的桥梁,通过继承或聚合的方式实现代码复用

Left to Subclass

Template Method

声明式编程 & 面对抽象编程

模板方法模式属于特殊的“声明式”编程,即通过抽象定义一个业务处理逻辑中各步骤的先后执行顺序,但对于各步骤的具体实现并不关心,交由运行时实际的子类对象受理。这也充分利用了OOP的多态性

例如,现有一个订单业务类OrderService如下:

public class OrderService {

    public void makeOrder() {
        safeVerification();
        reduceStock();
        reduceBalance();
        noticeDelivery();
    }

    public void safeVerification() {
        System.out.println("安全校验");
    }

    public void reduceStock() {
        System.out.println("去MySQL减库存");
    }

    public void reduceBalance() {
        System.out.println("去MySQL减余额");
    }

    public void noticeDelivery() {
        System.out.println("通知发货");
    }
}

客户端代码如下

public class Client {

    public static void main(String[] args) {
        OrderService orderService = new OrderService();
        orderService.makeOrder();
    }
}

上述代码逻辑清晰,看起来没什么问题。但假设现在要做秒杀,需要将库存、余额等信息做一个缓存,那你就需要在原有的OrderService上做修改了。这违反了“开闭原则”(应对修改关闭而对扩展开放)。

此时我们就需要将业务步骤抽象出来,具体的实现交由特定的子类去做,以满足不同的业务场景:

public abstract class AbstractOrderService {

    public void makeOrder() {
        safeVerification();
        reduceStock();
        reduceBalance();
        noticeDelivery();
    }

    public void safeVerification(){
        System.out.println("安全校验");
    }

    public abstract void reduceStock() ;

    public abstract void reduceBalance();

    public void noticeDelivery(){
        System.out.println("通知发货");
    }
}

此时若需要缓存支持,只需新增一个实现类即可

public class OrderServiceWithCache extends AbstractOrderService {
    
    @Override
    public void reduceStock() {
        System.out.println("从缓存中减库存");
    }

    @Override
    public void reduceBalance() {
        System.out.println("从缓存中减余额");
    }
    
}

客户端代码只需切换具体的实现类:

public class Client {

    public static void main(String[] args) {
        AbstractOrderService orderService = new OrderServiceWithCache(); //使用缓存
        orderService.makeOrder();
        orderService = new OrderService();	// 切换到MySQL
        orderService.makeOrder();
    }
}

UML & Summary

image.png

模板方法模式就是在抽象类中进行声明式编程(使用抽象方法的形式强调该业务的完成应该执行哪些步骤以及这些步骤的执行顺序,而不关注每个步骤具体是如何实现的),而将具体业务步骤的实现交由子类(运行时通过多态)完成。

在客户端看来,虽然只是切换了一下子类实例,但好像被切换的实例实现的具体步骤就被注入到整体地业务处理之中一样。

并且对于通用的步骤,如上述的safeVerificationnoticeDelivery,可能它们的处理逻辑是固定的,这时可以将它们提取到父类中,实现复用。

Roles

  • SuperClass & Template Method,模板方法通常声明在抽象类或接口中,调用本类的抽象方法(当然也可以是包含通用逻辑的非抽象方法)完成特定的业务逻辑。
  • Concrete SubClass & Realization,子类只需实现相应的抽象方法就可以将具体的功能步骤注入到整体业务功能中,无需自己显式调用(模板方法会根据运行时信息动态调用)

Factory Method

Delay the Instance’s Creation

工厂方法模式就是模板方法模式的一个应用,只不过就是抽象父类中的抽象方法特化为一个创建实例的方法,将实例的创建延迟到了子类。

例如Person类有一个获取自己交通工具的抽象方法getVehicle,并且能够在其他地方调用该Vehicle暴露的属性、方法,而将Vehicle实例的获取延迟到了子类(说是延迟,因为本身是抽象类,是无法被实例化的,因此在实例化Person的具体子类时能够确保其getVehicle已被重写了)。

如下是示例代码:

public class Vehicle {

    private String name;

    public Vehicle(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }
}

public class Bicycle extends Vehicle {
    public Bicycle() {
        super("自行车");
    }
}

public class JeepCar extends Vehicle {
    public JeepCar() {
        super("小汽车");
    }
}

public abstract class Person {

    public void useVehicle() {
        System.out.println("使用交通工具"+getVehicle().getName()+"来代步");
    }

    protected abstract Vehicle getVehicle();
}

public class Student extends Person {

    @Override
    protected Vehicle getVehicle() {
        return new Bicycle();
    }
}

public class Boss extends Person {
    @Override
    protected Vehicle getVehicle() {
        return new JeepCar();
    }
}

public class Client {

    public static void main(String[] args) {
        Person p = new Student();
        p.useVehicle();
        p = new Boss();
        p.useVehicle();
    }
}

UML & Summary

image.png

Roles

  • Factory Method,抽象父类中将某类实例的获取延迟到具体子类,但本类中的其他方法可以调用该方法并认为已获取到了该类实例,然后进行属性、方法的访问
  • Concrete Method,真正的获取并返回所需实例的逻辑

Generating Instance

Singleton

Seven Method

  1. Lazy loading

    懒加载模式,instance会在getInstance第一次被调用时被初始化

    public class LazyLoadingSingleton {
    
        private LazyLoadingSingleton() {
    
        }
    
        private static LazyLoadingSingleton instance;
    
        public static LazyLoadingSingleton getInstance() {
            if (instance == null) {
                instance = new LazyLoadingSingleton();
            }
            return instance;
        }
    }
    
  2. Synchronized Block Singleton

    上述代码在多线程并发执行时会出现instance被多次赋值的问题,为此可用内部锁语义Synchronized解决

    public class SynchronizerBlockSingleton {
    
        private SynchronizerBlockSingleton() {
    
        }
    
        private static SynchronizerBlockSingleton instance;
    
        public static SynchronizerBlockSingleton getInstance() {
            synchronized (SynchronizerBlockSingleton.class) {
                if (instance == null) {
                    instance = new SynchronizerBlockSingleton();
                }
            }
            return instance;
        }
    }
    
  3. Double-Checked Lock

    双重检查锁定,上述代码先锁定,然后检查,会导致instance被赋值后synchronized对后续并发调用getInstance带来上下文切换的开销,为此可以在锁定前先检查一次

    public class DoubleCheckedSingleton {
    
        private DoubleCheckedSingleton() {
    
        }
    
        private static DoubleCheckedSingleton instance;
    
        public static DoubleCheckedSingleton getInstance() {
            if (instance == null) {
                synchronized (DoubleCheckedSingleton.class) {
                    if (instance == null) {
                        instance = new DoubleCheckedSingleton();
                    }
                }
            }
            return instance;
        }
    }
    
  4. DCL with volatile

    由于指令重排序可能会导致new关键字初始化对象还未完成就返回对象的内存地址,进而导致后续访问instance属性时抛空指针异常,需要使用volatile保证对象初始化完毕后才返回引用地址

    public class VolatileSingleton {
    
        private VolatileSingleton() {
    
        }
    
        private static volatile VolatileSingleton instance;
    
        public static VolatileSingleton getInstance() {
            if (instance == null) {
                synchronized (VolatileSingleton.class) {
                    if (instance == null) {
                        instance = new VolatileSingleton();
                    }
                }
            }
            return instance;
        }
    }
    
  5. Eager Mode

    饿汉模式,如果实例对象的初始化开销较小(占用内存、初始化时间),那么完全可以在类初始化时完成

    public class EagerSingleton {
    
        private EagerSingleton() {
    
        }
    
        private static EagerSingleton instance = new EagerSingleton();
    
        public static EagerSingleton getInstance() {
            return instance;
        }
    }
    

    instance会在类初始化时被初始化,类只会在发生主动引用时被初始化一次,由JVM来保证

  6. Instance Holder

    如果你仍想使用懒汉模式又想优雅些,则可使用静态内部类的方式

    public class InstanceHolderSingleton {
    
        private static class SingletonHolder {
            private static InstanceHolderSingleton instance = new InstanceHolderSingleton();
        }
    
        private InstanceHolderSingleton() {
    
        }
    
        public static InstanceHolderSingleton getInstance() {
            return SingletonHolder.instance;
        }
    
    }
    

    初始化InstanceHolderSingleton时并不会初始化其静态内部类SingletonHolder,只有在调用InstanceHolderSingleton.getInstance()时,instance才会随着SingletonHolder的初始化而初始化。

    以下种情况会立即导致类的初始化:

    • 使用new关键字创建该类实例
    • 访问该类的静态域(包括静态属性和静态方法,但不包括常量)
    • 通过java.reflect包下的类反射访问该类,如Class.forName
    • 初始化子类时会检查其父类是否已被初始化,若父类未被初始化则先初始化其父类
  7. 枚举类的优雅

    JVM也会保证枚举实例在初始化枚举时被初始化一次

    public class EnumSingleton {
    
        private EnumSingleton() {
    
        }
    
        private static EnumSingleton instance;
    
        private enum InstanceEnum{
            INSTANCE;
            private EnumSingleton instance;
            InstanceEnum() {
                instance = new EnumSingleton();
            }
        }
    
        public static EnumSingleton getInstance() {
            return InstanceEnum.INSTANCE.instance;
        }
    
    }
    

Builder

组装具有复杂结构的实例

建造者模式通常应用于需要通过一系列复杂步骤才能得到最终实例的情况。就像建造房子一样,我们需要经过打地基、搭建框架、添砖加瓦、粉饰美化等一系列步骤才能得到最终能住人的房子。并且,这些步骤可以个性化定制,例如有人喜欢欧美风格的,那么房屋框架顶部就要打造成锥形的;有人喜欢粉色,那就可以铺上粉色的墙纸……

本例中,我们以一个邮件内容String实例的生成来演示Builder设计模式的应用。

public abstract class EmailBuilder {

    protected String content = "";

    public String getContent() {
        return content;
    }

    public abstract void makeTitle(String title);

    public abstract void makeBody(String body);

    public abstract void makeGreeting(String greeting);
}

public class TextEmailBuilder extends EmailBuilder {


    @Override
    public void makeTitle(String title) {
        StringBuilder stringBuilder = new StringBuilder(content);
        stringBuilder.append(title).append("\n");
        content = stringBuilder.toString();
    }

    @Override
    public void makeBody(String body) {
        StringBuilder stringBuilder = new StringBuilder(content);
        stringBuilder.append(body).append("\n");
        content = stringBuilder.toString();
    }

    @Override
    public void makeGreeting(String greeting) {
        StringBuilder stringBuilder = new StringBuilder(content);
        stringBuilder.append(greeting).append("\n");
        content = stringBuilder.toString();
    }
}

public class HTMLEmailBuilder extends EmailBuilder {
    @Override
    public void makeTitle(String title) {
        StringBuilder stringBuilder = new StringBuilder(content);
        stringBuilder.append("<h3>").append(title).append("</h3>").append("\n");
        content = stringBuilder.toString();
    }

    @Override
    public void makeBody(String body) {
        StringBuilder stringBuilder = new StringBuilder(content);
        stringBuilder.append("<p style=\"font-family: Microsoft Ya Hei; font-size: 16px\">").append(body).append("</p>").append("\n");
        content = stringBuilder.toString();
    }

    @Override
    public void makeGreeting(String greeting) {
        StringBuilder stringBuilder = new StringBuilder(content);
        stringBuilder.append("<i>").append(greeting).append("</i>").append("\n");
        content = stringBuilder.toString();
    }
}

public class Director {

    private EmailBuilder emailBuilder;

    public Director(EmailBuilder emailBuilder) {
        this.emailBuilder = emailBuilder;
    }

    public void construct() {
        emailBuilder.makeTitle("About Interview");
        emailBuilder.makeBody("We are honor to tell you that you can participate our interview.");
        emailBuilder.makeGreeting("Good Luck!");
    }
}

UML & Summary

image.png

其中EmailBuilder中声明了实例的初始状态(空串)和构建实例的一系列过程,而TextEmailBuilderHTMLEmailBuilder则对这一系列过程进行了个性化实现。最终Director是建造实例整个过程的监工,由它确保实例的成型规则地经历了哪些建造过程。

Roles

  • Builder,定义了产品成型需要经过的一系列工艺(接口方法)
  • ConcreteBuilder,针对每道工艺进行个性化处理
  • Director,监工,根据产品成型流程调用接口方法打造产品
  • Client,模式使用者,通知Director根据传入的ConcreteBuilder打造特定风格的产品

Abstract Factory

将一组关联的零件组装成产品

AbstractFactory其实就是包含了一系列Factory Method的类,只不过这些Factory Method生成的实例都是相互关联的,一起组成某个共同体,少了谁都不行。

例如汽车Car需要汽车外壳Facade、轮胎Wheel、发动机Engine等部件,那么我们就可以创建一个CarFactory抽象工厂,其中声明了一系列部件的获取(抽象方法,不关心该部件是哪个厂家生产的或是哪个牌子的),并提供了产品的构造过程(调用这一系列抽象方法获取所需部件组装成车)

public class Engine {
    String name;

    public Engine(String name) {
        this.name = name;
    }
}
public class Wheel {
    String name;

    public Wheel(String name) {
        this.name = name;
    }
}
public class Facade {
    String name;

    public Facade(String name) {
        this.name = name;
    }
}
public class Car {

    Engine engine;
    Wheel wheel;
    Facade facade;

    public Car(Engine engine, Wheel wheel, Facade facade) {
        this.engine = engine;
        this.wheel = wheel;
        this.facade = facade;
    }
}
public abstract class CarFactory {

    public Car getCar() {
        return new Car(getEngine(), getWheel(), getFacade());
    }

    public abstract Engine getEngine();

    public abstract Wheel getWheel();

    public abstract Facade getFacade();
}
public class CustomEngine extends Engine {

    public CustomEngine() {
        super("自定义牌发动机");
    }
}
public class CustomWheel extends Wheel{
    public CustomWheel() {
        super("自定义牌轮胎");
    }
}
public class CustomFacade extends Facade {
    public CustomFacade() {
        super("自定义牌车壳");
    }
}
public class CustomCarFactory extends CarFactory{
    @Override
    public Engine getEngine() {
        return new CustomEngine();
    }

    @Override
    public Wheel getWheel() {
        return new CustomWheel();
    }

    @Override
    public Facade getFacade() {
        return new CustomFacade();
    }
}
public class Client {
    public static void main(String[] args) {
        CarFactory carFactory = new CustomCarFactory();
        Car car = carFactory.getCar();
        System.out.println("custom car -> " + car.engine.name + "+" + car.wheel.name + "+" + car.facade.name);
    }
}

UML

image.png

Consider Individualy

Bridge

将类的功能层次和类的实现层次分开

  • 类的功能层次结构

    通过继承我们能够继承基类已有的功能,在此之上我们能够:重写基类已有功能(使该功能具备本类特色)、也可以新增功能,重写(这里的重写特指重写基类的已有实现)或新增功能的子类与基类构成类的功能层次结构

    public class Animal {
    
        public void eat() {
            System.out.println("动物会觅食");
        }
    }
    
    public class Bird extends Animal {
    
        @Override
        public void eat() {
            System.out.println("鸟觅食虫子");
        }
    
        public void fly() {
            System.out.println("鸟会飞");
        }
    }
    
  • 类的实现层次结构

    通过继承,我们能够实现基类的抽象方法,通过运用Template Method,我们可以将子类的逻辑注入到基类模板过程中,这时新增子类仅为了实现基类的抽象方法,子类和基类构成类的实现层次结构

    public abstract class Animal {
    
        public void hunt() {
            lockTarget();
            quickAttack();
            swallow();
        }
    
        public abstract void lockTarget();
    
        public abstract void quickAttack();
    
        public abstract void swallow();
    
    }
    
    public class Snake extends Animal {
        @Override
        public void lockTarget() {
            System.out.println("锁定猎物");
        }
    
        @Override
        public void quickAttack() {
            System.out.println("迅速咬住猎物喉部");
        }
    
        @Override
        public void swallow() {
            System.out.println("一口吞掉整个猎物");
        }
    }
    

如果我们将两个例子的Animal整合在一起:

public abstract class Animal {

    public void eat() {
        System.out.println("动物会觅食");
    }

    public void hunt() {
        lockTarget();
        quickAttack();
        swallow();
    }

    public abstract void lockTarget();

    public abstract void quickAttack();

    public abstract void swallow();

}

你会发现,Bird无法编译,作为具体子类它必须实现抽象方法lockTargetquickAttackswallow,但是我们新增Bird的初衷只是为了继承Animaleat方法,并新增一个自己会fly的功能。

这时就需要我们将类的功能层次和实现层次分开了

public abstract class Animal {

    private Hunt hunt;

    public Animal(Hunt hunt) {
        this.hunt = hunt;
    }

    public void eat() {
        System.out.println("动物会觅食");
    }

    public void hunt() {
        hunt.hunt();
    }
}

public abstract class Hunt {

    public void hunt() {
        lockTarget();
        quickAttack();
        swallow();
    }

    public abstract void lockTarget();

    public abstract void quickAttack();

    public abstract void swallow();

}

public class Bird extends Animal {

    public Bird(Hunt hunt) {
        super(hunt);
    }

    @Override
    public void eat() {
        System.out.println("鸟觅食虫子");
    }


    public void fly() {
        System.out.println("鸟会飞");
    }
}

public class DefaultHunt extends Hunt {
    @Override
    public void lockTarget() {
        System.out.println("用眼睛锁定猎物");
    }

    @Override
    public void quickAttack() {
        System.out.println("快速咬死猎物");
    }

    @Override
    public void swallow() {
        System.out.println("一口一口吃掉猎物");
    }
}

public class Snake extends Animal {

    public Snake(Hunt hunt) {
        super(hunt);
    }

}

public class SnakeHunt extends Hunt {
    @Override
    public void lockTarget() {
        System.out.println("红外线感知锁定猎物");
    }

    @Override
    public void quickAttack() {
        System.out.println("使用尖牙和毒液快速致死猎物");
    }

    @Override
    public void swallow() {
        System.out.println("一口吞掉整个猎物");
    }
}

public class Client {

    public static void main(String[] args) {
        Hunt defaultHunt = new DefaultHunt();
        Hunt snakeHunt = new SnakeHunt();

        Animal snake = new Snake(snakeHunt);
        System.out.println("蛇开始狩猎==========");
        snake.hunt();

        Animal bird = new Bird(defaultHunt);
        System.out.println("鸟开始狩猎===========");
        bird.hunt();
        System.out.println("鸟有不同于一般动物的功能");
        ((Bird) bird).fly();
    }
}

UML & Summary

image.png

如上,AnimalBirdSnake组成功能层次结构、HuntDefaultHuntSnake则组成了实现层次结构。这样,以后如果我们想扩展功能(重写或新增),那么就可以找对应功能层次结构中的类继承;如果想针对狩猎方式进行个性化实现,则继承实现层次结构中的类即可。

桥接模式避免了将实现和扩展捆绑在一起,减少底层类扩展基类的压力

Roles

  • 功能层次结构
  • 实现层次结构
  • 功能层次结构的基类持有实现层次结构基类的引用,并将实现层面的逻辑委托给该引用

Strategy

封装一个特定的算法

假如你是一个农场主,需要按照顾客的需求从已采摘的苹果中挑选出符合顾客标准的苹果

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Apple {

    private AppleColorEnum color;

    private double weight;

    public enum AppleColorEnum {
        RED,YELLOW,GREEN
    }
}

于是你编写了如下挑选苹果的业务处理类

public class AppleService {
    List<Apple> findAppleByColor(List<Apple> apples, Apple.AppleColorEnum color) {
        List<Apple> res = new ArrayList<>();
        for (Apple apple : apples) {
            if (Objects.equals(apple.getColor(),color)){
                res.add(apple);
            }
        }
        return res;
    }
}

但是如果你遇到了一个刁钻的顾客,他不仅要求颜色为红色,而且还要求重量在500g以上呢?你可以再添加一个findRedAndWeightGreatThan500,但是每个顾客可能对颜色和重量的标准都是不一样的,并且你无法估量有哪些顾客,对应有哪些挑选标准。

这时就需要把挑选标准(实际上就是一个算法)单独抽离出来,分析输入和输出:

  • 输入:Apple,给你一个苹果
  • 输出:boolean,该苹果是否符合标准
public interface AppleFilterStrategy {

    /**
     * 如果该苹果符合挑选标准,那么就返回true
     * @param apple
     * @return
     */
    boolean filterApple(Apple apple);
}

这时,挑选苹果业务类就无需预知和预置众多挑选苹果的方法了,因为挑选策略交给了AppleFilterStrategy

public class AppleService {
    List<Apple> findApple(List<Apple> apples, AppleFilterStrategy strategy) {
        List<Apple> res = new ArrayList<>();
        for (Apple apple : apples) {
            if (strategy.filterApple(apple)) {
                res.add(apple);
            }
        }
        return res;
    }
}

客户端可以根据客户提出的挑选需求,通过匿名类的方式随意地注入挑选策略

public class Client {

    public static void main(String[] args) {

        // 农场主采摘的苹果
        List<Apple> apples = Arrays.asList(
                new Apple(Apple.AppleColorEnum.RED, 200),
                new Apple(Apple.AppleColorEnum.RED, 400),
                new Apple(Apple.AppleColorEnum.RED, 600),
                new Apple(Apple.AppleColorEnum.YELLOW, 100),
                new Apple(Apple.AppleColorEnum.YELLOW, 500),
                new Apple(Apple.AppleColorEnum.YELLOW, 900),
                new Apple(Apple.AppleColorEnum.GREEN, 400),
                new Apple(Apple.AppleColorEnum.GREEN, 500),
                new Apple(Apple.AppleColorEnum.GREEN, 600)
        );
        AppleService appleService = new AppleService();

        // A顾客需要红色的重量大于500g的苹果
        List<Apple> res1 = appleService.findApple(apples, new AppleFilterStrategy() {
            @Override
            public boolean filterApple(Apple apple) {
                return Objects.equals(apple.getColor(), Apple.AppleColorEnum.RED) &&
                        apple.getWeight() >= 500;
            }
        });
        System.out.println(res1);

        System.out.println("======================");

        // B顾客需要青色的种类小于400的
        List<Apple> res2 = appleService.findApple(apples, new AppleFilterStrategy() {
            @Override
            public boolean filterApple(Apple apple) {
                return Objects.equals(apple.getColor(), Apple.AppleColorEnum.GREEN) &&
                        apple.getWeight() <= 400;
            }
        });
        System.out.println(res2);
    }
}

函数式编程

在Java8之后,像AppleFilterStrategy这种只包含一个接口方法的接口可以被标注为@FunctionalInterface,并使用Lambda表达式替代冗余的匿名类

@FunctionalInterface
public interface AppleFilterStrategy {
    boolean filterApple(Apple apple);
}

List<Apple> res3 = appleService.findApple(apples, apple -> apple.getColor() != Apple.AppleColorEnum.GREEN && apple.getWeight() >= 300);
System.out.println(res3);

UML & Summary

image.png

策略模式的核心思想就是将复杂多变的算法逻辑抽取出来,交给客户端实现,而业务层只负责应用客户端传递的算法实现

Role

  • Strategy Interface,策略接口,定义了某个特定的算法
  • Service,应用策略接口的算法,面对抽象编程
  • Client,调用Service时注入具体的算法实现
  • Concrete Strategy,具体的算法实现,通常以匿名类的形式存在,Java8之后可用Lambda代替

Consistency

Composite

让容器和容器中的内容有着一致的外观

混合模式的典型应用就是文件系统,一个目录Directory中可以存放若干条目Entry,每个条目既可以是目录又可以是文件File。混合模式的目的就是让容器(如目录)和容器中的内容(如目录或文件)有着一致性的外观(如Entry

public abstract class Entry {


    private String name;

    private int size;

    private List<Entry> items;

    public Entry(String name, int size) {
        this.name = name;
        this.size = size;
        items = new ArrayList<>();
    }

    public void addEntry(Entry entry) {
        this.items.add(entry);
    }

    public abstract void print(int... layer);
}

public class Directory extends Entry {

    public Directory(String name, int size) {
        super(name, size);
    }
    
    @Override
    public void print(int... layer) {
        int n;
        if (layer == null || layer.length == 0) {
            n = 0;
        } else {
            n = layer[0];
        }
        for (int i = 0; i < n; i++) {
            System.out.print("\t");
        }
        System.out.println(getName());
        getItems().forEach(entry -> entry.print(n + 1));
    }
}

public class File extends Entry {
    public File(String name, int size) {
        super(name, size);
    }
    
    @Override
    public void print(int... layer) {
        int n;
        if (layer == null || layer.length == 0) {
            n = 0;
        } else {
            n = layer[0];
        }
        for (int i = 0; i < n; i++) {
            System.out.print("\t");
        }
        System.out.println(getName() + " size=" + getSize()+"kb");
        getItems().forEach(entry -> entry.print(n + 1));
    }
}

public class Client {

    public static void main(String[] args) {
        Entry root = new Directory("/root", 2);

        Entry bin = new Directory("/bin", 0);
        root.addEntry(bin);
        Entry usr = new Directory("/usr", 1);
        root.addEntry(usr);
        Entry etc = new Directory("/etc", 0);
        root.addEntry(etc);

        Entry local = new Directory("/local", 3);
        usr.addEntry(local);
        Entry java = new File("java.sh", 128);
        local.addEntry(java);
        Entry mysql = new File("mysql.sh", 64);
        local.addEntry(mysql);
        Entry hadoop = new File("hadoop.sh", 1024);
        local.addEntry(hadoop);

        root.print();
    }
}

/root
	/bin
	/usr
		/local
			java.sh size=128kb
			mysql.sh size=64kb
			hadoop.sh size=1024kb
	/etc

UML

image.png

Roles

  • 组件

    如本例的Entry,是容器和容器内容的同一外观。系统不直接操作容器和容器中的内容,而是操作组件

  • 容器

    其中可包含若干容器和条目

  • 条目

    系统中的基本单元

Decorator

装饰者模式是一种结合继承和组合(委托)设计模式,通过继承能够实现统一外观(装饰类和目标类有共同的父类),通过委托能够在目标功能的基础之上进行增强。并且装饰类不管目标类是源目标类还是被装饰过的目标类,它对目标类是否已被保存过和被哪种包装器(装饰类)包装过以及被包装了几层都不关心,它只负责当前的装饰逻辑。

不改变目标类的行为,只是在其之上做一些包装

本例中,有一个糕点师Baker,他有一个bake烘焙面包的抽象方法,BreadBaker则是他的一个实现。现在我们需要根据顾客的不同口味对原味的面包进行包装,例如应该加哪些佐料Ingredient,以下是示例代码

public abstract class Baker {

    public abstract void bake();
}

public class BreadBaker extends Baker {
    @Override
    public void bake() {
        System.out.println("面包被烘焙");
    }
}
public class IngredientDecorator extends Baker {

    private Baker baker;
    private String ingredient;

    public IngredientDecorator(Baker baker,String ingredient) {
        this.baker = baker;
        this.ingredient = ingredient;
    }

    @Override
    public void bake() {
        System.out.print("添加了" + ingredient + "的");
        baker.bake();
    }
}
public class Client {

    public static void main(String[] args) {
        Baker baker = new BreadBaker();
        baker.bake();

        Baker pepper = new IngredientDecorator(baker, "胡椒粉");
        pepper.bake();

        Baker mustard = new IngredientDecorator(baker, "芥末");
        mustard.bake();

        Baker oliveOil = new IngredientDecorator(baker, "橄榄油");
        oliveOil.bake();

        Baker pepperAndOlive = new IngredientDecorator(new IngredientDecorator(baker, "橄榄油"), "胡椒粉");
        pepperAndOlive.bake();

        Baker mustardAndOliveAndPepper = 
            new IngredientDecorator(
            	new IngredientDecorator(
                	new IngredientDecorator(baker, "胡椒粉"),
                "橄榄油"),
            "芥末");
        mustardAndOliveAndPepper.bake();

    }
}

面包被烘焙
添加了胡椒粉的面包被烘焙
添加了芥末的面包被烘焙
添加了橄榄油的面包被烘焙
添加了胡椒粉的添加了橄榄油的面包被烘焙
添加了芥末的添加了橄榄油的添加了胡椒粉的面包被烘焙

当然本例只是单纯演示装饰者模式的思想,你完全可以将Ingredient具体化为PepperIngredientMustardIngredident等代替字符串魔法值

UML & Summary

image.png

应用装饰者模式有一个口诀:是你(IS-A)还有你(HAS-A),一切拜托你(所有的重写方法委托给目标类对象,在此之上自己可以添加当前这一层包装的逻辑)。

Roles

  • Parent,装饰类和目标类有一个共同的父类,通过在父类声明抽象方法使得两者有共同的外观
  • Target,目标类,需要被包装的类
  • Decorator,装饰类,既可以直接装饰目标对象,又可以装饰装饰过目标对象的装饰对象,因为两者有共同的外观

Access Data Structure

Visitor

将数据结构的管理和访问处理分开

visitor模式目的是将数据结构的管理(对元素的增删改查)和对数据结构的访问处理逻辑分离开,通常和迭代器模式结合使用。我们将对数据访问处理的逻辑单独定义一个Visitor接口以及声明相应的visit(E element)方法,而数据接口则对应提供一个受理Visitoraccept(Visitor visitor)方法(其实就是简单的调用visitor.visit()visit(E element)相当于对访问到的元素进行消费。

以下是数据结构为多叉树时的示例代码(其中迭代器用的是前文实现的而非JDK自带的)

@Data

public abstract class Node<E> implements Iterable<Node<E>> {

    private E element;

    private List<Node<E>> children;

    public Node(E element) {
        this.element = element;
    }

    public abstract void accept(NodeVisitor<E> visitor);

    @Override
    public Iterator<Node<E>> iterator() {
        return new NodeIterator(this);
    }
}

public class Leaf<E> extends Node<E> {
    public Leaf(E element) {
        super(element);
        this.setChildren(Collections.emptyList());
    }

    @Override
    public void accept(NodeVisitor<E> visitor) {
        visitor.visitLeaf(this);
    }
}

public class Branch<E> extends Node<E> {
    public Branch(E elemnt) {
        super(elemnt);
        this.setChildren(new ArrayList<>());
    }

    @Override
    public void accept(NodeVisitor<E> visitor) {
        visitor.visitBranch(this);
    }
}
public class NodeIterator<E> implements Iterator<Node<E>> {

    private Node<E> root;
    private Stack<Node<E>> stack;

    public NodeIterator(Node<E> root) {
        this.root = root;
        this.stack = new Stack<>();
        stack.push(root);
    }

    @Override
    public boolean hasNext() {
        return stack.size() > 0;
    }

    @Override
    public Node<E> next() {
        if (!hasNext()) {
            throw new RuntimeException("no more elements");
        }
        Node<E> node = stack.pop();
        List<Node<E>> children = node.getChildren();
        for (int i = children.size() - 1; i >= 0; i--) {
            stack.push(children.get(i));
        }
        return node;
    }
}
public interface NodeVisitor<E> {

    void visitLeaf(Leaf<E> leaf);

    void visitBranch(Branch<E> branch);
}

public class PrintNodeVisitor<E> implements NodeVisitor<E> {
    @Override
    public void visitLeaf(Leaf<E> leaf) {
        System.out.print(leaf.getElement()+" ");
    }

    @Override
    public void visitBranch(Branch<E> branch) {
        System.out.print(branch.getElement()+" ");
    }
}

public class PlusOneNodeVisitor implements NodeVisitor<Integer> {

    /**
     * 访问到叶子节点则将节点值+1
     * @param leaf
     */
    @Override
    public void visitLeaf(Leaf<Integer> leaf) {
        leaf.setElement(leaf.getElement() + 1);
    }

    /**
     * 访问到分叉节点,则将节点值+其孩子节点数
     * @param branch
     */
    @Override
    public void visitBranch(Branch<Integer> branch) {
        branch.setElement(branch.getElement() + branch.getChildren().size());
    }
}

UML & Summary

image.png

visitor模式将对数据结构的访问逻辑通过accept委托给Visitor接口和迭代器模式将遍历访问逻辑通过createIterator委托给Iterator接口有异曲同工之妙。将专业的事交给专业的人做,满足Single ResponsibilityInterface Segregation Principle;需要修改访问处理逻辑我们只需要新增一个NodeVisitor的实现,满足Open/Closed Principle;逻辑实现和客户端代码都面向接口NodeVisitor编程,满足Dependency Inversion

Roles

  • Visitor,拜访者,声明相关的visit方法,用于对数据结构进行访问处理
  • DataStructure,数据结构,提供元素的增删改查接口,通过accept(visitor)将访问处理逻辑交给Visitor
  • ConcreteVisitor,根据业务所需,实现具体的访问处理逻辑

Chain Of Responsibility

责任链模式通过维护若干请求受理者形成一条链来处理客户端发起的请求,该模式最大的优点在于它弱化了客户端和请求受理者之间的关系,客户端只需要将请求发送到责任链,在责任链中通过连环委托的机制就能够做到无法受理请求的人直接忽略请求,而能够受理请求的人截断请求并受理。

客户端不用关心请求具体会被谁受理,这样就提高了客户端的独立性。

弱化请求方和受理方之间的关系

本例中,存在一条公司组织架构责任链(Employee->Leader->Manager->Boss),他们都能够受理报销费用的请求handle(int amount)Client无需关心多少面值的报销金额应该由谁来受理

public abstract class Handler {

    private Handler handler = null;

    public abstract void handle(int amount);

    public Handler setNext(Handler handler) {
        this.handler = handler;
        return handler;
    }

    public Handler getNext() {
        return handler;
    }
}

public class Employee extends Handler {
    @Override
    public void handle(int amount) {
        if (amount <= 100) {
            System.out.println("$" + amount + " is handled by employee");
            return;
        }
        this.getNext().handle(amount);
    }
}

public class Leader extends Handler{

    @Override
    public void handle(int amount) {
        if (amount <= 1000) {
            System.out.println("$" + amount + " is handled by leader");
            return;
        }
        this.getNext().handle(amount);
    }
}

public class Manager extends Handler{
    @Override
    public void handle(int amount) {
        if (amount <= 5000) {
            System.out.println("$" + amount + " is handled by manager");
            return;
        }
        this.getNext().handle(amount);
    }
}

public class Boss extends Handler {

    @Override
    public void handle(int amount) {
        System.out.println("$" + amount + " is handled by boss");
    }
}
public class Client {

    public static void main(String[] args) {
        Handler employee = new Employee();
        Handler leader = new Leader();
        Handler manager = new Manager();
        Handler boss = new Boss();
        employee.setNext(leader).setNext(manager).setNext(boss);
        for (int i = 0; i < 6; i++) {
            int amount = (int) (Math.random() * 10000);
            employee.handle(amount);
        }
    }
}

$6643 is handled by boss
$4964 is handled by manager
$684 is handled by leader
$9176 is handled by boss
$8054 is handled by boss
$909 is handled by leader

参考资料

《图解设计模式》


标题:图解设计模式
作者:zanwen
地址:http://www.zhenganwen.top/articles/2019/08/13/1565708710564.html

评论