设计模式

Zephyr Lv3

更加高效的搬砖方法

设计模式

Adapter模式

适配器:在实际情况和需求之间,填补两者之间的差异。因此适配器模式就是用于填补现有程序和需要程序之间的差异,提高代码的复用性。通俗来说,就是在不修改现有代码的情况下,在适配器里添加新的业务逻辑,使其满足现有的需求。

类适配器模式

在这种模式中,会让Adapter直接继承Adaptee,然后再根据Target需要的方法进行一定的适配。

类适配器模式的类图

例如,假设我们现在已经有了Banner类,它可以输出由()*包裹的指定字符串。而我们现在希望有的功能是,在输出字符串前后添加提示语句。这时候我们就可以用适配器,在不修改Banner的情况下完成业务条件。

担任Adaptee的Banner,代表了我们已有的代码

public class Banner {
    
    private String str;

    public Banner(String str) {
        this.str = str;
    }

    public void showWithParen() {
        System.out.println("(" + str + ")");
    }

    public void showWithStar() {
        System.out.println("*" + str + "*");
    }
}

担任Target的Print接口,规定了我们希望实现的业务功能。

public interface Print {
    void printWeak();
    void printStrong();
}

本场主角,担任Adapter的PrintBanner类,它继承了Banner,获取了已有的方法,并实现接口Print,完成现阶段的任务。

public class PrintBanner extends Banner implements Print {

    public PrintBanner(String str) {
        super(str);
    }

    @Override
    public void printWeak() {
        System.out.println("invoke printWeak()");
        showWithParen();
    }

    @Override
    public void printStrong() {
        System.out.println("invoke printStrong()");
        showWithStar();
    }
    
}

对象适配器模式

对象适配器模式在思想上和类适配器模式没有区别,主要差异在于,类适配器模式通过继承的方式来获取原有代码的方法,而对象适配器模式则通过委托,即让适配器持有原有方法。此时适配器可以直接继承Target的抽象类。

对象适配器类图

在本设计模式中,有如下登场角色:

  1. Target:该角色负责定义需要的方法,也就是要满足的新业务逻辑。
  2. Client:使用Target提供的方法进行具体处理。
  3. Adaptee:一个持有既定方法的角色,并且这些方法不能直接被应用于新的业务。
  4. Adapter:利用Adaptee提供的方法来满足Target的需求。

Template Method模式

这是一种带有模板功能的设计模式(废话),在父类中定义组成模板的方法,并将他们设为抽象方法,交给子类去实现。同时在父类中实现一个表达抽象方法执行流程的方法。通俗来说就是父类制定好某个程序的运行流程,但将内部的具体实现交给子类实现。(父类是提要求的甲方,子类是满足要求的苦逼乙方)

利用模板设计模式,我们就可以做到处理逻辑的通用化,在父类中定义了程序的运行逻辑,子类中实现运行细节。这样即使运行逻辑出现问题,也只需要修改父类中的方法,而不需要大规模的改动子类。不过要注意的是,虽然将部分方法的实现放在父类中可以让子类的实现更加轻松,但也降低了子类的灵活性;如果父类中的方法实现过少,子类又会变得臃肿。因此哪些处理交给父类,哪些交给子类,是在开发中要着重考虑的,避免造成设计模式的滥用。

template method类图

在父类中定义模板方法,并实现程序运行模板。

public abstract class AbstractDisplay {

    /** 将模板方法声明为final,防止子类修改 */
    public final void display() {
        open();
        print();
        close();
    }

    /** 将子类要实现的方法声明为protected,防止被无关的类调用 */
    protected abstract void open();
    protected abstract void print();
    protected abstract void close();
}

以下是两种子类的实现

public class CharDisplay extends AbstractDisplay {

    private char ch;

    public CharDisplay(char ch) {
        this.ch = ch;
    }

    @Override
    protected void open() {
        System.out.printf("<<");
    }

    @Override
    protected void print() {
        System.out.print(ch);
    }

    @Override
    protected void close() {
        System.out.print(">>\n");
    }
    
}
public class StringDisplay extends AbstractDisplay {

    private String str;

    public StringDisplay(String str) {
        this.str = str;
    }

    @Override
    protected void open() {
        System.out.println("=======start=======");
    }

    @Override
    protected void print() {
        System.out.println(str);
        
    }

    @Override
    protected void close() {
        System.out.println("=========end=======");
        
    }
}

Factory Method 模式

上一章提到,模板模式是在父类中规定处理的流程,在子类中实现具体的处理。如果把流程换成生成实例,就是本章要讲的工厂模式。在工厂模式中,父类决定实例的生成方式,子类生成具体的类。

工厂模式类图

在本设计模式中的登场角色:

  1. Product:一个抽象类,定义了Factory生成的实例应当持有的接口。
  2. Creator:负责生成Product的抽象类。该工厂并不了解子类中生成实例的具体实现,但知道可以通过调用对应的生成方法获得需要的实例。同时,这里使用createProduct来生成实例,和new相比,耦合性更低。用new创建实例需要该类有具体的实现类,当业务发生变动时,new的对象就必须更改,同时导致依赖该方法的对象也要做出更改。而createProduct属于抽象方法,程序调用时只知道他会返回需要的对象,即使该方法的实现有变动,也不会影响其他代码的运行。 (解耦通俗来说就是让程序间的交互从具体到抽象,调用者只需知道方法会返回正确结果,而无需了解任何内部实现情况)

框架包

/** 工厂生成的产品 */
public abstract class Product {
    public abstract void use();
}

=============================================

/** 抽象工厂类 */
public abstract class Factory {
    
    /** 实例的生成流程 */
    public final Product create(String owner) {
        Product p = createProduct(owner);
        regiserProduct(p);
        return p;
    }

    protected abstract Product createProduct(String owner);

    protected abstract void regiserProduct(Product p);
}

具体实现

public class IDCard extends Product {

    private String id;
    private String owner;

    public IDCard(String owner) {
        Random random = new Random();
        StringBuilder builder = new StringBuilder();
        for (int i = 0; i < 6; i++) {
            builder.append(random.nextInt(10));
        }
        this.id = builder.toString();
        this.owner = owner;
    }

    @Override
    public void use() {
        System.out.println(owner + "使用id为:" + id + "的卡");
    }

    public String getId() {
        return id;
    }

    public String getOwner() {
        return owner;
    }
     
}

==============================================

public class CardFactory extends Factory {

    private Map<String, String> idOwners = new HashMap<>();

    @Override
    protected Product createProduct(String owner) {
        return new IDCard(owner);
    }

    @Override
    protected void regiserProduct(Product p) {
        IDCard card = (IDCard) p;
        idOwners.put(card.getId(), card.getOwner());
    }
    
}

Prototype 模式

不使用类名来生成实例,而是用现有的实例来生成实例。
主要应用场景:

  1. 对象种类繁多,难以整合到一个类。此时用现有实例直接复制,可以避免编写大量的类。
  2. 难以根据类生成实例。例如,用户在画图中画了一个不规则图形,当用户画出后就先保存起来,再次需要一个一样的实例时,直接复制一份。
  3. 解耦框架与生成实例。

Prototype模式类图

在本设计模式中,要让Product实现Cloneable接口,这样才能调用clone方法

// Product.java
public abstract class Product implements Cloneable{
    
    public abstract void use(String str);

    // 生成一个当前实例的拷贝
    public abstract Product createClone();
}

Manager使用单例模式来实现,并且记录已有的实例,为克隆做准备。

// Manager.java
public class Manager {
    
    private Map<String, Product> showcase = new HashMap<>();
    private static final Manager instance = new Manager();
    
    private Manager() {
    }

    public static Manager getInstance() {
        return instance;
    }

    public void register(String name, Product product) {
        showcase.put(name, product);
    }

    public Product create(String prototypeName) {
        return showcase.get(prototypeName);
    }
}

驱动类,其中MessageBox和UnderlinePen均为Product实现类

// Main.java
public class Main {
    public static void main(String[] args) {
        Manager manager = Manager.getInstance();
        UnderlinePen uPen = new UnderlinePen('~');
        MessageBox wBox = new MessageBox('*');
        MessageBox sBox = new MessageBox('/');
        manager.register("strong", uPen);
        manager.register("warning", wBox);
        manager.register("slash", sBox);

        Product p1 = manager.create("strong");
        Product p2 = manager.create("warning");
        Product p3 = manager.create("slash");
        p1.use("a");
        p2.use("b");
        p3.use("c");
    }
}

借助以上实例,我们再来感受一下Prototype模式的应用场景。

  • 在上面的实例中,我们总共生成了3种样式,并且最后都通过副本来使用。如果我们希望固化某种样式便于之后直接使用,那势必需要为它新建一个类。当类似的样式越来越多时,类的数量就会越来越庞大,难以管理。但如果我们直接将对应的实例交给Manager,让他保存该实例,当下一次需要这个实例时,就可以直接复制,而不需要新建一个类。
  • 解耦在此处也比较好理解,Manager一直调用的都是Product相关的方法,而没有直接使用子类的类名,这样可以让框架更好的从类名的束缚挣脱出来(当一个类知道了另一个类的类名,就代表这两者紧密联合了起来)
    不过类名也不一定是束缚,如果编码的目的是制作高复用的组件,那类与类之间的耦合度必须很低,但当多个类必须紧密连接时,使用类名也没有问题。

Abstract Factory 模式

工厂的作用是将零件组装成产品,而抽象工厂的作用便是将抽象零件组装成抽象产品。由于抽象零件中只包含该零件持有的接口,因此抽象工厂便是利用这些接口,将零件组装为产品。
使用场景:

  • 一个系统不应当依赖于产品类实例如何被创建、组合和表达的细节,这对于所有类型的工厂模式都是重要的。
  • 系统中有多于一个的产品族,而每次只使用其中某一产品族。
  • 属于同一个产品族的产品将在一起使用,这一约束必须在系统的设计中体现出来。
  • 系统提供一个产品类的库,所有的产品以同样的接口出现,从而使客户端不依赖于具体实现。
    最典型的就是应用主题切换

抽象工厂的优缺点:
优点:易于增加新的工厂,如果出现新的产品族,不需要修改抽象工厂和客户端代码,只需要再实现一个新的具体工厂即可。
缺点:难以增加新的零件,若要增加零件,则需要改动所有的具体工厂以及抽象工厂。因此,抽象工厂适用于有相同特质的产品族。

抽象工厂类图

example. 用列表的形式展示链接的HTML文档
以下是抽象工厂与抽象产品,Item用于统一管理Link与Tray。Tray中可以包含若干个Link与Tray。Page标识HTML文档

public abstract class Factory {
    public static Factory getFactory(String className) {
        Factory factory = null;
        try {
            factory = (Factory) Class.forName(className).newInstance();
        } catch (ClassNotFoundException e) {
            System.out.println("class not found");
        } catch (Exception e) {
            System.out.println("unknown error");
        }
        return factory;
    }

    public abstract Link createLink(String caption, String url);
    public abstract Tray createTray(String caption);
    public abstract Page createPage(String caption, String author);
}
public abstract class Item {
    protected String caption;

    public Item(String caption) {
        this.caption = caption;
    }
    
    public abstract String makeHTML();
}
public abstract class Link extends Item {
    protected String url;
    public Link(String caption, String url) {
        super(caption);
        this.url = url;
    }
}
public abstract class Page {
    protected String title;
    protected String author;
    protected List<Item> content = new ArrayList<>();
    
    public Page(String title, String author) {
        this.title = title;
        this.author = author;
    }

    public void add(Item item) {
        content.add(item);
    }

    public void output() {
        String filename = title + ".html";
        try (Writer writer = new PrintWriter(filename)) {
            writer.write(this.makeHTML());
            System.out.println(filename + "完成");
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
    
    public abstract String makeHTML();
}
public abstract class Tray extends Item {
    protected List<Item> tray = new ArrayList<>();

    public Tray(String caption) {
        super(caption);
    }

    public void add(Item item) {
        tray.add(item);
    }
}

以下是实现类

public class ListFactory extends Factory {

    @Override
    public Link createLink(String caption, String url) {
        return new ListLink(caption, url);
    }

    @Override
    public Tray createTray(String caption) {
        return new ListTray(caption);
    }

    @Override
    public Page createPage(String caption, String author) {
        return new ListPage(caption, author);
    }
    
}
public class ListLink extends Link {

    public ListLink(String caption, String url) {
        super(caption, url);
    }
    
    @Override
    public String makeHTML() {
        return "<li><a href=\"" + url + "\">" + caption + "</a></li>\n";
    }
}
public class ListPage extends Page {

    public ListPage(String title, String author) {
        super(title, author);
    }

    @Override
    public String makeHTML() {
        StringBuffer buffer = new StringBuffer();
        buffer.append("<html><head><title>" + title + "</title></head>\n");
        buffer.append("<body>\n");
        buffer.append("<h1>" + title + "</h1>\n");
        buffer.append("<ul>\n");
        for (Item item : content) {
            buffer.append(item.makeHTML());
        }
        buffer.append("</ul>");
        buffer.append("<hr><address>" + author + "</address>");
        buffer.append("</body></html>\n");
        return buffer.toString();
    }
    
}
public class ListTray extends Tray{

    public ListTray(String caption) {
        super(caption);
    }

    @Override
    public String makeHTML() {
        StringBuffer buffer = new StringBuffer();
        buffer.append("<li>\n");
        buffer.append(caption + "\n");
        buffer.append("<ul>\n");
        for (Item item : tray) {
            buffer.append(item.makeHTML());
        }
        buffer.append("</ul>\n");
        buffer.append("</li>\n");
        return buffer.toString();
    }
    
}

类的层次结构

在继续学习Bridge模式之前,首先要了解一下类的层次结构。

类的功能层次结构

当我们希望为某一个类增添新的功能时,我们通常会选择写一个子类继承原先的类。这种添加功能的方式就形成了一个功能层次结构。父类具有基本功能,在子类中添加新的功能
Something
|_____ Something good
如果我们要进一步扩展 something good,就会再为他写一个子类,层次结构就又加深了,通常功能层次结构不宜过深。

类的实现层次结构

功能层次结构是为了标识类功能的扩展,那么实现层次结构表现的就是类的不同实现,也就是父类声明抽象方法来定义接口,子类实现这些接口。他的作用是帮助我们拆分接口定义与接口实现这两个部分。

通过上面两种结构,我们不难发现,子类存在的目的无外乎两种:对父类功能的扩展,对父类方法的实现。当类的层次结构只有一层时,两个层次结构都混杂在一起,很容易导致层次结构变复杂。因此我们需要将这两个层次结构分离开来,并使用Bridge模式连接二者。

Bridge 模式

Bridge模式的作用是将类的功能层次结构与类的实现层次结构连接起来。通过将功能层次与实现层次分离,确保各层次的类能作为组件使用,Bridge模式则可以帮助拼接这些组件,提高灵活性。

Bridge 模式类图

example. 显示指定内容。
Display是功能层次的最高层,它将任务委托给实现类似DisplayImpl来达成display的功能。
DisplayImpl继承也可以实现这样的效果,但继承就意味着强联系,一旦出现更改,就会导致两个类都受到影响,但委托则可以让委托者不必关注实现类的具体操作,只负责进行调用。

// 功能层次的最高层
public class Display {
    
    // 将显示的任务委托给实现类
    private DisplayImpl impl;

    public Display(DisplayImpl impl) {
        this.impl = impl;
    }

    public void open() {
        impl.open();
    }

    public void print() {
        impl.print();
    }

    public void close() {
        impl.close();
    }

    public final void display() {
        open();
        print();
        close();
    }
}

CountDisplay是对功能的扩展,增加multiDisplay功能。

// 功能扩展类
public class CountDisplay extends Display{

    public CountDisplay(DisplayImpl impl) {
        super(impl);
    }

    public void multiDisplay(int times) {
        open();
        for (int i = 0; i < times; i++) {
            print();
        }
        close();
    }
    
}

DisplayImpl定义了实现层次的接口

public interface DisplayImpl {

    public abstract void open();

    public abstract void close();

    public abstract void print();
    
}

StringDisplayImpl是实现类。

/** 实现类 */
public class StringDisplayImpl implements DisplayImpl {

    private String str;
    private int width;

    public StringDisplayImpl(String str) {
        this.str = str;
        this.width = str.length();
    }

    @Override
    public void open() {
        printLine();
    }

    @Override
    public void close() {
        printLine();
    }

    @Override
    public void print() {
        System.out.println("|" + str + "|");
    }
    
    private void printLine() {
        System.out.print("+");
        for (int i = 0; i < width; i++) {
            System.out.print("-");
        }
        System.out.println("+");
    }
}

通过以上实例我们不难看出,显示这个任务被切分成了展示方式(功能),展示内容(实现)两个部分,如果我们希望多次展示字符串内容,我们只需要将StringDisplayImpl委托给CountDisplay即可,而不需要再生成一个具体的类。让代码更加组件化,方便复用。即使实现或功能层次进行了修改也不会影响另一方的调用。

Strategy 模式

该设计模式可以帮助我们整体的替换算法的实现部分。具体是将算法与其他部分分离开来,只定义与算法有关的接口,并在程序中使用委托的方式使用算法。一个很典型的应用场景就是在游戏中更该AI的难度。
Strategy 模式类图

登场角色:

  1. Strategy:负责决定实现策略所必需的接口。
  2. ConcreteStrategy:负责实现具体的策略。
  3. Context:保存ConcreteStrategy角色实例,并使用它来实现需求。

example. 排序算法

定义的策略接口,要实现的业务仅仅是排序

public interface Sorter {
    void sort(Comparable[] data);
}

两种策略的具体实现

public class InsertionSort implements Sorter {
    @Override
    public void sort(Comparable[] data) {
        for (int i = 1; i < data.length; i++) {
            Comparable com = data[i];
            int j = i - 1;
            for (; j >= 0; j--) {
                if (com.compareTo(data[j]) < 0) {
                    data[j + 1] = data[j];
                } else {
                    break;
                }
            }
            data[j + 1] = com;
        }
    }
}

public class SelectionSort implements Sorter {

    @Override
    public void sort(Comparable[] data) {
        for (int i = 0; i < data.length - 1; i++) {
            int min = i;
            for (int j = i + 1; j < data.length; j++) {
                if (data[min].compareTo(data[j]) > 0) {
                    min = j;
                }
            }
            Comparable o = data[i];
            data[i] = data[min];
            data[min] = o;
        }
    }
    
}

用于调用策略实现业务的上下文。将策略接口委托给该类。

public class SortAndPrint {
    Comparable[] data;
    Sorter sorter;
    public SortAndPrint(Comparable[] data, Sorter sorter) {
        this.data = data;
        this.sorter = sorter;
    }
    public void execute() {
        print();
        sorter.sort(data);
        print();
    }
    public void print() {
        for (int i = 0; i < data.length; i++) {
            System.out.println(data[i] + ", ");
        }
        System.out.println("=====================");
    }
    
}

驱动

public class Main {
    public static void main(String[] args) {
        Integer[] nums = {3, 5, 1, 2, 0, 100, 53};
        SortAndPrint sortAndPrint = new SortAndPrint(nums, new InsertionSort());
        SortAndPrint sortAndPrint1 = new SortAndPrint(nums, new SelectionSort());
        sortAndPrint.execute();
        sortAndPrint1.execute();
    }
}

Composite 模式

使容器内容具有一致性,创造出递归结构的模式;最典型的就是 Java gui。
递归的本质是将问题拆分成一个个小问题,并将小问题的解合并在一起获得最终答案。多个小问题拼凑起来的解,又能作为一个解参与大问题的解决。这种将多个对象合并在一起当作一个对象处理的情况,我们称为多个和单个的一致性,有这种特质的问题,就适合用Composite模式解决。

 Composite 模式类图

登场角色:
Leaf:表示内容的角色,该角色中不能再放入其他角色。
Composite:表示容器的角色,可以在其中放入Leaf和Composite。
Component:使上述两个角色具有一致性的角色。

/** 用于体现一致性的类 */
public abstract class Entry {
    public abstract String getName();
    public abstract int getSize();
    public Entry add(Entry entry) throws UnsupportedOperationException {
        throw new UnsupportedOperationException("Entry cannot be added");
    }
    public void printList() {
        printList("");
    }
    protected abstract void printList(String prefix);
    @Override
    public String toString() {
        return getName() + " (" + getSize() + ")";
    }
}
public class File extends Entry {

    private String name;
    private int size;

    public File(String name, int size) {
        this.name = name;
        this.size = size;
    }

    @Override
    public String getName() {
        return name;
    }

    @Override
    public int getSize() {
        return size;
    }

    @Override
    protected void printList(String prefix) {
        System.out.println(prefix + "/" + this);
    }
    
}
public class Directory extends Entry {

    private String name;
    private List<Entry> children;

    public Directory(String name) {
        this.name = name;
        children = new ArrayList<Entry>();
    }

    @Override
    public String getName() {
        return name;
    }

    @Override
    public int getSize() {
        int size = 0;
        for (Entry entry : children) {
            size += entry.getSize();
        }
        return size;
    }

    @Override
    protected void printList(String prefix) {
        System.out.println(prefix + "/" + this);
        for (Entry entry : children) {
            entry.printList(prefix + "/" + name);
        }
    }

    @Override
    public Entry add(Entry entry) throws UnsupportedOperationException {
        children.add(entry);
        return this;
    }
    
}

Decorator 模式

不断为对象添加装饰的设计模式。
首先要明确的是,如果一个对象想要被复数个装饰物装饰,那么被装饰物与装饰物必须具备一致性。举例来说,黄金在被制作成首饰后,它就失去了一般等价物的特质,这显然不是一种装饰,因为黄金与首饰不具备一致性。
接口的透明性:在编程语言中,要想体现两个类的一致性,最常用的方法就是继承。这样,即使被装饰物被包装起来了,它的API接口也不会被隐藏,这称为接口的透明性。
同时,我们使用委托的方式,让装饰物提出的要求转交给被装饰物去处理,这样方便我们在不修改被装饰物的前提下为它添加功能。并且当我们需要进行功能扩展时,也不会导致子类的爆炸式增长,导致类的功能层次过深。
Decorator 模式类图

登场角色:
Component:增加功能的核心角色。
ConcreteComponent:Component实现类,尚未被装饰的对象。
Decorator:该角色与Component有接口一致性,并在内部保存被装饰的对象。
ConcreteDecorator:Decorator的实现类。

Component

public abstract class Display {
    public abstract int getColumns();
    public abstract int getRows();
    public abstract String getRowText(int row);
    public final void show() {
        for (int i = 0; i < getRows(); i++) {
            System.out.println(getRowText(i));
        }
    }
}

Decorate,字段和构造器全部声明为protected类型,避免子类之外的类调用。

public abstract class Border extends Display {
    protected Display display;
    protected Border(Display display) {
        this.display = display;
    }
}
public class StringDisplay extends Display {

    private String str;

    public StringDisplay(String str) {
        this.str = str;
    }

    @Override
    public int getColumns() {
        return str.length();
    }

    @Override
    public int getRows() {
        return 1;
    }

    @Override
    public String getRowText(int row) {
        if (row == 0) {
            return str;
        }
        return null;
    }
    
}
public class SideBorder extends Border {

    private char borderChar;

    public SideBorder(Display display, char borderChar) {
        super(display);
        this.borderChar = borderChar;
    }

    @Override
    public int getColumns() {
        return display.getColumns() + 2;
    }

    @Override
    public int getRows() {
        return display.getRows();
    }

    @Override
    public String getRowText(int row) {
        return borderChar + display.getRowText(row) + borderChar;
    }
    
}
public class FullBorder extends Border {

    public FullBorder(Display display) {
        super(display);
    }

    @Override
    public int getColumns() {
        return display.getColumns() + 2;
    }

    @Override
    public int getRows() {
        return display.getRows() + 2;
    }

    @Override
    public String getRowText(int row) {
        if (row == 0) {
            return "+" + makeLine('-', display.getColumns()) + "+";
        } else if (row == display.getRows() + 1) {
            return "+" + makeLine('-', display.getColumns()) + "+";
        } else {
            return "|" + display.getRowText(row - 1) + "|";
        }
    }

    private String makeLine(char ch, int count) {
        StringBuffer buf = new StringBuffer();
        for (int i = 0; i < count; i++) {
            buf.append(ch);
        }
        return buf.toString();
    }
    
}

Visitor 模式

在程序中,我们将元素存放在数据结构中。通常我们也会把对应的处理也存放在里面,但如果处理有多种,每增加一种新的处理,就要修改数据结构的代码。
如果我们将数据结构和处理分离开来,编写一个访问者类来访问数据结构中的元素,并且进行处理,那每当要增加处理时,就只需要增添新的访问者。
图的处理就用到了这种思想,Graph类专门存储图的数据,GraphPath类执行图论算法。

Visitor 模式类图

登场角色:
Visitor:负责对数据结构中每一个具体的元素声明一个用于访问的方法。该方法中也包含着元素对应的处理。
ConcreteVIsitor:实现如何处理每一个ConcreteElement角色。
Element:表示Visitor的访问对象,声明了接受访问者的accept方法。
ConcreteElement:实现Element定义的接口。
ObjectStructure:负责处理Element角色的集合。

有了以上基础,不难看出,每当我们需要添加一种新的处理方法时,只需要添加一个访问者即可。但如果我们想修改数据结构中的元素,则必须连带着修改所有的访问者。同时,visitor可以处理元素的前提条件是该元素向他暴露了足够多的信息。而这又带来了新的问题,如果暴露了不该暴露的信息,后续的修改将十分艰难。

访问者接口,其中声明了所有元素的访问方法。

public interface Visitor {
    void visit(File file);
    void visit(Directory directory);
}

元素接口,声明了接受访问者的方法。

public interface Element {
    void accept(Visitor visitor);
}

这里使用了Composite模式,这是体现一致性的类。

public abstract class Entry implements Element {
    public abstract String getName();
    public abstract int getSize();
    public Entry add(Entry entry) {
        throw new UnsupportedOperationException("entry cannot be added");
    }
    public Iterator<Entry> iterator() {
        throw new UnsupportedOperationException("entry cannot be iterator");
    }
    public String toString() {
        return getName() + " (" + getSize() + ")";
    }
}

对于每一个元素的子类,其accept方法都是允许让visitor将自身作为参数调用访问方法。

public class File extends Entry {

    private String name;
    private int size;

    public File(String name, int size) {
        this.name = name;
        this.size = size;
    }

    @Override
    public String getName() {
        return name;
    }

    @Override
    public int getSize() {
        return size;
    }
    
    @Override
    public void accept(Visitor visitor) {
        visitor.visit(this);
    }
}
public class Directory extends Entry {

    private String name;
    private List<Entry> children;

    public Directory(String name) {
        this.name = name;
        children = new ArrayList<Entry>();
    }

    @Override
    public String getName() {
        return name;
    }

    @Override
    public int getSize() {
        int size = 0;
        for (Entry entry : children) {
            size += entry.getSize();
        }
        return size;
    }

    @Override
    public Entry add(Entry entry) throws UnsupportedOperationException {
        children.add(entry);
        return this;
    }


    @Override
    public Iterator<Entry> iterator() {
        return children.iterator();
    }

    @Override
    public void accept(Visitor visitor) {
        visitor.visit(this);
    }
    
}

这是访问者的实现类,其中实现了每一种元素的访问方法。

public class ListVisitor implements Visitor {

    private String currentdir = "";

    @Override
    public void visit(File file) {
        System.out.println(currentdir + "/" + file);
    }

    @Override
    public void visit(Directory directory) {
        System.out.println(currentdir + "/" + directory);
        String savedir = currentdir;
        currentdir = currentdir + "/" + directory.getName();
        Iterator<Entry> iterator = directory.iterator();
        while (iterator.hasNext()) {
            Entry entry = iterator.next();
            entry.accept(this);
        }
        currentdir = savedir;
    }
    
}

和一般的递归思路不同,visitor模式中用到了双重分发,visit和accept相互之间递归调用。类似于V向A申请续杯,A同意续杯,V喝完后继续申请续杯。这里递归的终止条件是访问者到达了原子类(不可再拆分的文件类)。

Chain of repository 模式

当程序进行某个请求的处理,但又无法确定由哪个对象来进行处理时,就可以使用责任链模式。将请求在预定好的责任链中传播,链中的每一个对象接受到请求之后,如果无法处理,就传递给下一个对象,否则就地处理请求。
这种模式的一大特征就是弱化了请求者和处理者之间的关系。请求者只需将请求提交给首个处理者,该请求就会在职责链中传播,直至被处理或抛弃。整个过程中,请求者不需要了解职责链中对象的具体职责,提高了自身作为组件的独立性。(了解的越多,就会导致自身牵扯越多对象,无法独立出来)
除此之外,职责链也可以进行动态的调整,处理方式的灵活性提高。每一个处理者也只需要关注自己的工作。
不过比起有对象统一调度请求处理的情况,采用该设计模式会损失一定的效率。

Chain of repository 模式类图

登场角色:

  1. Handler:定义了处理请求的接口,并且知道下一个处理者是谁,如果自己无法处理请求,就交给下一个去做。
  2. ConcreteHandler:处理请求的具体角色。

处理器抽象类

public abstract class Support {
    private String name;
    private Support next;

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

    // 设置下一个处理者,并返回下一个处理者对象,方便链式调用
    public Support setNext(Support next) {
        this.next = next;
        return next;
    }

    @Override
    public String toString() {
        return "Support [name= " + name + "]";
    }

    // 解决问题的步骤
    // 1. 先尝试自己解决
    // 2. 解决不了就交给下一个处理者
    // 3. 如果没有后继者了就放弃
    public final void support(Trouble problem) {
        if (resolve(problem)) {
            done(problem);
        } else if (next != null) {
            next.support(problem);
        } else {
            fail(problem);
        }
    }

    // 解决问题的方法,使用proetcted防止外部类调用
    protected abstract boolean resolve(Trouble problem);

    protected void done(Trouble problem) {
        System.out.println(problem + " is resolved by" + this);
    }

    protected void fail(Trouble problem) {
        System.out.println(problem + " cannot resolved");
    }
}
public class SpecialSupport extends Support{

    private int num;

    public SpecialSupport(String name, int num) {
        super(name);
        this.num = num;
    }

    @Override
    protected boolean resolve(Trouble problem) {
        return problem.getNum() == num;
    }
    
}
public class LimitSupport extends Support {
    private int limit;

    public LimitSupport(int limit, String name) {
        super(name);
        this.limit = limit;
    }

    @Override
    protected boolean resolve(Trouble problem) {
        return problem.getNum() < limit;
    }
    
}

Facade 模式

随着时间的推移,程序必然会变得越来越复杂,类与类之间的关系也会变得极其复杂。这是我们就可以创建一个窗口来处理程序中类与类之间的调用,用户只需向窗口发出请求,窗口就会自行去处理类的调用。
从以上描述中不难看出,Facade模式的目的就是减少程序的接口,使得调用关系能够更加清晰,除此之外,API的减少也意味着程序与外部的关联度下降,能够更方便的作为组件使用。Facade模式同样可以递归地使用,一个窗口去调用其他的窗口,从而进一步减少API。
不过在设计类和包时,我们也要考虑类的可见性,如果公开的方法过多,就会导致后续的修改变得困难。
Facade 模式类图

登场角色:
Facade:构成系统的许多其他角色的窗口,向系统外部提供高层接口。
构成系统的许多其他角色:这些角色各自完成自己的工作,并不知道Facade的存在。Facade通过调用其他角色来完成指定的任务。

作为系统内部的类,将公开度设置为默认,防止外部系统直接调用。

class Database {
    private Database() {}
    public static Properties getProperties() {
        Properties properties = new Properties();
        properties.setProperty("1@qq.com", "cxc");
        return properties;
    }
}
class HTMLWriter {
    private Writer writer;
    
    public HTMLWriter(Writer writer) {
        this.writer = writer;
    }

    public void title(String title) throws IOException {
        writer.write("<html><head><title>");
        writer.write(title + "</title></head><body>");
        writer.write("<h1>" + title + "</h1>");
    }

    public void paragraph(String msg) throws IOException {
        writer.write("<p>" + msg + "</p>");
    }

    public void link(String href, String caption) throws IOException {
        paragraph("<a href=\"" + href + "\">" + caption + "</a>");
    }

    public void mailto(String mailaddr, String username) throws IOException {
        link("mailto:" + mailaddr, username);
    }

    public void close() throws IOException {
        writer.write("</body>");
        writer.write("</html>");
        writer.close();
    }
}

本例中的窗口

public class PageMaker {
    private PageMaker() {}

    public static void makeWelcomePage(String mailaddr, String filename) throws IOException {
        Properties properties = Database.getProperties();
        String username = properties.getProperty(mailaddr);
        HTMLWriter writer = new HTMLWriter(new FileWriter(filename));
        writer.title("Welcome to " + username + "'s page");
        writer.paragraph(username + "欢迎来到" + username + "的主页");
        writer.mailto(mailaddr, username);
        writer.close();
    }
}

Observer 模式

当被观察者的状态发生改变时,将通知给观察者。被观察者维护一个存储观察者的列表,当有观察者加入该列表时,就表示这个观察者订阅了被观察者,此后被观察者的状态更新都会发送给它,因此该模式又称为发布订阅。在Subject角色中可以注册多个Observer角色,先注册的会被先调用,因此需要注意这些观察者的调用顺序。
在该设计模式中也同样出现了可替换性,无论是观察者还是被观察者都无需了解对方的具体实现,只需要清楚对方实现了指定的接口并且可以通过接口获得想要的数据。这就方便我们在之后的扩展中替换实现类。
传递更新的方式也有许多种:

void update(Generator gen);
void update(Generator gen, int num);
void update(int num);
  1. 只传递Subject角色,Observer从Subject中获取数据。
  2. 一并传送Observer需要的数据,这样虽能免去Observer提取数据的麻烦,但会导致Subject了解到Observer要处理的内容,这会导致Subject必须对Observer负责,降低程序灵活性。
  3. 只传递要处理的数据,但这样会写死数据类型,不方便观察多个Subject角色。

Observer 模式类图

登场角色:

  1. Subject:观察对象,定义了注册和删除观察者的方法。并且声明了获取当前状态的方法。
  2. ConcreteSubject:具体的观察对象,当自身状态发生改变后会通知已经注册的Observer角色。
  3. Observer:负责接收来自Subject角色的状态变化通知。
  4. ConcreteObserver:具体的观察者,当他的update方法被调用后会去获取Subject的最新状态。
public interface Observer {
    void update(NumberGenerator generator);
}
public abstract class NumberGenerator {
    private List<Observer> observers = new ArrayList<Observer>();
    public void addObserver(Observer observer) {
        observers.add(observer);
    }
    public void deleteObserver(Observer observer) {
        observers.remove(observer);
    }
    public void notifyObservers() {
        for (Observer observer : observers) {
            observer.update(this);
        }
    }
    public abstract int getNum();
    public abstract void execute();
}
public class RandomNumberGenerator extends NumberGenerator {
    private Random random = new Random();
    private int num;
    public int getNum() {
        return num;
    }
    public void execute() {
        num = random.nextInt(10);
        notifyObservers();
    }
}
public class DigitObserver implements Observer {

    @Override
    public void update(NumberGenerator generator) {
        System.out.println(generator.getNum());
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
    
}
public class GraphObserver implements Observer {

    @Override
    public void update(NumberGenerator generator) {
        int cnt = generator.getNum();
        for (int i = 0; i < cnt; i++) {
            System.out.print("*");
        }
        System.out.println();
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
    
}

Mediator 模式

当程序中交互的组件过多时,扁平化的管理结构就不用了,在同一级的成员显然不会认为其他成员有权利命令自己。这时候需要一个中立的仲裁者来统一调度程序的运行。组员向仲裁者报告,仲裁者向组员下达指令。
OOP思想可以帮助我们分散处理各个类的执行逻辑,但如果一段逻辑过于分散,无论是使用还是修改都会带来巨大的困难,这时候就需要Mediator模式来将他们集中起来。
除此之外,过多的类也会导致通信线路的增加,导致程序结构变得异常复杂。n个类之间相互通信产生的通信线路数量为$C_n^2$,这样的程序结构看上去就很难理清调用结构,复用性也很低。而使用Mediator模式,每个组件都只需要与Mediator通信,大大简化了程序结构。

Mediator 模式类图

登场角色:

  1. Mediator:负责定义与Colleague角色进行通信和做出决定的接口
  2. ConcreteMediator:负责实现上述接口
  3. Colleague:负责定义与Mediator角色进行通信的接口
  4. ConcreteColleague:负责实现上述接口
public interface Colleague {
    void setMediator(Mediator mediator);
    void setColleagueEnabled(boolean enabled);
}
public interface Mediator {
    void createColleagues();
    void colleagueChanged();
}
public class ColleagueButton extends Button implements Colleague {

    private Mediator mediator;

    public ColleagueButton(String caption) {
        super(caption);
    }

    @Override
    public void setMediator(Mediator mediator) {
        this.mediator = mediator;  
    }

    // Mediator 下达启用/禁用的指示
    @Override
    public void setColleagueEnabled(boolean enabled) {
        setEnabled(enabled);
    }
    
}
public class ColleagueCheckbox extends Checkbox implements ItemListener, Colleague{

    private Mediator mediator;

    public ColleagueCheckbox(String caption, CheckboxGroup group, boolean state) {
        super(caption, group, state);
    }

    @Override
    public void setMediator(Mediator mediator) {
       this.mediator = mediator;
    }

    @Override
    public void setColleagueEnabled(boolean enabled) {
        setEnabled(enabled);
    }

    @Override
    public void itemStateChanged(ItemEvent e) {
        mediator.colleagueChanged();
    }
    
}
public class ColleagueTextField extends TextField implements Colleague, TextListener {
    private Mediator mediator;

    public ColleagueTextField(String text, int columns) {
        super(text, columns);
    }

    @Override
    public void setMediator(Mediator mediator) {
        this.mediator = mediator;
    }

    @Override
    public void setColleagueEnabled(boolean enabled) {
        setEnabled(enabled);
        setBackground(enabled ? Color.white : Color.lightGray);
    }

    // 文字发生变化时通知仲裁者
    public void textValueChanged(TextEvent event) {
        mediator.colleagueChanged();
    }
}
public class LoginFrame extends Frame implements ActionListener, Mediator{

    private ColleagueCheckbox checkGuest;
    private ColleagueCheckbox checkLogin;
    private ColleagueTextField textUser;
    private ColleagueTextField textPassword;
    private ColleagueButton buttonOK;
    private ColleagueButton buttonCancel;

    public LoginFrame(String title) {
        super(title);
        setBackground(Color.lightGray);
        setLayout(new GridLayout(4, 2));
        add(checkGuest);
        add(checkLogin);
        add(new Label("Username: "));
        add(textUser);
        add(new Label("Password: "));
        add(textPassword);
        add(buttonOK);
        add(buttonCancel);
        colleagueChanged();
        pack();
        setVisible(true);
    }

    // 初始化组件
    @Override
    public void createColleagues() {
        CheckboxGroup g = new CheckboxGroup();
        checkGuest = new ColleagueCheckbox("Guest", g, true);
        checkLogin = new ColleagueCheckbox("Login", g, false);
        textUser = new ColleagueTextField("", 10);
        textPassword = new ColleagueTextField("", 10);
        textPassword.setEchoChar('*');
        buttonOK = new ColleagueButton("OK");
        buttonCancel = new ColleagueButton("Cancel");
        checkGuest.setMediator(this);
        checkLogin.setMediator(this);
        textUser.setMediator(this);
        textPassword.setMediator(this);
        buttonOK.setMediator(this);
        buttonCancel.setMediator(this);
        checkGuest.addItemListener(checkGuest);
        checkLogin.addItemListener(checkLogin);
        textUser.addTextListener(textUser);
        textPassword.addTextListener(textPassword);
        buttonOK.addActionListener(this);
        buttonCancel.addActionListener(this);
    }

    // 接受到状态变化的通知后判断各组件的启用/禁用状态
    @Override
    public void colleagueChanged() {
        if (checkGuest.getState()) {
            textUser.setColleagueEnabled(false);
            textPassword.setColleagueEnabled(false);
            buttonOK.setEnabled(true);
        } else {
            textUser.setColleagueEnabled(true);
            userpassChanged();
        }
    }

    private void userpassChanged() {
        if (textUser.getText().length() > 0) {
            textPassword.setColleagueEnabled(true);
            if (textPassword.getText().length() > 0) {
                buttonOK.setColleagueEnabled(true);
            } else {
                buttonOK.setColleagueEnabled(false);
            }
        } else {
            textPassword.setColleagueEnabled(false);
            buttonOK.setColleagueEnabled(false);
        }
    }

    @Override
    public void actionPerformed(ActionEvent e) {
        System.out.println(e.toString());
        System.exit(0);
    }
    
}

通过上述示例可以看出,所有的组件状态变化逻辑全部由仲裁者执行,也就是说,组件可以完全脱离出业务,可以被轻易的复用。但与之相对的,仲裁者的执行逻辑与业务紧密耦合,相应的代码无法被复用,针对不同的业务需要实现不同的仲裁者。

Memento 模式

试想我们希望程序具备以下功能:撤销,重做,历史记录,快照。这时,我们就需要一个类似存档点的类来储存某个阶段对象的信息。不过如果想要恢复实例,就需要可以自由访问实例内部结构的权限;如果有关的代码分散在程序的各个地方,就会导致程序变得难以维护。这时,就需要Memento模式来维护类的封装性。
我们可以借助游戏存档的过程来理解该设计模式。将玩家当作驱动类,存档当作程序中存储快照的类,游戏进程代表当前的程序。接下来思考这三个角色各自的职责,玩家可以游玩游戏,决定什么时候存档,什么时候恢复存档。游戏进程可以生成存档,让游戏状态恢复到存档记录的状态。里面比较重要的一点是,只有游戏进程能自由操作存档内部的所有信息,玩家只能根据存档透露的有限的状态来判断是否要存档或是选取哪一个存档。如果玩家能够随意修改存档信息,那就代表程序的封装性被破环了。
想要保证封装性不被破坏,最重要的一点就是,确保无法在game包外部改变存档内部的状态,但同时有需要透露给玩家足够的信息,方便判断存档的选择。从此引申出两个新的概念:

  1. 宽接口:所有用于获取恢复对象状态信息的方法集合。由于他们会暴露Memento内部的所有信息,因此只有生成他们的角色可以使用。
  2. 窄接口:为用户提供一部分存档的信息,他们的可见性是public。
    Memento 模式类图

登场角色:

  1. Originator:在保存自己的最新状态时生成Memento角色,将以前的Memento传给该角色时,会将自己恢复至生成该Memento的状态。
  2. Memento:将Originator的内部信息保存在一起,并且不会向外界公开这些信息。
  3. Caretaker:决定Originator的保存与恢复,会一直保存Memento实例,但只能通过窄接口访问有限的内部信息。
public class Memento {

    int money;
    List<String> fruits;

    // 窄接口
    public int getMoney() {
        return money;
    }

    // 可见性设为默认,不允许包外的类创建
    Memento(int money) {
        this.money = money;
        this.fruits = new ArrayList<String>();
    }

    // 宽接口
    void addFruits(String fruit) {
        fruits.add(fruit);
    }

    List<String> getFruits() {
        return new ArrayList<>(fruits);
    }
}
public class Gamer {
    private int money;
    private List<String> fruits = new ArrayList<String>();
    private Random random = new Random();
    private static String[] fruitname = {
        "苹果", "葡萄", "香蕉", "橘子"
    };

    public Gamer(int money) {
        this.money = money;
    }

    public int getMoney() {
        return money;
    }

    public void bet() {
        int dice = random.nextInt(6) + 1;
        if (dice == 1) {
            money += 100;
            System.out.println("持有的金钱增加了 money=" + money);
        } else if (dice == 2) {
            money /= 2;
            System.out.println("持有的金钱减半了 money=" + money);
        } else if (dice == 6) {
            String f = getFruit();
            System.out.println("获得了水果 " + f);
            fruits.add(f);
        } else {
            System.out.println("无事发生");
        }
    }

    public Memento createMemento() {
        Memento memento = new Memento(money);
        fruits.forEach((s) -> {
            memento.addFruits(s);
        });
        return memento;
    }

    public void restoreMemento(Memento memento) {
        this.money = memento.getMoney();
        this.fruits = memento.getFruits();
    }

    @Override
    public String toString() {
        return "Gamer [fruits=" + fruits + ", money=" + money + "]";
    }

    private String getFruit() {
        String prefix = "";
        if (random.nextBoolean()) {
            prefix = "好吃的";
        }
        return prefix + fruitname[random.nextInt(fruitname.length)];
    }
}
public class Main {
    public static void main(String[] args) throws InterruptedException {
        Gamer gamer = new Gamer(100);
        Memento memento = gamer.createMemento();
        for (int i = 0; i < 100; i++) {
            System.out.println("=========" + i + " =====");
            System.out.println("当前状态 " + gamer);
            gamer.bet();
            System.out.println("持有金钱为 " + gamer.getMoney());
            if (gamer.getMoney() > memento.getMoney()) {
                System.out.println("所持金钱增加,保存游戏状态");
                memento = gamer.createMemento();
            } else if (gamer.getMoney() < memento.getMoney() / 2) { 
                System.out.println("所持金钱大幅减少,回档");
                gamer.restoreMemento(memento);
            }
            Thread.sleep(100);
        }
    }
}

State 模式

该设计模式的核心思想是将状态当作类来处理,最明显的优势就是减少if语句的使用。在之前学习MIT的软件工程课程中就有尝试过用类表示某数据结构的组成部分以此来避免使用instanceof
在程序中存在诸多状态的时候,要想判断此时执行哪个状态对应的方法,就需要使用大量的if来进行状态的判断。如果此时设置一个表示状态的接口,让每一个状态实现他们对应的处理,那么在程序使用的时候,就只需要在特定的情况完成状态迁移,其余时候都可以直接调用需要的处理方法而无需再去判断目前处于什么状态。这也算是一种分治思想,我们将复杂的状态判断分解成一个个类的实现,在程序调用时就可以通过对应的类型寻找到正确的处理。
同时,使用State模式后,我们也可以轻易的完成状态的扩充,但和之前学习的诸多具有类似优势的设计模式一样,在进行状态内部功能的扩充时会产生不小的麻烦。不过在有大量状态要判断的情况下,这个缺点带来的麻烦显然不如大量的if语句。
State 模式类图

登场角色:

  1. State:负责表示状态,定义了所有依赖于状态的方法。
  2. ConcreteState:负责表示各个具体的状态。
  3. Context:持有表示当前具体状态的ConcreteState,并且提供了给外部调用者使用的State模式的接口。
public interface State {
    void doClock(Context context, int hour);
    void doUse(Context context);
    void doAlarm(Context context);
    void doPhone(Context context);
}
public interface Context {
    void setClock(int hour);
    void changeState(State state);
    void callSecurityCenter(String msg);
    void recordLog(String msg);
}
public class DayState implements State {

    private static DayState singleton = new DayState();
    private DayState() {}

    public static DayState getInstance() {
        return singleton;
    }

    @Override
    public void doClock(Context context, int hour) {
        if (hour < 9 || hour >= 17) {
            context.changeState(NightState.getInstance());
        }
    }

    @Override
    public void doUse(Context context) {
        context.recordLog("使用金库,白天");
    }

    @Override
    public void doAlarm(Context context) {
        context.callSecurityCenter("按下警铃 白天");
    }

    @Override
    public void doPhone(Context context) {
        context.callSecurityCenter("正常通话,白天");
    }
    
    @Override
    public String toString() {
        return "白天";
    }
}
public class NightState implements State {

    private static NightState singleton = new NightState();
    private NightState() {}
    public static NightState getInstance() {
        return singleton;
    }

    @Override
    public void doClock(Context context, int hour) {
        if (hour >= 9 || hour < 17) {
            context.changeState(DayState.getInstance());
        }
    }

    @Override
    public void doUse(Context context) {
        context.callSecurityCenter("紧急,晚上使用金库");
    }

    @Override
    public void doAlarm(Context context) {
        context.callSecurityCenter("按下警铃(晚上)");
    }

    @Override
    public void doPhone(Context context) {
        context.recordLog("晚上通话录音");
    }
    
    @Override
    public String toString() {
        return "晚上";
    }
}
public class SafeFrame extends Frame implements ActionListener, Context{

    private TextField textClock = new TextField(60);
    private TextArea textScreen = new TextArea(10, 60);
    private Button buttonUse = new Button("使用金库");
    private Button buttonAlarm = new Button("按下警铃");
    private Button buttonPhone = new Button("正常通话");
    private Button buttonExit = new Button("结束");

	private State state = DayState.getInstance();

	public SafeFrame() {
		// ... 构造gui界面
	}

    @Override
    public void setClock(int hour) {
        String clockstring = "现在时间是";
        if (hour < 10) {
	        clockstring += "0" + hour + ":00";
        } else {
	        clockstring += hour + ":00";
        }
        System.out.println(clockstring);
        textClock.setText(clockstring);
        state.doClock(this, hour);
    }

    @Override
    public void changeState(State state) {
        System.out.println("从" + this.state + "状态变为了" + state + "状态");
        this.state = state;
    }

    @Override
    public void callSecurityCenter(String msg) {
        textScreen.append("call! " + msg + "\n");
    }

    @Override
    public void recordLog(String msg) {
        textScreen.append("record ... " + msg + "\n");
    }

    @Override
    public void actionPerformed(ActionEvent e) {
        if (e.getSource() == buttonUse) {
	        state.doUse(this);
        } else if (e.getSource() == buttonAlarm) {
	        state.doAlarm(this);
        } else if (e.getSource() == buttonPhone) {
	        state.doPhone(this);
        } else if (e.getSource() == buttonExit) {
	        System.exit(0);
        } else {
	        System.out.println("?");
        }
    }
    
}

这个设计模式还有一个注意点就是由谁来管理状态迁移,在上面的例子中,我们让每一个具体状态类自己去判断当前的时间是否需要切换状态,如果需要修改就通知context。这种处理方式将什么时候切换类的信息集中到了一个类中,而缺点就是让每一个具体实现类都不可避免的要了解到其他类的状态信息,增加了耦合度。当然也可以将状态迁移的任务交给context来完成,但这样就要求context必须知道所有的具体状态类。

Flyweight 模式

在英语中flyweight表示轻量级的意思,而在程序中,他指的就是减少对象的内存占用。通常情况下,我们会使用new来获得对象实例,但如果对象本身占用的空间比较大,若总是使用new来创建对象,就会消耗大量的内存空间。Flyweight模式就是通过共享实例来避免new出实例,减少内存的占用,同时也因为不需要生成新的实例,一定程度上提升了运行速度。
最典型的使用该设计模式的技术就是池化技术,用池来管理现有的昂贵资源,接收到请求后,尽量使用现有的资源。
不过一旦提到共享,我们就必须要想到,如果对某个实例进行了修改,那么修改的效果会在多个地方产生影响。这违背了我们在并发状况下的不共享原则,需要谨慎选择哪些信息可以被共享哪些不可以。
Intrinsic:表示应当被共享的信息,该单词的英文意思是“本质的”,“固有的”。从意思上我们就可以看出,这类信息并不依赖与某个单独的实例,它是某个类的共同特质,且不会被更改。这一类信息属于可以被共享的信息。
Extrinsic:表示不应当被共享的信息。与上面的Intrinsic相对的,这类信息依托于实例的状态,会随着状况改变而产生变化。在并发环境下,状况的变化会非常剧烈,因此这类信息的改动就会非常频繁,明显会对程序造成不好的影响。

Flyweight 模式类图

登场角色:

  1. Flyweight:按照通常方式生成会占用大量空间的类,即要被共享实例的类。
  2. FlyweightFactory:生成轻量级角色的工厂,并在其中实现共享实例。
public class BigChar {
    private char charname;
    private String fontdata;

    public BigChar(char charname) {
        this.charname = charname;
        try (BufferedReader reader = new BufferedReader(new FileReader(charname + ".txt"))) {
            String line;
            StringBuffer buffer = new StringBuffer();
            while ((line = reader.readLine()) != null) {
                buffer.append(line);
                buffer.append("\n");
            }
            this.fontdata = buffer.toString();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

    }

    public void print() {
        System.out.println(fontdata);
    }
}
public class BigCharFactory {
    private HashMap<String, BigChar> pool = new HashMap<String, BigChar>();
    private static BigCharFactory instance = new BigCharFactory();
    private BigCharFactory() {}
    public static BigCharFactory getInstance() {
        return instance;
    }

    // 生成/共享实例
    // 这里要保证线程安全,避免生成两个实例
    public synchronized BigChar getBigChar(char charname) {
        BigChar bc = pool.get("" + charname);
        if (bc == null) {
            bc = new BigChar(charname);
            pool.put("" + charname, bc);
        }
        return bc;
    }
}
public class BigString {
	private BigChar[] bigchars;
	public BigString(String string) {
		bigchars = new BigChar[string.length];
		BigCharFactory factory = BigCharFactory.getInstance();
		for (int i = 0; i < bigchars.length; i++) {
			bigchars[i] = factory.getBigChar(string.charAt(i));
		}
	}

	public void print() {
		for (int i = 0; i < bigchars.length; i++) {
			bigchars[i].print();
		}
	}
}

垃圾回收:Java可以通过new关键字分配内存空间,当内存不足时就会开始垃圾回收处理。在堆空间中寻找没有被使用的实例,并释放掉他们。这里gc判断是否为垃圾的依据是:有没有其他对象引用了这个实例。在上面的示例中,生成的所有实例都交给pool托管,因此这些实例会长期贮存在内存中,但如果程序要长期运行或是以有限的内存运行,为了避免发生OOM,就要显示地去解除一些引用让gc可以回收他们,这时就需要注意不能把正被共享的实例回收了。

Proxy 模式

Proxy模式指代理模式,在面对不需要本体进行工作的请求时,将它交给代理去处理;只有当碰到代理职能之外的任务时,才交给本体去处理。
代理模式最典型的就是HTTP代理,如果接收到的请求页面在代理服务器中有缓存,就由代理服务器直接处理,如果没有缓存,才交给真正的服务器处理。所以代理模式的本质就是:只有在必要的时候采取劳烦本体。由于在实际环境中,本体象征的一般都是十分昂贵的资源,在中间添加一层代理,可以有效的提升程序运行的效率以及程序本身的健壮性。
Proxy 模式类图

登场角色:

  1. Subject:定义了使Proxy和RealSubject角色之间保持一致性的接口,并且可以让用户不必在意使用的究竟是代理还是真实角色。
  2. Proxy:代理会尽可能处理接收到的请求,只有当自己不能处理时,才会生成RealSubject角色并将工作转交给它。
  3. RealSubject:实际的主体。
    将代理人和本人划分出来,可以更加明确的指出哪些由代理人负责完成,哪些由本人负责完成,更好的将二者组件化。并且可以极大程度上避免修改本人的相关代码。

各种Proxy模式

  1. Virtual Proxy:只有当真正需要实例的时候才会生成和初始化实例。
  2. Remote Proxy:远程调用
  3. Access Proxy:在调用RealSubject角色的功能时设置访问限制,相当于给代理划分权限级别。

保持代理与本体一致性的接口

public interface Printable {
    void setPrintName(String name);
    String getPrintName();
    void print(String str);
}

本体

public class Printer implements Printable {
    private String name;
    public Printer() {
        heavyJob("Printer实例生成中");
    }

    public Printer(String name) {
        this.name = name;
        heavyJob("Printer实例生成中[" + name + "]");
    }

    public void setPrintName(String name) {
        this.name = name;
    }

    public String getPrintName() {
        return name;
    }

    public void print(String str) {
        System.out.println("============" + name + "============");
        System.out.println(str);
    }

    private void heavyJob(String msg) {
        System.out.print(msg);
        for (int i = 0; i < 5; i++) {
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            System.out.print('.');
        }
        System.out.println("生成完毕");
    }

}

代理人。这里设置本体名称以及生成本体的操作都需要加上锁,避免在有多个线程访问的情况下出现更新丢失或生成多个实例的情况。
此外,这里还是用反射来生成指定的本体,以此解除本体与代理的耦合。

public class PrinterProxy implements Printable {

    private String name;
    private Printable real;
    private String className;

    public PrinterProxy() {}

    public PrinterProxy(String name, String className) {
        this.name = name;
        this.className = className;
    }

    @Override
    public synchronized void setPrintName(String name) {
        if (real != null) {
            real.setPrintName(name);
        }
        this.name = name;
    }

    @Override
    public String getPrintName() {
        return name;
    }

    @Override
    public void print(String str) {
        realize();
        real.print(str);
    }

    private synchronized void realize() {
        try {
            if (real == null) {
                real = (Printable) Class.forName(className).newInstance();
                real.setPrintName(name);
            }
        } catch (InstantiationException | IllegalAccessException | ClassNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
    
}

Command 模式

一个类在调用自己或其他类的方法时,虽然调用结果会反映在对象的状态中,但不会留下工作的历史记录。
但如果我们使用类来表示相关的命令就可以很轻易的实现记录工作的历史记录,除此之外我们也可以随时执行过去的命令或是将多个过去的命令整合为一个新命令执行。
有时,Command也被称为事件(event)。

Command模式类图

登场角色:

  1. Command:Command角色负责定义命令的接口。
  2. ConcreteCommand:负责实现在Command角色中定义的接口
  3. Receiver:Command角色执行命令时的对象,负责接收命令的角色
  4. Client:负责生成ConcretCommand并分配Receiver角色
  5. Invoker:开始执行命令的角色,会调用Command角色定义的接口。

Command接口

public interface Command {
    void execute();
}

表示复数个命令的集合,这里使用栈存储历史命令方便后续撤销操作。

public class MarcoCommand implements Command {

    private Stack<Command> commands = new Stack<Command>();

    @Override
    public void execute() {
        for (Command c : commands) {
            c.execute();
        }
    }

    public void append(Command command) {
        if (command == this) {
            return;
        }
        commands.push(command);
    }
    
    public void undo() {
        if (!commands.isEmpty()) {
            commands.pop();
        }
    }

    public void clear() {
        commands.clear();
    }
}

命令接收者的接口

public interface Drawable {
    void draw(int x, int y);
}

命令的一个具体实现类,其中保存了接收者的实例,当该命令要执行时就通知对应的接收者。
由于这里使用Drawable存储接收者,因此接收者可以轻易更换。

public class DrawCommand implements Command {

    protected Drawable drawable;

    private Point point;

    public DrawCommand(Drawable drawable, Point point) {
        this.drawable = drawable;
        this.point = point;
    }

    @Override
    public void execute() {
        drawable.draw(point.x, point.y);
    }

}

命令接收者的实现

public class DrawCanvas extends Canvas implements Drawable {

    private Color color = Color.RED;
    
    private int radius = 6;

    private MarcoCommand history;

    public DrawCanvas(int width, int height, MarcoCommand history) {
        setSize(width, height);
        setBackground(Color.white);
        this.history = history;
    }

    public void paint(Graphics g) {
        history.execute();
    }

    @Override
    public void draw(int x, int y) {
        Graphics g = getGraphics();
        g.setColor(color);
        g.fillOval(x - radius, y - radius, radius * 2, radius * 2);
    }
    
}

Interpreter 模式

在解释器模式当中,我们不再单纯使用java来解决问题,而是采用更加简单的迷你语言编写一个迷你程序来解决问题。此时java负责的就是编写该迷你语言的解释器,只要解释器编写正确,当问题发生变化时,就不必再修改java代码而是简单的迷你语言代码。

Interpreter 模式类图

登场角色:

  1. AbstractExpression:定义了语法树结点的共同接口
  2. TerminalExpression:对应BNF中的终结符表达式,即不可再被展开的表达式
  3. NonterminalExpression:非终结符表达式
  4. Context:为解释器及进行语法解析提供必要的信息

下面假定有一种迷你语言的BNF表达式为:
< program > ::= program < command list >
< command list > ::= < command >* end
< command > ::= < repeat command > | < primitive command >
< repeat command > ::= repeat < number > < command list >
< primitive command > ::= go | left | right

这里定义了语法树的公用接口

public interface Node {
    void parse(Context context) throws ParseException;
}

上下文角色,这里使用StringTokenizer来将文本处理成标记,方便后续的解析

public class Context {
    private StringTokenizer tokenizer;
    private String currentToken;
    // 将接收到的字符串分割为标记,分隔符为空格,制表符,换行符,回车符等等
    private Context(String text) {
        tokenizer = new StringTokenizer(text);
        nextToken();
    }
    public String nextToken() {
        if (tokenizer.hasMoreTokens()) {
            currentToken = tokenizer.nextToken();
        } else {
            currentToken = null;
        }
        return currentToken;
    }
    public String currentToken() {
        return currentToken;
    }
    public void skipToken(String token) {
        if (!token.equals(currentToken)) {
            throw new ParseException("Warning: " + token + " is expected, but " + currentToken + " get");
        }
        nextToken();
    }

    public int currentNumber() {
        int number = 0;
        number = Integer.parseInt(currentToken);
        return number;
    }
}

以下就是各个语法树结点的具体实现类,每个结点的组成部分都严格符合上面BNF定义的结构。将子级结点保存在内部,并调用对应的解析方法。

// <program> ::= program <command list>
public class ProgramNode implements Node {
    private Node commandListNode;
    @Override
    public void parse(Context context) throws ParseException {
        context.skipToken("program");
        commandListNode = new CommandListNode();
        commandListNode.parse(context);
    }

    @Override
    public String toString() {
        return "ProgramNode []";
    }

}
// <commandlist> ::= <command>* end
public class CommandListNode implements Node {

    private List<Node> commands = new ArrayList<>();

    @Override
    public void parse(Context context) throws ParseException {
        while (true) {
            if (context.currentToken() == null) {
                throw new ParseException("Missing end");
            } else if (context.currentToken().equals("end")) {
                context.skipToken("end");
                break;
            } else {
                Node commandNode = new CommandNode();
                commandNode.parse(context);
                commands.add(commandNode);
            }
        }
    }

    @Override
    public String toString() {
        return commands.toString();
    }
    
}
// command ::= repeat command | primitive command
public class CommandNode implements Node {

    private Node node;

    @Override
    public void parse(Context context) throws ParseException {
        if (context.currentToken().equals("repeat")) {
            node = new RepeatCommandNode();
            node.parse(context);
        } else {
            node = new PrimitiveCommandNode();
            node.parse(context);
        }
    }

    @Override
    public String toString() {
        return node.toString();
    }
    
}
// <repeat command> ::= repeat number commandlist
public class RepeatCommandNode implements Node {

    private Integer number;
    private CommandListNode node;

    @Override
    public void parse(Context context) throws ParseException {
        context.skipToken("repeat");
        number = Integer.parseInt(context.currentToken());
        context.nextToken();
        node = new CommandListNode();
        node.parse(context);
    }
    
    @Override
    public String toString() {
        return "[repeat " + number + " " + node + "]";
    }
}
// primitive command ::= go | left | right
public class PrimitiveCommandNode implements Node {
    private String name;
    @Override
    public void parse(Context context) throws ParseException {
        name = context.currentToken();
        context.skipToken(name);
        if (!name.equals("go") && !name.equals("left") && !name.equals("right")) {
            throw new ParseException(name + " is undefined");
        }
    }
    
    @Override
    public String toString() {
        return name;
    }
}
  • 标题: 设计模式
  • 作者: Zephyr
  • 创建于 : 2022-08-16 10:13:56
  • 更新于 : 2023-01-26 12:32:16
  • 链接: https://faustpromaxpx.github.io/2022/08/16/design-mode/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。
评论
评论
  • 按正序
  • 按倒序
  • 按热度
Powered by Waline v2.15.8