专栏首页算法之名设计模式整理 顶

设计模式整理 顶

1、Iterator模式

Iterator模式可以帮助我们分离具体的集合跟遍历,就是在代码中更换了集合,也可以不需要重新调用新集合的方法。

该图中,Aggregate为一个集合的接口,他有一个公共方法iterator(),返回一个迭代器接口,也就是说他可以返回任何该迭代器接口的具体实现类。

具体代码如下

/**
 * 集合接口
 */
public interface Aggregate {
    public Iterator iterator();
}
/**
 * 迭代器接口
 */
public interface Iterator {
    public abstract boolean hasNext();
    public abstract Object next();
}
/**
 * 书的实体类
 */
@Data
@AllArgsConstructor
public class Book {
    private String name;
}
/**
 * 书架类,实现了集合接口,包含了书的数组
 */
public class BookShelf implements Aggregate {
    private Book[] books;
    private int last = 0;
    public BookShelf(int maxsize) {
        this.books = new Book[maxsize];
    }
    public Book getBookAt(int index) {
        return books[index];
    }
    public void appendBook(Book book) {
        this.books[last] = book;
        last++;
    }
    public int getLength() {
        return last;
    }
    public Iterator iterator() {
        return new BookShelfIterator(this);
    }
}
/**
 * 书架迭代器,遍历书架
 */
public class BookShelfIterator implements Iterator {
    private BookShelf bookShelf;
    private int index;

    public BookShelfIterator(BookShelf bookShelf) {
        this.bookShelf = bookShelf;
        this.index = 0;
    }
    public boolean hasNext() {
        if (index < bookShelf.getLength()) {
            return true;
        }else {
            return false;
        }
    }

    public Object next() {
        Book book = bookShelf.getBookAt(index);
        index++;
        return book;
    }
}

执行main方法

public class Main {
    public static void main(String[] args) {
        BookShelf bookShelf = new BookShelf(4);
        bookShelf.appendBook(new Book("Around the World in 80 Days"));
        bookShelf.appendBook(new Book("Bible"));
        bookShelf.appendBook(new Book("Cinderella"));
        bookShelf.appendBook(new Book("Daddy-Long-Legs"));
        Iterator it = bookShelf.iterator();
        while (it.hasNext()) {
            Book book = (Book)it.next();
            System.out.println(book.getName());
        }
    }
}

运行结果:

Around the World in 80 Days Bible Cinderella Daddy-Long-Legs

2、Adapter模式

2.1类适配器模式(使用继承的适配器)

类适配器模式可以隐藏被适配的类本身的方法,转换成接口的方法,这样我们就只需要调用接口方法完成对被适配类的适配。

具体代码如下

@AllArgsConstructor
public class Banner {
    private String string;
    public void showWithParen() {
        System.out.println("(" + string + ")");
    }
    public void showWithAster() {
        System.out.println("*" + string + "*");
    }
}
public interface Print {
    public void printWeak();
    public void printStrong();
}
public class PrintBanner extends Banner implements Print {
    public PrintBanner(String string) {
        super(string);
    }

    public void printWeak() {
        showWithParen();
    }

    public void printStrong() {
        showWithAster();
    }
}
public class Main {
    public static void main(String[] args) {
        Print p = new PrintBanner("Hello");
        p.printWeak();
        p.printStrong();
    }
}

运行结果为

(Hello) *Hello*

2.2对象适配器模式(使用委托的适配器)

委托是交给其他类处理的意思。比如下面的实例中PrintBanner类交给Banner类进行处理的关系。

具体代码如下

@AllArgsConstructor
public class Banner {
    private String string;
    public void showWithParen() {
        System.out.println("(" + string + ")");
    }
    public void showWithAster() {
        System.out.println("*" + string + "*");
    }
}
public abstract class Print {
    public abstract void printWeak();
    public abstract void printStrong();
}
public class PrintBanner extends Print {
    private Banner banner;
    public PrintBanner(String string) {
        this.banner = new Banner(string);
    }
    @Override
    public void printWeak() {
        banner.showWithParen();
    }

    @Override
    public void printStrong() {
        banner.showWithAster();
    }
}
public class Main {
    public static void main(String[] args) {
        Print p = new PrintBanner("Hello");
        p.printWeak();
        p.printStrong();
    }
}

运行结果:

(Hello) *Hello*

Adapter模式的使用场景

很多时候我们并非从零开始编程,经常会用到现有的类。Adapter模式会对现有的类进行适配,生成新的类。通过该模式可以很方便的创建我们需要的方法群。使用Adapter模式可以在完全不改变现有代码的前提下使现有代码适配于新的接口(API)。软件的生命周期总是伴随着版本的升级,这时,可以使用Apapter模式使新旧版本兼容。当然功能完全不同的类,Adapter模式是无法使用的,就像我们无法用交流100V电压让自来水管出水一样。

3、交给子类Template Method模式

Template Method模式是带有模板功能的模式,组成模板的方法被定义在父类中,这些方法是抽象方法。实现上述这些抽象方法的是子类。无论子类的具体实现如何,处理的流程都会按照父类中所定义的那样进行。

具体代码如下:

public abstract class AbstractDisplay {
    public abstract void open();
    public abstract void print();
    public abstract void close();
    public final void display() {
        open();
        for (int i = 0;i < 5;i++) {
            print();
        }
        close();
    }
}
@AllArgsConstructor
public class CharDisplay extends AbstractDisplay {
    private char ch;
    @Override
    public void open() {
        System.out.print("<<");
    }

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

    @Override
    public void close() {
        System.out.println(">>");
    }
}
public class StringDisplay extends AbstractDisplay {
    private String string;
    private int width;
    public StringDisplay(String string) {
        this.string = string;
        this.width = string.getBytes().length;
    }
    @Override
    public void open() {
        printLine();
    }

    @Override
    public void print() {
        System.out.println("|" + string + "|");
    }

    @Override
    public void close() {
        printLine();
    }
    private void printLine() {
        System.out.print("+");
        for (int i = 0;i < width;i++) {
            System.out.print("-");
        }
        System.out.println("+");
    }
}
public class Main {
    public static void main(String[] args) {
        AbstractDisplay d1 = new CharDisplay('H');
        AbstractDisplay d2 = new StringDisplay("Hello,world.");
        AbstractDisplay d3 = new StringDisplay("你好,世界。");
        d1.display();
        d2.display();
        d3.display();
    }
}

运行结果:

<<HHHHH>> +------------+ |Hello,world.| |Hello,world.| |Hello,world.| |Hello,world.| |Hello,world.| +------------+ +------------------+ |你好,世界。| |你好,世界。| |你好,世界。| |你好,世界。| |你好,世界。| +------------------+

我们理解类的层次时,通常是站在子类的角度进行思考,容易着眼于以下几点。

  • 在子类中可以使用父类中定义的方法。
  • 可以通过在子类中增加方法以实现新的功能。
  • 在子类中重写父类的方法可以改变程序的行为。

站在父类的角度思考,我们声明了抽象方法,将该方法的实现交给了子类。声明抽象方法达到以下目的。

  • 期待子类去实现抽象方法。
  • 要求子类去实现抽象方法。

4、将实例的生成交给子类Factory Method模式

如果将Template Method模式用于生成实例,就变成了Factory Method模式。父类决定实例的生产方式,但并不决定所要生成的具体的类,具体的处理全部交给子类负责。这样就可以将生成实例的框架和实际负责生成实例的类解耦。

具体代码如下

package com.guanjian.factory.framework;

/**
 * 所有产品的父类
 */
public abstract class Product {
    public abstract void use();
}
package com.guanjian.factory.framework;

/**
 * 生成具体产品的工厂的父类
 */
public abstract class Factorry {
    public final Product create(String owner) {
        Product p = createProduct(owner);
        registerProduct(p);
        return p;
    }
    protected abstract Product createProduct(String owner);
    protected abstract void registerProduct(Product product);
}
package com.guanjian.factory.idcard;

import com.guanjian.factory.framework.Product;
import lombok.Getter;

/**
 * 具体产品ID卡
 */
@Getter
public class IDCard extends Product {
    private String owner;
    public IDCard(String owner) {
        System.out.println("制作" + owner + "的ID卡");
        this.owner = owner;
    }
    @Override
    public void use() {
        System.out.println("使用" + owner + "的ID卡。");
    }
}
package com.guanjian.factory.idcard;

import com.guanjian.factory.framework.Factorry;
import com.guanjian.factory.framework.Product;
import lombok.Getter;

import java.util.ArrayList;
import java.util.List;

/**
 * 生产ID卡的具体工厂
 */
@Getter
public class IDCardFactory extends Factorry {
    private List<String> owners = new ArrayList();
    @Override
    protected Product createProduct(String owner) {
        return new IDCard(owner);
    }

    @Override
    protected void registerProduct(Product product) {
        owners.add(((IDCard)product).getOwner());
    }
}
public class Main {
    public static void main(String[] args) {
        Factorry factorry = new IDCardFactory();
        Product card1 = factorry.create("小明");
        Product card2 = factorry.create("小红");
        Product card3 = factorry.create("小刚");
        card1.use();
        card2.use();
        card3.use();
    }
}

运行结果:

制作小明的ID卡 制作小红的ID卡 制作小刚的ID卡 使用小明的ID卡。 使用小红的ID卡。 使用小刚的ID卡。

5、Singleton模式

  • 想确保任何情况下都绝对只有1个实例
  • 想在程序上表现出“只存在一个实例”

具体代码如下:

public class Singleton {
    private static Singleton singleton = new Singleton();
    //构造器必须设置为private,防止从外部new一个实例
    private Singleton() {
        System.out.println("生成了一个实例.");
    }
    public static Singleton getSingleton() {
        return singleton;
    }
}
public class Main {
    public static void main(String[] args) {
        System.out.println("Start.");
        Singleton obj1 = Singleton.getSingleton();
        Singleton obj2 = Singleton.getSingleton();
        if (obj1 == obj2) {
            System.out.println("obj1与obj2是相同的实例.");
        }else {
            System.out.println("obj1与obj2是不同的实例.");
        }
        System.out.println("End.");
    }
}

运行结果:

Start. 生成了一个实例. obj1与obj2是相同的实例. End.

6、通过复制生成实例Prototype模式

Prototype有“原型”,“模型”的意思。在Java中,我们可以通过使用new关键字指定类名来生成类的实例。但是在开发过程中,有时候也会有“在不指定类名的前提下生成实例”的需求。

(1)对象种类繁多,无法将他们整合到一个类中时。如果将他们分别作为一个类,必须要编写很多个类文件。

(2)难以根据类生成实例时,生成实例的过程太过复杂,很难重新模仿生成,只能先将之前生成的实例保存下来,然后通过复制来生成新的实例。

(3)想解耦框架于生成的实例时,想要让生成实例的框架不依赖于具体的类,不能指定类名来生成实例,必须要先“注册”一个“原型”实例,然后通过复制该实例来生成新的实例。

framework框架图

该框架不依赖于任何具体的实现类。

代码如下

public interface Product extends Cloneable {
    public void use(String s);
    public Product createClone();
}
/**
 * 产品管理类
 */
public class Manager {
    private Map<String,Product> showcase = new HashMap();
    //通过名称注册具体的产品
    public void register(String name,Product proto) {
        showcase.put(name,proto);
    }
    //通过名称拿出具体的产品,并克隆一个新的产品
    public Product create(String protoname) {
        Product p = (Product)showcase.get(protoname);
        return p.createClone();
    }
}

关于Cloneable接口,先做一个简单的介绍,实现 Cloneable来表示该对象能被克隆,能使用Object.clone()方法。如果没有实现 Cloneable的类对象调用clone()就会抛出CloneNotSupportedException。

@AllArgsConstructor
public class Info implements Cloneable {
    private int id;
    private String text;

    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (obj == null) {
            return false;
        }
        if (obj.getClass()!= getClass()) {
            return false;
        }
        Info temp = (Info) obj;
        if (id != temp.id) {
            return false;
        }
        if (text == null) {
            if (temp.text != null) {
                return false;
            }
        } else if (!text.equals(temp.text)) {
            return false;
        }
        return true;
    }



    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }



    public static void main(String[] args) throws CloneNotSupportedException {
        Info info1 = new Info(1, "I am Colyn Lu.");
        Info info2 = (Info) info1.clone();
        System.out.println(info1.getClass() == info2.getClass());//true
        System.out.println(info1 == info2);//false
        System.out.println(info1.equals(info2));//true
    }
}

运行结果:

true false true

然后是具体的实现类图

这里我们可以想象成有很多的具体产品类。

具体代码如下:

@AllArgsConstructor
public class MessageBox implements Product {
    private char decochar;
    public void use(String s) {
        int length = s.getBytes().length;
        for (int i = 0;i < length + 4;i++) {
            System.out.print(decochar);
        }
        System.out.println("");
        System.out.println(decochar + " " + s + " " + decochar);
        for (int i = 0;i < length + 4;i++) {
            System.out.print(decochar);
        }
        System.out.println("");
    }

    public Product createClone() {
        Product p = null;
        try {
            p = (Product)clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return p;
    }
}
@AllArgsConstructor
public class UnderlinePen implements Product {
    private char ulchar;
    public void use(String s) {
        int length = s.getBytes().length;
        System.out.println("\"" + s + "\"");
        System.out.print(" ");
        for (int i = 0;i < length;i++) {
            System.out.print(ulchar);
        }
        System.out.println("");
    }

    public Product createClone() {
        Product p = null;
        try {
            p = (Product)clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return p;
    }
}

这两种具体的产品类都可以注册进管理类,并且使用不同的产品,会有不同的效果,并且克隆出新的产品实例。

public class Main {
    public static void main(String[] args) {
        Manager manager = new Manager();
        UnderlinePen upen = new UnderlinePen('~');
        MessageBox mbox = new MessageBox('*');
        MessageBox sbox = new MessageBox('/');
        manager.register("strong message",upen);
        manager.register("warning box",mbox);
        manager.register("slash box",sbox);

        Product p1 = manager.create("strong message");
        p1.use("Hello,world.");
        Product p2 = manager.create("warning box");
        p2.use("Hello,world.");
        Product p3 = manager.create("slash box");
        p3.use("Hello,world.");
    }
}

运行结果:

"Hello,world." ~~~~~~~~~~~~ **************** * Hello,world. * **************** //////////////// / Hello,world. / ////////////////

7、组装复杂的实例Builder模式

在搭建大型框架的时候,我们需要首先建造组成这个框架的各个部分,然后分阶段把它们组装起来。

该示例是输出的文本内容相同,但是组装的格式各不相同,一份是文档的,一份是HTML的,具体代码如下:

public abstract class Builder {
    public abstract void makeTitle(String title);
    public abstract void makeString(String str);
    public abstract void makeItems(String[] items);
    public abstract void close();
}
/**
 * 负责调用抽象类来输出他们的文档,具体调用哪个实现类,由个人意愿决定
 */
@AllArgsConstructor
public class Director {
    private Builder builder;
    public void construct() {
        builder.makeTitle("Greeting");
        builder.makeString("从早上至下午");
        builder.makeItems(new String[]{
                "早上好。",
                "下午好。",
        });
        builder.makeString("晚上");
        builder.makeItems(new String[]{
                "晚上好。",
                "晚安。",
                "再见。",
        });
        builder.close();
    }
}
public class TextBuilder extends Builder {
    private StringBuffer buffer = new StringBuffer();
    @Override
    public void makeTitle(String title) {
        buffer.append("===========================\n");
        buffer.append("[" +title + "]\n");
        buffer.append("\n");
    }

    @Override
    public void makeString(String str) {
        buffer.append('@' + str + "\n");
        buffer.append("\n");
    }

    @Override
    public void makeItems(String[] items) {
        for (int i = 0;i < items.length;i++) {
            buffer.append("  ." + items[i] + "\n");
        }
        buffer.append("\n");
    }

    @Override
    public void close() {
        buffer.append("==================================\n");
    }
    public String getResult() {
        return buffer.toString();
    }
}
public class HTMLBuilder extends Builder {
    private String filename;
    private PrintWriter writer;
    @Override
    public void makeTitle(String title) {
        filename = title + ".html";
        try {
            writer = new PrintWriter(new FileWriter(filename));
        } catch (IOException e) {
            e.printStackTrace();
        }
        writer.println("<html><head><title>" + title + "</title></head><body>");
        writer.println("<h1>" + title + "</h1>");
    }

    @Override
    public void makeString(String str) {
        writer.println("<p>" +str + "</p>");
    }

    @Override
    public void makeItems(String[] items) {
        writer.println("<ul>");
        for (int i = 0;i < items.length;i++) {
            writer.println("<li>" + items[i] + "</li>");
        }
        writer.println("</ul>");
    }

    @Override
    public void close() {
        writer.println("</body></html>");
        writer.close();
    }
    public String getResult() {
        return filename;
    }
}
public class Main1 {
    public static void main(String[] args) {
        if (args.length != 1) {
            usage();
            System.exit(0);
        }
        if (args[0].equals("plain")) {
            TextBuilder textBuilder = new TextBuilder();
            Director director = new Director(textBuilder);
            director.construct();
            String result = textBuilder.getResult();
            System.out.println(result);
        }else if (args[0].equals("html")) {
            HTMLBuilder htmlBuilder = new HTMLBuilder();
            Director director = new Director(htmlBuilder);
            director.construct();
            String filename = htmlBuilder.getResult();
            System.out.println(filename + "文件编写完成");
        }else {
            usage();
            System.exit(0);
        }
    }
    public static void usage() {
        System.out.println("Usage: java Main plain      编写纯文本文档");
        System.out.println("Usage: java Main html       编写HTML文档");
    }
}

当我们要使用文档格式时,使用main参数

运行结果:

=========================== [Greeting]

@从早上至下午

.早上好。 .下午好。

@晚上

.晚上好。 .晚安。 .再见。

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

当要用到html时,修改main方法参数

运行后产生一个html文件,用浏览器打开如下

8、将关联零件组装成产品Abstract Factory模式

抽象工厂的工作是将“抽象零件”组装成“抽象产品”。我们并不关心零件的具体实现,而是只关心接口(API)。我们仅使用该接口(API)将零件组装成为产品。总的来说就是在抽象类中就把业务流程写完了,完全不用考虑具体的类的参与。

以上是抽象框架,Item是产品抽象类,Link,Tray,Page是产品类型抽象类。Factory工厂生产这几种类型的产品。

具体代码如下:

@AllArgsConstructor
public abstract class Item {
    protected String 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 Tray extends Item {
    protected List<Item> tray = new ArrayList();
    public Tray(String caption) {
        super(caption);
    }
    public void add(Item item) {
        tray.add(item);
    }
}
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() {
        try {
            String filename = title + ".html";
            Writer writer = new FileWriter(filename);
            writer.write(this.makeHTML());
            writer.close();
            System.out.println(filename + "编写完成。");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    public abstract String makeHTML();
}
public abstract class Factory {
    public static Factory getFactory(String classname) {
        Factory factory = null;
        try {
            factory = (Factory)Class.forName(classname).newInstance();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            System.err.println("没有找到" + classname + " 类");
        }
        return factory;
    }
    public abstract Link createLink(String caption,String url);
    public abstract Tray createTray(String caption);
    public abstract Page createPage(String title,String author);
}

通过main方法,我们可以直接把整个业务流程写出来,此时无需知道具体的类的实现。

public class Main2 {
    public static void main(String[] args) {
        if (args.length != 1) {
            System.out.println("Usage: java Main class.name.of.ConcreteFactory");
            System.out.println("Example 1: java Main listfactory.ListFactory");
            System.out.println("Example 2: java Main tablefactory.TableFactory");
            System.exit(0);
        }
        Factory factory = Factory.getFactory(args[0]);
        Link people = factory.createLink("人民日报","http://www.people.com.cn/");
        Link gmw = factory.createLink("光明日报","http://www.gmw.cn/");
        Link us_yahoo = factory.createLink("Yahoo!","http://www.yahoo.com/");
        Link jp_yahoo = factory.createLink("Yahoo!Japan","http://www.yahoo.co.jp/");
        Link excite = factory.createLink("Excite","http://www.excite.com/");
        Link google = factory.createLink("Google","http://www.google.com/");

        Tray traynews = factory.createTray("日报");
        traynews.add(people);
        traynews.add(gmw);

        Tray trayyahoo = factory.createTray("Yahoo!");
        trayyahoo.add(us_yahoo);
        trayyahoo.add(jp_yahoo);

        Tray traysearch = factory.createTray("搜索引擎");
        traysearch.add(trayyahoo);
        traysearch.add(excite);
        traysearch.add(google);

        Page page = factory.createPage("LinkPage","杨文轩");
        page.add(traynews);
        page.add(traysearch);
        page.output();
    }
}

具体的实现类UML图

另外我们可以实现多套具体的实现

具体代码如下:

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 title, String author) {
        return new ListPage(title,author);
    }
}
public class ListLink extends Link {
    @Override
    public String makeHTML() {
        return "  <li><a href=\"" + url + "\">" +caption + "</a></li>\n";
    }

    public ListLink(String caption, String url) {
        super(caption, url);
    }
}
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");
        Iterator<Item> it = tray.iterator();
        while (it.hasNext()) {
            Item item = (Item)it.next();
            buffer.append(item.makeHTML());
        }
        buffer.append("</ul>\n");
        buffer.append("</li>\n");
        return buffer.toString();
    }
}
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");
        Iterator<Item> it = content.iterator();
        while (it.hasNext()) {
            Item item = (Item) it.next();
            buffer.append(item.makeHTML());
        }
        buffer.append("</ul>\n");
        buffer.append("<hr><address>" + author + "</address>");
        buffer.append("</body></html>\n");
        return buffer.toString();
    }
}

main方法通过参数,产生网页

结果如下

public class TableFactory extends Factory{
    @Override
    public Link createLink(String caption, String url) {
        return new TableLink(caption,url);
    }

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

    @Override
    public Page createPage(String title, String author) {
        return new TablePage(title,author);
    }
}
public class TableLink extends Link {
    public TableLink(String caption, String url) {
        super(caption, url);
    }

    @Override
    public String makeHTML() {
        return "<td><a href=\"" +url + "\">" +caption + "</a></td>\n";
    }
}
public class TableTray extends Tray {
    public TableTray(String caption) {
        super(caption);
    }

    @Override
    public String makeHTML() {
        StringBuffer buffer = new StringBuffer();
        buffer.append("<td>");
        buffer.append("<table width=\"100%\" border=\"1\"><tr>");
        buffer.append("<td bgcolor=\"#cccccc\" align=\"center\" colspan=\"" + tray.size() + "\"><b>" + caption + "</b></td>");
        buffer.append("</tr>\n");
        buffer.append("<tr>\n");
        Iterator<Item> it = tray.iterator();
        while (it.hasNext()) {
            Item item = (Item) it.next();
            buffer.append(item.makeHTML());
        }
        buffer.append("</tr></table>");
        buffer.append("</td>");
        return buffer.toString();
    }
}
public class TablePage extends Page {
    public TablePage(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("<table width=\"80%\" border=\"3\">\n");
        Iterator<Item> it = content.iterator();
        while (it.hasNext()) {
            Item item = (Item) it.next();
            buffer.append("<tr>" + item.makeHTML() + "</tr>");
        }
        buffer.append("</table>\n");
        buffer.append("<hr><address>" + author + "</address>");
        buffer.append("</body></html>\n");
        return buffer.toString();
    }
}

配置tablefactory参数

运行结果

9、将类的功能层次结构与实现层次结构分离Bridge模式

Bridge模式的作用是在“类的功能层次结构”和“类的实现层次结构”之间搭建桥梁。

类的功能层次结构,一般指父类与派生类之间的层次结构,父类具有基本功能,在子类中增加新功能。通常来说类的层次结构不应当过深。

类的实现层次结构,一般是指父类通过声明抽象方法来定义接口(API),子类通过实现具体方法来实现接口(API),子类并不是为了增加新功能,而只是为了实现父类的抽象方法。

在该示例中,Display到CountDisplay为增加新的功能muliDisplay,此为类的功能层次结构扩展。而DisplayImpl到StringDisplayImpl则没有增加新的功能,只是去实现DisplayImpl的抽象方法,则为类的实现层次结构。

具体代码如下:

@AllArgsConstructor
public class Display {
    private DisplayImpl impl;
    public void open() {
        impl.rawOpen();
    }
    public void print() {
        impl.rawPrint();
    }
    public void close() {
        impl.rawClose();
    }
    public final void display() {
        open();
        print();
        close();
    }
}
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();
    }
}
public abstract class DisplayImpl {
    public abstract void rawOpen();
    public abstract void rawPrint();
    public abstract void rawClose();
}
public class StringDisplayImpl extends DisplayImpl {
    private String string;
    private int width;
    public StringDisplayImpl(String string) {
        this.string = string;
        this.width = string.getBytes().length;
    }
    @Override
    public void rawOpen() {
        printLine();
    }

    @Override
    public void rawPrint() {
        System.out.println("|" + string + "|");
    }

    @Override
    public void rawClose() {
        printLine();
    }
    private void printLine() {
        System.out.print("+");
        for (int i = 0;i < width;i++) {
            System.out.print("-");
        }
        System.out.println("+");
    }
}
public class MainBridge {
    public static void main(String[] args) {
        Display d1 = new Display(new StringDisplayImpl("Hello,China."));
        Display d2 = new CountDisplay(new StringDisplayImpl("Hello,World."));
        CountDisplay d3 = new CountDisplay(new StringDisplayImpl("Hello,Universe."));
        d1.display();
        d2.display();
        d3.display();
        d3.multiDisplay(5);
    }
}

运行结果:

+------------+ |Hello,China.| +------------+ +------------+ |Hello,World.| +------------+ +---------------+ |Hello,Universe.| +---------------+ +---------------+ |Hello,Universe.| |Hello,Universe.| |Hello,Universe.| |Hello,Universe.| |Hello,Universe.| +---------------+

Bridge模式的特征是将“类的功能层次结构”于“类的实现层次结构”分离开。将类的这两个层次结构分离开有利于独立地对它们进行扩展。而且,增加后的功能可以被“所有的实现”使用。

继承是强关联,委托是弱关联。虽然使用“继承”很容易扩展类,但是类之间也形成了一种强关联关系。如果想要很轻松地改变类之间的关系,使用继承就不适合了,因为每次改变类之间的关系时都需要修改程序。这时可以使用“委托”来代替“继承”关系。

示例程序的Display类中使用了“委托”。如果我们不传递StringDisplayImpl类的实例,而是将其他ConcreteImplementor角色的实例传递给Display类和CountDisplay类,就很容易改变实现。这时发生变化的代码只有Main类,Display类和DisplayImpl类则不需要做任何修改。

10、整体的替换算法Strategy模式

Strategy的意思是“策略”,使用Strategy模式可以整体地替换算法的实现部分。

该示例是模拟两个玩家玩猜拳的游戏。其中Hand类是表示猜拳游戏中“手势”的类,我们只需要3个实例,所以在程序的开始,创建这3个实例,并保存在hand数组中。获取实例可以通过getHand来获取。具体代码如下

public class Hand {
    public static final int HANDVALUE_GUU = 0;  //表示石头的值
    public static final int HANDVALUE_CHO = 1;  //表示剪刀的值
    public static final int HANDVALUE_PAA = 2;  //表示布的值
    public static final Hand[] hand = {
            new Hand(HANDVALUE_GUU),
            new Hand(HANDVALUE_CHO),
            new Hand(HANDVALUE_PAA)
    };
    private static final String[] name = {"石头","剪刀","布"}; //表示猜拳中手势所对应的字符串
    private int handvalue;   //猜拳中出的手势的值
    private Hand(int handvalue) {
        this.handvalue = handvalue;
    }
    public static Hand getHand(int handvalue) {  //根据手势的值获取其对应的实例
        return hand[handvalue];
    }
    public boolean isStrongerThan(Hand h) { //如果this胜了h则返回true
        return fight(h) == 1;
    }
    public boolean isWeakerThan(Hand h) {  //如果this输给了h则返回true
        return fight(h) == -1;
    }
    private int fight(Hand h) {   //计分:平0,胜1,负-1
        if (this == h) {
            return 0;
        }else if ((this.handvalue + 1) % 3 == h.handvalue) {
            return 1;
        }else {
            return -1;
        }
    }

    @Override
    public String toString() {
        return name[handvalue];
    }
}

Strategy是定义了猜拳策略的接口。

public interface Strategy {
    public Hand nextHand();  //表示下一局出什么
    public void study(boolean win);  //根据上一局的输赢来学习和改变策略
}

WinningStrategy是实现了Strategy接口的策略之一。它的策略很简单,就是上一局赢了就跟上一局相同,输了就随便出。

public class WinningStrategy implements Strategy {
    private Random random;
    private boolean won = false;
    private Hand prevHand;
    public WinningStrategy(int seed) {
        random = new Random(seed);
    }
    public Hand nextHand() {
        if (!won) {
            prevHand = Hand.getHand(random.nextInt(3));
        }
        return prevHand;
    }

    public void study(boolean win) {
        won = win;
    }
}

ProbStrategy类是实现了Strategy接口的另一个实现类。它的策略是根据历史中,上一局出啥,本局出啥的的未败概率来出这一局的手势,也就是说哪种情况发生的越多,下局出这个手势的可能性就越大。

public class ProbStrategy implements Strategy {
    private Random random;
    private int prevHandValue = 0;
    private int currentHandValue = 0;
    /**
     * history字段是一个表,用于根据过去的胜负来进行概率计算
     * 数组下标的意思history[上一局出的手势][这一局出的手势]
     */
    private int[][] history = {
            {1,1,1},   //在上一局出石头时,本局出石头,剪刀,布的历史未失败次数
            {1,1,1},   //在上一局出剪刀时,本局出石头,剪刀,布的历史未失败次数
            {1,1,1}    //在上一局出布时,本局出石头,剪刀,布的历史未失败次数
    };
    public ProbStrategy(int seed) {
        random = new Random(seed);
    }
    public Hand nextHand() {
        int bet = random.nextInt(getSum(currentHandValue));
        int handvalue = 0;
        if (bet < history[currentHandValue][0]) {
            handvalue = 0;
        }else if (bet < history[currentHandValue][0] + history[currentHandValue][1]) {
            handvalue = 1;
        }else {
            handvalue = 2;
        }
        prevHandValue = currentHandValue;
        currentHandValue = handvalue;
        return Hand.getHand(handvalue);
    }

    public void study(boolean win) {
        if (win) {
            history[prevHandValue][currentHandValue]++;
        }else {
            history[prevHandValue][(currentHandValue + 1) % 3]++;
            history[prevHandValue][(currentHandValue + 2) % 3]++;
        }
    }
    private int getSum(int hv) {
        int sum = 0;
        for (int i = 0;i < 3;i++) {
            sum += history[hv][i];
        }
        return sum;
    }
}

Player类是表示进行猜拳游戏的选手的类。它是采用委托的方式,委托Strategy接口来进行策略的决定。此时与具体的实现类无关。在决定下一局要出的手势时,需要知道之前各局的胜、负、平等结果,因此Player类会通过strategy字段调用study方法,然后study方法会改变策略的内部状态。

public class Player {
    private String name;
    private Strategy strategy;
    private int wincount;
    private int losecount;
    private int gamecount;
    public Player(String name,Strategy strategy) {   //赋予姓名和策略
        this.name = name;
        this.strategy = strategy;
    }
    public Hand nextHand() {     //策略决定下一局要出的手势
        return strategy.nextHand();
    }
    public void win() {   //胜
        strategy.study(true);
        wincount++;
        gamecount++;
    }
    public void lose() {  //负
        strategy.study(false);
        losecount++;
        gamecount++;
    }
    public void even() {  //平
        gamecount++;
    }

    @Override
    public String toString() {
        return "[" + name + ":" + gamecount + " games, " + wincount + " win, " + losecount + " lose" + "]";
    }
}
public class StrategyMain {
    public static void main(String[] args) {
        if (args.length != 2) {
            System.out.println("Usage: java Main randomseed1 randomseed2");
            System.out.println("Example: java Main 314 15");
            System.exit(0);
        }
        int seed1 = Integer.parseInt(args[0]);
        int seed2 = Integer.parseInt(args[1]);
        Player player1 = new Player("Taro",new WinningStrategy(seed1));
        Player player2 = new Player("Hana",new ProbStrategy(seed2));
        for (int i = 0;i < 10000;i++) {
            Hand nextHand1 = player1.nextHand();
            Hand nextHand2 = player2.nextHand();
            if (nextHand1.isStrongerThan(nextHand2)) {
                System.out.println("Winner:" + player1);
                player1.win();
                player2.lose();
            }else if (nextHand2.isStrongerThan(nextHand1)) {
                System.out.println("Winner:" + player2);
                player1.lose();
                player2.win();
            }else {
                System.out.println("Even...");
                player1.even();
                player2.even();
            }
        }
        System.out.println("Total result:");
        System.out.println(player1.toString());
        System.out.println(player2.toString());
    }
}

通过放入main参数

运行结果类似这样

Winner:[Taro:9972 games, 3157 win, 3481 lose] Winner:[Hana:9973 games, 3481 win, 3158 lose] Winner:[Taro:9974 games, 3158 win, 3482 lose] Winner:[Hana:9975 games, 3482 win, 3159 lose] Winner:[Taro:9976 games, 3159 win, 3483 lose] Even... Winner:[Taro:9978 games, 3160 win, 3483 lose] Winner:[Taro:9979 games, 3161 win, 3483 lose] Winner:[Hana:9980 games, 3483 win, 3162 lose] Winner:[Hana:9981 games, 3484 win, 3162 lose] Even... Even... Winner:[Hana:9984 games, 3485 win, 3162 lose] Winner:[Taro:9985 games, 3162 win, 3486 lose] Even... Winner:[Hana:9987 games, 3486 win, 3163 lose] Winner:[Taro:9988 games, 3163 win, 3487 lose] Winner:[Hana:9989 games, 3487 win, 3164 lose] Even... Even... Winner:[Taro:9992 games, 3164 win, 3488 lose] Winner:[Hana:9993 games, 3488 win, 3165 lose] Winner:[Taro:9994 games, 3165 win, 3489 lose] Winner:[Taro:9995 games, 3166 win, 3489 lose] Winner:[Hana:9996 games, 3489 win, 3167 lose] Even... Even... Even... Total result: [Taro:10000 games, 3167 win, 3490 lose] [Hana:10000 games, 3490 win, 3167 lose]

Strategy策略模式其实就是使用接口,而不要使用具体的类,通过委托的方式来使用算法,具体替换的时候只需要修改main方法中使用哪个实现类来实现罢了。

11、容器与内容的一致性Composite模式

当我们想查找某个文件夹中有什么东西时,找到的可能是文件夹,也可能是文件。简单说,找到的都是目录条目。将文件夹和文件都作为目录条目看待一样,将容器和内容作为同一种东西看待,可以帮助我们方便地处理问题。

具体代码如下:

public abstract class Entry {
    //获取名字
    public abstract String getName();
    //获取大小
    public abstract int getSize();
    //加入目录条目
    public Entry add(Entry entry) throws FileTreatMentException {
        throw new FileTreatMentException();
    }
    //显示目录条目一览
    public void printList() {
        printList("");
    }
    protected abstract void printList(String prefix);

    @Override
    public String toString() {
        return getName() + " (" + getSize() + ")";
    }
}

Entry类是具体的File以及Dicectory的父类,成为一个容器。

@NoArgsConstructor
public class FileTreatMentException extends RuntimeException {
    public FileTreatMentException(String message) {
        super(message);
    }
}
@AllArgsConstructor
public class File extends Entry {
    private String name;
    private int size;
    @Override
    public String getName() {
        return name;
    }

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

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

File子类继承于Entry,同时也继承了add方法,由于文件无法增加条目,只有文件夹可以,所以会抛出异常。

public class Dicectory extends Entry {
    //文件夹的名字
    private String name;
    //文件夹中目录条目的集合
    private List<Entry> directory = new ArrayList();
    public Dicectory(String name) {
        this.name = name;
    }
    @Override
    public Entry add(Entry entry) throws FileTreatMentException {
        directory.add(entry);
        return this;
    }

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

    @Override
    public int getSize() {
        int size = 0;
        Iterator<Entry> it = directory.iterator();
        while (it.hasNext()) {
            Entry entry = it.next();
            size += entry.getSize();
        }
        return size;
    }

    @Override
    protected void printList(String prefix) {
        System.out.println(prefix + "/" + this);
        Iterator<Entry> it = directory.iterator();
        while (it.hasNext()) {
            Entry entry = it.next();
            entry.printList(prefix + "/" + name);
        }
    }
}

Dicectory子类也继承于Entry,文件夹可以添加条目,所以它重写了add方法。getSize方法是一个比较重要的方法,这个就要看directory这个集合中包含的是什么,如果是文件夹,则递归;如果是文件,则直接调用File类的getSize方法,取得文件的大小。这就是Composite模式的特征“容器与内容的一致性”的表现。

public class CompositeMain {
    public static void main(String[] args) {
        try {
            System.out.println("Making root entries...");
            Dicectory rootdir = new Dicectory("root");
            Dicectory bindir = new Dicectory("bin");
            Dicectory tmpdir = new Dicectory("tmp");
            Dicectory usrdir = new Dicectory("usr");
            rootdir.add(bindir);
            rootdir.add(tmpdir);
            rootdir.add(usrdir);
            bindir.add(new File("vi",10000));
            bindir.add(new File("latex",20000));
            rootdir.printList();

            System.out.println("");
            System.out.println("Making user entries...");
            Dicectory yuki = new Dicectory("yuki");
            Dicectory hanako = new Dicectory("hanako");
            Dicectory tomura = new Dicectory("tomura");
            usrdir.add(yuki);
            usrdir.add(hanako);
            usrdir.add(tomura);
            yuki.add(new File("diary.html",100));
            yuki.add(new File("Composite.java",200));
            hanako.add(new File("memo.tex",300));
            tomura.add(new File("game.doc",400));
            tomura.add(new File("junk.mail",500));
            rootdir.printList();
        } catch (FileTreatMentException e) {
            e.printStackTrace();
        }
    }
}

运行结果

Making root entries... /root (30000) /root/bin (30000) /root/bin/vi (10000) /root/bin/latex (20000) /root/tmp (0) /root/usr (0)

Making user entries... /root (31500) /root/bin (30000) /root/bin/vi (10000) /root/bin/latex (20000) /root/tmp (0) /root/usr (1500) /root/usr/yuki (300) /root/usr/yuki/diary.html (100) /root/usr/yuki/Composite.java (200) /root/usr/hanako (300) /root/usr/hanako/memo.tex (300) /root/usr/tomura (900) /root/usr/tomura/game.doc (400) /root/usr/tomura/junk.mail (500) 使用Composite模式可以使容器与内容具有一致性,也可以称其为多个和单个的一致性,即将多个对象结合在一起,当做一个对象进行处理。

12、装饰边框与被装饰物的一致性Decorator模式

不断为对象添加装饰的设计模式被称为Decorator模式。

我们要展示一个字符串,并为其添加各种边框,Display为展示的抽象类。

public abstract class Display {
    /**
     * 获取横向字符数
     * @return
     */
    public abstract int getColumns();

    /**
     * 获取纵向行数
     * @return
     */
    public abstract int getRows();

    /**
     * 获取第几行的字符串
     * @param row
     * @return
     */
    public abstract String getRowText(int row);

    /**
     * 全部显示
     */
    public final void show() {
        for (int i = 0;i < getRows();i++) {
            System.out.println(getRowText(i));
        }
    }
}

StringDisplay是显示单行字符串的类

@AllArgsConstructor
public class StringDisplay extends Display {
    private String string; //要显示的字符串
    @Override
    public int getColumns() {  //字符串的字符数
        return string.getBytes().length;
    }

    @Override
    public int getRows() { //只有一行
        return 1;
    }

    @Override
    public String getRowText(int row) {
        if (row == 0) {
            return string;
        }else {
            return null;
        }
    }
}

Border边框类,继承于Display的抽象类

@AllArgsConstructor
public abstract class Border extends Display {
    protected Display display;  //被装饰物,此处采用委托机制
}

SideBorder一种具体的装饰边框,用指定的字符装饰字符串的左右两侧。

public class SideBorder extends Border {
    private char borderChar;  //装饰边框的字符
    public SideBorder(Display display,char ch) {
        super(display);
        this.borderChar = ch;
    }

    @Override
    public int getColumns() {    //字符串字符数加上两侧边框字符数
        return 1 + display.getColumns() + 1;
    }

    @Override
    public int getRows() {   //被装饰物的行数
        return display.getRows();
    }

    @Override
    public String getRowText(int row) { //字符串加两侧字符
        return borderChar + display.getRowText(row) + borderChar;
    }
}

FullBorder也是一种具体的装饰边框,它会给被装饰物上下左右都加上装饰边框。

public class FullBorder extends Border {
    public FullBorder(Display display) {
        super(display);
    }

    @Override
    public int getColumns() { //左右加1
        return 1 + display.getColumns() + 1;
    }

    @Override
    public int getRows() {  //上下加1
        return 1 + display.getRows() + 1;
    }

    @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) + "|";
        }
    }

    /**
     * 生成一个重复count次字符ch的字符串
     * @param ch
     * @param count
     * @return
     */
    private String makeLine(char ch,int count) {
        StringBuffer buf = new StringBuffer();
        for (int i = 0;i < count;i++) {
            buf.append(ch);
        }
        return buf.toString();
    }
}
public class DecoratorMain {
    public static void main(String[] args) {
        Display b1 = new StringDisplay("Hello,world.");
        Display b2 = new SideBorder(b1,'#');
        Display b3 = new FullBorder(b2);
        b1.show();
        b2.show();
        b3.show();
        Display b4 = new SideBorder(
                new FullBorder(
                        new SideBorder(
                                new FullBorder(
                                        new StringDisplay("你好,世界.")
                                ),'*'
                        )
                ),'/'
        );
        b4.show();
    }
}

运行结果

Hello,world. #Hello,world.# +--------------+ |#Hello,world.#| +--------------+ /+--------------------+/ /|*+----------------+*|/ /|*|你好,世界.|*|/ /|*+----------------+*|/ /+--------------------+/

在Decorator模式中,装饰边框与被装饰物具有一致性。Border类是Display类的子类,这就体现了它们之间的一致性。也就是说边框(Border)与被装饰物(Display)具有相同的接口(API)。在main方法中,b4被装饰了多次,但是接口(API)却没有发生变化。依然可以调用getColumns、getRows、getRowText以及show方法,这就是接口(API)的透明性。虽然接口是相同的,但是越装饰,功能则越多。我们完全不需要对被装饰的类做任务修改就实现了被装饰的类即可增加功能。Decorator模式使用了委托。对“装饰边框”提出的要求(调用装饰边框的方法)会被转交(委托)给“被装饰物”去处理。

继承和委托中的一致性

继承:父类和子类的一致性

子类和父类具有一致性

public class Parent {
    public void parentMethod() {
        System.out.println("It's parent");
    }
}
public class Child extends Parent {
    public void childMethod() {
        System.out.println("It's child");
    }

    public static void main(String[] args) {
        Parent obj = new Child();
        obj.parentMethod();
        ((Child)obj).childMethod();
    }
}

运行结果

It's parent It's child

Child类的实例可以被保存在Parent类型的变量中,也可以调用从Parent类中继承的方法。也就是说,可以像操作Parent类的实例一样操作Child类的实例。这是将子类当做父类看待。但是,反过来,如果想将父类当做子类一样操作,则需要先进行类型转换。

委托:自己和被委托对象的一致性

public class Rose {
    private Violet obj = new Violet();
    public void method() {
        obj.method();
    }
}
public class Violet {
    public void method() {
        System.out.println("it's violet");
    }
}

这两种花玫瑰和紫罗兰虽然都有method方法,但是却没有明确地在代码中体现出这个“共通性”。如果要明确的表示method方法是共通的,只需要编写一个共通的抽象类Flower就可以了。

public abstract class Flower {
    public abstract void method();
}
public class Rose extends Flower {
    private Violet obj = new Violet();
    public void method() {
        obj.method();
    }
}
public class Violet extends Flower {
    public void method() {
        System.out.println("it's violet");
    }
}

或者是让Flower作为接口也行

public interface Flower {
    public void method();
}
public class Rose implements Flower {
    private Violet obj = new Violet();
    public void method() {
        obj.method();
    }
}
public class Violet implements Flower {
    public void method() {
        System.out.println("it's violet");
    }
}

至于委托的obj是什么类型的可以视具体情况而定,一般可以定义为抽象类或者接口Flower类型。

13、访问数据结构并处理数据Visitor模式

在Visitor模式中,数据结构与处理被分离开来。我们编写一个“访问者”类来访问数据结构中的元素,并把对各元素的处理交给访问者。当需要增加新的处理时,我们只需要编写新的访问者,然后让数据结构可以接受访问者的访问即可。

该示例依然是对文件和文件夹的操作。访问者Visitor被接口Element接受.Entry类与Composite模式中的Entry类是一样的,不过它实现了Element接口。Visitor通过方法重载来访问文件和目录。

public abstract class Visitor {
    public abstract void visit(File file);
    public abstract void visit(Directory directory);
}
public interface Element {
    public void accept(Visitor v);
}
public abstract class Entry implements Element {
    //获取名字
    public abstract String getName();
    //获取大小
    public abstract int getSize();
    //增加目录条目
    public Entry add(Entry entry) throws FileTreatmentException {
        throw new FileTreatmentException();
    }
    //生成迭代器
    public Iterator iterator() throws FileTreatmentException {
        throw new FileTreatmentException();
    }
    //显示字符串
    @Override
    public String toString() {
        return getName() + " (" + getSize() + ")";
    }
}

File类与Composite模式中的File类一样,重点是它对Element接口accept的实现,让访问者可以访问自身的实例(this)。

@AllArgsConstructor
public class File extends Entry {
    private String name;
    private int size;
    public void accept(Visitor v) {
        v.visit(this);
    }

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

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

Directory类与Composite模式中的Directory类相比,增加了两个方法。一个是迭代器,是重写Entry抽象类的方法;一个是accept方法,是实现Element接口的方法。

public class Directory extends Entry {
    //文件夹名字
    private String name;
    //目录条目集合
    private List<Entry> dir = new ArrayList();

    public Directory(String name) {
        this.name = name;
    }
    //接受访问者的访问
    public void accept(Visitor v) {
        v.visit(this);
    }
    //获取名字
    @Override
    public String getName() {
        return name;
    }
    //获取大小
    @Override
    public int getSize() {
        int size = 0;
        Iterator<Entry> it = dir.iterator();
        while (it.hasNext()) {
            Entry entry = it.next();
            size += entry.getSize();
        }
        return size;
    }
    //增加目录条目
    @Override
    public Entry add(Entry entry) throws FileTreatmentException {
        dir.add(entry);
        return this;
    }
    //生成迭代器
    @Override
    public Iterator iterator() throws FileTreatmentException {
        return dir.iterator();
    }
}

因为Visitor是一个抽象类,我们需要一个具体访问的子类.ListVistor类的作用是访问数据结构并显示元素一览。

public class ListVisitor extends Visitor {
    //当前访问的文件夹的名字
    private String currentdir = "";

    /**
     * 访问文件时被调用
     * @param file
     */
    @Override
    public void visit(File file) {
        System.out.println(currentdir + "/" + file);
    }

    /**
     * 访问文件夹时被调用
     * @param directory
     */
    @Override
    public void visit(Directory directory) {
        System.out.println(currentdir + "/" +directory);
        String savedir = currentdir;
        currentdir = currentdir + "/" + directory.getName();
        Iterator<Entry> it = directory.iterator();
        while (it.hasNext()) {
            Entry entry = it.next();
            entry.accept(this);
        }
        currentdir = savedir;
    }
}
public class FileTreatmentException extends RuntimeException {
    public FileTreatmentException() {
    }

    public FileTreatmentException(String message) {
        super(message);
    }
}
public class VisitorMain {
    public static void main(String[] args) {
        try {
            System.out.println("Making root entries....");
            Directory rootdir = new Directory("root");
            Directory bindir = new Directory("bin");
            Directory tmpdir = new Directory("tmp");
            Directory usrdir = new Directory("usr");
            rootdir.add(bindir);
            rootdir.add(tmpdir);
            rootdir.add(usrdir);
            bindir.add(new File("vi",10000));
            bindir.add(new File("latex",20000));
            rootdir.accept(new ListVisitor());

            System.out.println("");
            System.out.println("Making user entries...");
            Directory yuki = new Directory("yuki");
            Directory hanako = new Directory("hanako");
            Directory tomura = new Directory("tomura");
            usrdir.add(yuki);
            usrdir.add(hanako);
            usrdir.add(tomura);
            yuki.add(new File("diary.html",100));
            yuki.add(new File("Composite.java",200));
            hanako.add(new File("memo.tex",300));
            tomura.add(new File("game.doc",400));
            tomura.add(new File("junk.mail",500));
            rootdir.accept(new ListVisitor());
        } catch (FileTreatmentException e) {
            e.printStackTrace();
        }
    }
}

运行结果

Making root entries.... /root (30000) /root/bin (30000) /root/bin/vi (10000) /root/bin/latex (20000) /root/tmp (0) /root/usr (0)

Making user entries... /root (31500) /root/bin (30000) /root/bin/vi (10000) /root/bin/latex (20000) /root/tmp (0) /root/usr (1500) /root/usr/yuki (300) /root/usr/yuki/diary.html (100) /root/usr/yuki/Composite.java (200) /root/usr/hanako (300) /root/usr/hanako/memo.tex (300) /root/usr/tomura (900) /root/usr/tomura/game.doc (400) /root/usr/tomura/junk.mail (500)

Visitor与Element之间的相互调用

  • 对于Directory类的实例和File类的实例,我们调用了它们的accept方法
  • 对于每一个Directory类的实例和File类的实例,我们只调用了一次它们的accept方法
  • 对于ListVisitor的实例,我们调用了它的visit(Directory)和visit(File)方法
  • 处理visit(Directory)和visit(File)的是一个ListVisitor的实例

在Visitor模式中,visit方法将“处理”都集中在ListVisitor里面了。

Visitor模式的目的是将处理从数据结构中分离出来。数据结构很重要,它能将元素集合和关联在一起。但是,保存数据结构与以数据结构为基础进行处理是两种不同的东西。在本示例中,Visitor模式提高了File类和Directory类作为组件的独立性。如果将进行处理的方法定义在File类和Directory类中,当每次要扩展功能,增加新的“处理”时,就不得不去修改File类和Directory类。

开闭原则——对扩展开放,对修改关闭

在不修改现有的代码的前提下进行扩展,这就是开闭原则。

14、推卸责任Chain of Responsibility模式

“推卸责任”听起来有些贬义的意思,但是有时候也确实存在需要“推卸责任”的情况。当外部请求程序进行某个处理,但程序暂时无法直接决定由哪个对象负责处理时,就需要推卸责任。将多个对象组成一条职责链,然后按照它们在职责链上的顺序一个一个地找出到底应该谁来负责处理。

Trouble类是表示发生问题的类,number是问题编号。

@AllArgsConstructor
@Getter
public class Trouble {
    private int number;

    @Override
    public String toString() {
        return "[Trouble " + number + "]";
    }
}

Support类是所有解决问题类的父类,它是一个抽象类

public abstract class Support {
    //解决问题的实例的名字
    private String name;
    //要推卸给的对象
    private Support next;
    public Support(String name) {
        this.name = name;
    }

    /**
     * 设置要推卸给的对象
     * @param next
     * @return
     */
    public Support setNext(Support next) {
        this.next = next;
        return next;
    }

    /**
     * 解决问题的步骤
     * @param trouble
     */
    public final void support(Trouble trouble) {
        if (resolve(trouble)) {
            done(trouble);
        }else if (next != null) {
            next.support(trouble);
        }else {
            fail(trouble);
        }
    }

    /**
     * 解决问题的方法,此处使用了Template Method模式
     * @param trouble
     * @return
     */
    protected abstract boolean resolve(Trouble trouble);

    /**
     * 解决
     * @param trouble
     */
    protected void done(Trouble trouble) {
        System.out.println(trouble + " is resolved by " + this + ".");
    }

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

    /**
     * 未解决
     * @param trouble
     */
    protected void fail(Trouble trouble) {
        System.out.println(trouble + " cannot be resolved.");
    }
}

NoSupport类是Support类的子类,它什么问题都不解决

public class NoSupport extends Support {
    public NoSupport(String name) {
        super(name);
    }

    @Override
    protected boolean resolve(Trouble trouble) {
        return false;
    }
}

LimitSupport类解决编号小于limit值的问题。

public class LimitSupport extends Support {
    //可以解决编号小于limit的问题
    private int limit;
    public LimitSupport(String name,int limit) {
        super(name);
        this.limit = limit;
    }

    @Override
    protected boolean resolve(Trouble trouble) {
        if (trouble.getNumber() < limit) {
            return true;
        }else {
            return false;
        }
    }
}

OddSupport类是解决奇数编号的问题

public class OddSupport extends Support {
    public OddSupport(String name) {
        super(name);
    }

    @Override
    protected boolean resolve(Trouble trouble) {
        if (trouble.getNumber() % 2 == 1) {
            return true;
        }else {
            return false;
        }
    }
}

SpecialSupport类只解决指定编号的问题

public class SpecialSupport extends Support {
    //只能解决指定的编号的问题
    private int number;
    public SpecialSupport(String name,int number) {
        super(name);
        this.number = number;
    }

    @Override
    protected boolean resolve(Trouble trouble) {
        if (trouble.getNumber() == number) {
            return true;
        }else {
            return false;
        }
    }
}
public class SupportMain {
    public static void main(String[] args) {
        Support alice = new NoSupport("Alice");
        Support bob = new LimitSupport("Bob",100);
        Support charlie = new SpecialSupport("Chailie",429);
        Support diana = new LimitSupport("Diana",200);
        Support elmo = new OddSupport("Elmo");
        Support fred = new LimitSupport("Fred",300);
        //形成责任链
        alice.setNext(bob).setNext(charlie).setNext(diana).setNext(elmo).setNext(fred);
        //制造各种问题
        for (int i = 0;i < 500;i += 33) {
            alice.support(new Trouble(i));
        }
    }
}

运行结果

[Trouble 0] is resolved by [Bob]. [Trouble 33] is resolved by [Bob]. [Trouble 66] is resolved by [Bob]. [Trouble 99] is resolved by [Bob]. [Trouble 132] is resolved by [Diana]. [Trouble 165] is resolved by [Diana]. [Trouble 198] is resolved by [Diana]. [Trouble 231] is resolved by [Elmo]. [Trouble 264] is resolved by [Fred]. [Trouble 297] is resolved by [Elmo]. [Trouble 330] cannot be resolved. [Trouble 363] is resolved by [Elmo]. [Trouble 396] cannot be resolved. [Trouble 429] is resolved by [Chailie]. [Trouble 462] cannot be resolved. [Trouble 495] is resolved by [Elmo].

根据结果来看,Alice肯定是不会去解决任何问题的,所以她推给了Bob,Bob能处理编号为100以内的问题,超过100他就无能为力了,所以他把问题推给了Charlie,但是Charlie只能处理编号为429的问题,所以他又推给了Diana,Diana能处理200以内的问题。超过200她也无能为力了,所以她推给了Elmo,Elmo能处理奇数问题等等以此类推...这里要注意的是,任何问题都是从Alice开始的,然后形成一个责任链,谁能处理这个问题,谁就处理。

Trouble 363时序图

Chain of Responsibility模式的最大优点就在于它弱化了发出请求的人和处理请求的人之间的关系。可以动态地改变职责链。使用Chain of Responsibility模式可以推卸请求,直至查到合适的处理请求的对象,可以提高程序的灵活性,但会导致处理请求发生延迟。如果请求和处理者之间的关系是确定的,而且需要非常快的处理速度,不使用Chain of Responsibility模式会更好。

15、简单窗口Facade模式

使用Facade模式可以为互相关联在一起的错综复杂的类整理出高层接口(API)。其中的Facade角色可以让系统对外只有一个简单的接口(API)。而且,Facade角色还会考虑到系统内部各个类之间的责任关系和依赖关系,按照正确的顺序调用各个类。

该示例中,Database是一个简单从文本获取数据的数据源

public class Database {
    /**
     * 防止外部new出实例
     */
    private Database() {

    }

    /**
     * 根据文件名获取Properties
     * @param dbname
     * @return
     */
    public static Properties getProperties(String dbname) {
        String filename = dbname + ".txt";
        Properties prop = new Properties();
        try {
            prop.load(new FileInputStream(filename));
        } catch (IOException e) {
            System.out.println("Warning:" + filename + " is not found");
        }
        return prop;
    }
}

文件中的数据

hyuki@hyuki.com=Hiroshi Yuki
hanako@hyuki.com=Hanako Sato
tomura@hyuki.com=Tomura
mamoru@hyuki.com=Mamoru Takahashi

HtmlWriter用于编写简单的Web页面

@AllArgsConstructor
public class HtmlWriter {
    private Writer writer;

    /**
     * 输出标题
     * @param title
     * @throws IOException
     */
    public void title(String title) throws IOException {
        writer.write("<html>");
        writer.write("<head>");
        writer.write("<title>" + title + "</title>");
        writer.write("</head>");
        writer.write("<body>\n");
        writer.write("<h1>" + title + "</h1>\n");
    }

    /**
     * 输出段落
     * @param msg
     * @throws IOException
     */
    public void paragraph(String msg) throws IOException {
        writer.write("<p>" + msg + "</p>\n");
    }

    /**
     * 输出超链接
     * @param href
     * @param caption
     * @throws IOException
     */
    public void link(String href,String caption) throws IOException {
        paragraph("<a href=\">" + href + "\">" + caption + "</a>");
    }

    /**
     * 输出邮件地址
     * @param mailaddr
     * @param username
     * @throws IOException
     */
    public void mailto(String mailaddr,String username) throws IOException {
        link("mailto:" + mailaddr,username);
    }

    /**
     * 结束输出html
     * @throws IOException
     */
    public void close() throws IOException {
        writer.write("</body>");
        writer.write("</html>\n");
        writer.close();
    }
}

PageMaker类使用Database类和HtmlWriter类来生成指定用户的Web页面。PageMaker类一手包办了调用HtmlWriter类的方法这一工作。对外部,它只是提供了makeWelcomePage接口。这就是一个简单窗口。

public class PageMaker {
    private PageMaker() {

    }
    public static void makeWelcomePage(String mailaddr,String filename) {
        try {
            Properties mailprop = Database.getProperties("maildata");
            String username = mailprop.getProperty(mailaddr);
            HtmlWriter writer = new HtmlWriter(new FileWriter(filename));
            writer.title("Welcome to " + username + "'s page!");
            writer.paragraph(username + "欢迎来到" + username + "的主页");
            writer.paragraph("等着你的邮件哦!");
            writer.mailto(mailaddr,username);
            writer.close();
            System.out.println(filename + " is created for " + mailaddr + " (" + username + ")");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
public class FacadeMain {
    public static void main(String[] args) {
        PageMaker.makeWelcomePage("hyuki@hyuki.com","welcome.html");
    }
}

运行结果

welcome.html is created for hyuki@hyuki.com (Hiroshi Yuki)

我们在写一个很庞大的系统的时候,可能类会非常多,非常复杂,因为我们使用的是面向对象的设计,而不是面向过程的,所以有时候看代码非常困难,很多类方法,我们并不知道该怎么时候使用。对于某一个需求来说,我们可以把这些调用关系放在一个接口里面。在设计类时,我们还需要考虑将哪些方法的可见性设为public。如果公开的方法过多,会导致类的内部修改变的困难。与设计类一样,在设计包时,需要考虑类的可见性。如果让包的外部看到了类,包内部代码就会变的困难。

在超大系统中,往往都含有非常多的类和包。如果我们在每个关键的地方都使用Facade模式,那么系统的维护就会变的轻松很多。通常熟悉系统内部复杂处理的开发人员可能不太愿意创建Facade角色。因为对熟练的开发人员而言,系统中的所有信息全部都记忆在脑中,他们对类之间的所有相互依赖关系都一清二楚。当别人看不懂的时候,对于那些明确地用语言描述出来的知识,我们不应该将他们隐藏在自己的脑袋中,而是应该用代码将它们表现出来。这就意味着我们需要引入Facade角色了。

16、只有一个仲裁者Mediator模式

在一个团队中有一个仲裁者,整个团队向仲裁者报告,仲裁者向组员下达指示。组员之间不再相互询问和相互指示。在Mediator模式中,“仲裁者”被称为Mediator,各组员被称为Colleague。

Mediator接口是表示仲裁者的接口。具体的仲裁者会实现这个接口。

public interface Mediator {
    /**
     * 创建组员
     */
    public void createColleagues();

    /**
     * 组员报告
     */
    public void colleagueChanged();
}

Colleague接口是表示向仲裁者进行报告的组员的接口。具体的组员会实现这个接口。

public interface Colleague {
    /**
     * 确立仲裁者
     * @param mediator
     */
    public void setMediator(Mediator mediator);

    /**
     * 仲裁者下达的指示
     * @param enabled
     */
    public void setColleagueEnabled(boolean enabled);
}

ColleagueButton是具体的组员之一

public class ColleagueButton extends Button implements Colleague {
    private Mediator mediator;
    public ColleagueButton(String label) throws HeadlessException {
        super(label);
    }

    /**
     * 保存Mediator
     * @param mediator
     */
    public void setMediator(Mediator mediator) {
        this.mediator = mediator;
    }

    /**
     * Mediator下达启用/禁用的指示
     * @param enabled
     */
    public void setColleagueEnabled(boolean enabled) {
        setEnabled(enabled);
    }
}

ColleagueTextField是具体的组员之一。

public class ColleagueTextField extends TextField implements TextListener,Colleague {
    private Mediator mediator;
    public ColleagueTextField(String text, int columns) throws HeadlessException {
        super(text, columns);
    }

    /**
     * 保存Mediator
     * @param mediator
     */
    public void setMediator(Mediator mediator) {
        this.mediator = mediator;
    }

    /**
     * Mediator下达启用/禁用的指示
     * @param enabled
     */
    public void setColleagueEnabled(boolean enabled) {
        setEnabled(enabled);
        setBackground(enabled ? Color.WHITE : Color.lightGray);
    }

    /**
     * 当文字发生变化时通知Mediator
     * @param e
     */
    public void textValueChanged(TextEvent e) {
        mediator.colleagueChanged();
    }
}

ColleagueCheckbox是组员之一

public class ColleagueCheckbox extends Checkbox implements ItemListener,Colleague {
    private Mediator mediator;
    public ColleagueCheckbox(String label, CheckboxGroup group, boolean state) throws HeadlessException {
        super(label, group, state);
    }

    /**
     * 保存Mediator
     * @param mediator
     */
    public void setMediator(Mediator mediator) {
        this.mediator = mediator;
    }

    /**
     * Mediator下达启用/禁用指示
     * @param enabled
     */
    public void setColleagueEnabled(boolean enabled) {
        setEnabled(enabled);
    }

    /**
     * 当状态发生变化时通知Mediator
     * @param e
     */
    public void itemStateChanged(ItemEvent e) {
        mediator.colleagueChanged();
    }
}

LoginFrame类是具体的仲裁者,所有最终的决定都是由仲裁者的colleagueChanged方法下达的。

public class LoginFrame extends Frame implements ActionListener,Mediator{
    private ColleagueCheckbox checkGuest;
    private ColleagueCheckbox checkLogin;
    private ColleagueTextField textUser;
    private ColleagueTextField textPass;
    private ColleagueButton buttonOk;
    private ColleagueButton buttonCancel;

    /**
     * 构造函数,生成并配置各个Colleague后,显示对话框
     * @param title
     * @throws HeadlessException
     */
    public LoginFrame(String title) throws HeadlessException {
        super(title);
        setBackground(Color.lightGray);
        //使用布局管理器生成4*2窗格
        setLayout(new GridLayout(4,2));
        //生成各个Colleague
        createColleagues();
        add(checkGuest);
        add(checkLogin);
        add(new Label("Username:"));
        add(textUser);
        add(new Label("Password"));
        add(textPass);
        add(buttonOk);
        add(buttonCancel);
        //设置初始的启用/禁用状态
        colleagueChanged();
        //显示
        pack();
        show();
    }

    /**
     * 生成各个colleague
     */
    public void createColleagues() {
        CheckboxGroup g = new CheckboxGroup();
        checkGuest = new ColleagueCheckbox("Guest",g,true);
        checkLogin = new ColleagueCheckbox("Login",g,false);
        textUser = new ColleagueTextField("",10);
        textPass = new ColleagueTextField("",10);
        textPass.setEchoChar('*');
        buttonOk = new ColleagueButton("OK");
        buttonCancel = new ColleagueButton("Cancel");
        //设置Mediator
        checkGuest.setMediator(this);
        checkLogin.setMediator(this);
        textUser.setMediator(this);
        textPass.setMediator(this);
        buttonOk.setMediator(this);
        buttonCancel.setMediator(this);
        //设置Listener
        checkGuest.addItemListener(checkGuest);
        checkLogin.addItemListener(checkLogin);
        textUser.addTextListener(textUser);
        textPass.addTextListener(textPass);
        buttonOk.addActionListener(this);
        buttonCancel.addActionListener(this);
    }

    /**
     * 接受来自于Colleage的通知然后判断各Colleage的启用/禁用状态
     */
    public void colleagueChanged() {
        //Guest mode
        if (checkGuest.getState()) {
            textUser.setColleagueEnabled(false);
            textPass.setColleagueEnabled(false);
            buttonOk.setColleagueEnabled(true);
        }else {
            //Login mode
            textUser.setColleagueEnabled(true);
            userpassChanged();
        }
    }

    /**
     * 当textUser或textPass文本输入框中的文字发生变化时
     * 判断各Colleage的启用/禁用状态
     */
    private void userpassChanged() {
        if (textUser.getText().length() > 0) {
            textPass.setColleagueEnabled(true);
            if (textPass.getText().length() > 0) {
                buttonOk.setColleagueEnabled(true);
            }else {
                buttonOk.setColleagueEnabled(false);
            }
        }else {
            textPass.setColleagueEnabled(false);
            buttonOk.setColleagueEnabled(false);
        }
    }
    public void actionPerformed(ActionEvent e) {
        System.out.println(e.toString());
        System.exit(0);
    }
}
public class MediatorMain {
    public static void main(String[] args) {
        new LoginFrame("Mediator Sample");
    }
}

运行结果

当点Guest的时候,Username和Password的输入框都是不可用的

当点Login的时候,只有输入完Username和Password的时候,OK按钮才变为可用。

该模式中重点在于LoginFrame中的colleagueChanged方法,它控制着所有的colleague的启用/禁用状态,所以其他地方并没有控制控件启用/禁用状态的逻辑处理。如果这段逻辑分散在各个colleague类(ColleagueButton,ColleagueTextField,ColleagueCheckbox)中,那么无论编写代码还是调试代码,都会非常困难。

假设我们现在需要制作另外一个对话框,那么所有的colleague类都可以复用,但Mediator角色不行。因为所有的colleague角色中,并没有任何依赖于特定对话框的代码,而所有依赖于特定应用程序的部分都被封装到了Mediator角色中。依赖于特定应用程序就意味着难以复用。

17、发送状态变化通知Observer模式

Observer是“观察者”的意思。适用于根据对象状态进行相应处理的场景。

Observer接口是表示“观察者”的接口。具体的观察者会实现这个接口。用于生成数值的NumberGenerator类会调用update方法,会将“生成的数值发生了变化,请更新显示内容”的通知发送给Observer.

public interface Observer {
    public void update(NumberGenerator generator);
}

NumberGenerator类是用于生成数值的抽象类,生成数值的方法(execute方法)和获取数值的方法(getNumber方法)都是抽象方法,需要子类去实现。

public abstract class NumberGenerator {
    //保存Observer们
    private List<Observer> observers = new ArrayList();

    /**
     * 注册Observer
     * @param observer
     */
    public void addObserver(Observer observer) {
        observers.add(observer);
    }

    /**
     * 删除Observer
     * @param observer
     */
    public void deleteObserver(Observer observer) {
        observers.remove(observer);
    }

    /**
     * 向所有Observer发送通知,告诉它们"我生成的数值发生了变化,
     * 请更新显示内容"
     */
    public void notifyObservers() {
        Iterator<Observer> it = observers.iterator();
        while (it.hasNext()) {
            Observer o = it.next();
            o.update(this);
        }
    }

    /**
     * 获取数值
     * @return
     */
    public abstract int getNumber();

    /**
     * 生成数值
     */
    public abstract void execute();
}

RandomNumberGenerator类是NumberGenerator的子类,它会生成随机数。

public class RandomNumberGenerator extends NumberGenerator {
    //随机数生成器
    private Random random = new Random();
    //当前数值
    private int number;
    @Override
    public int getNumber() {
        return number;
    }

    @Override
    public void execute() {
        for (int i = 0;i < 20;i++) {
            number = random.nextInt(50);
            notifyObservers();
        }
    }
}

DigitObserver类实现了Observer接口,它的功能是以数字形式显示观察到的数值。

public class DigitObserver implements Observer {
    public void update(NumberGenerator generator) {
        System.out.println("DigitObserver:" + generator.getNumber());
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

GraphObserver类也实现了Observer接口,它的功能是会将观察到的数值以******的形式显示出来。

public class GraphObserver implements Observer {
    public void update(NumberGenerator generator) {
        System.out.println("GraphObserver:");
        int count = generator.getNumber();
        for (int i = 0;i < count;i++) {
            System.out.print("*");
        }
        System.out.println("");
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
public class ObserverMain {
    public static void main(String[] args) {
        NumberGenerator generator = new RandomNumberGenerator();
        Observer observer1 = new DigitObserver();
        Observer observer2 = new GraphObserver();
        generator.addObserver(observer1);
        generator.addObserver(observer2);
        generator.execute();
    }
}

运行结果

DigitObserver:41 GraphObserver: ***************************************** DigitObserver:31 GraphObserver: ******************************* DigitObserver:10 GraphObserver: ********** DigitObserver:35 GraphObserver: *********************************** DigitObserver:37 GraphObserver: ************************************* DigitObserver:15 GraphObserver: *************** DigitObserver:40 GraphObserver: **************************************** DigitObserver:19 GraphObserver: ******************* DigitObserver:38 GraphObserver: ************************************** DigitObserver:36 GraphObserver: ************************************ DigitObserver:2 GraphObserver: ** DigitObserver:42 GraphObserver: ****************************************** DigitObserver:26 GraphObserver: ************************** DigitObserver:43 GraphObserver: ******************************************* DigitObserver:5 GraphObserver: ***** DigitObserver:47 GraphObserver: *********************************************** DigitObserver:48 GraphObserver: ************************************************ DigitObserver:21 GraphObserver: ********************* DigitObserver:46 GraphObserver: ********************************************** DigitObserver:21 GraphObserver: *********************

在Observer模式中,一方面RandomNumberGenerator类并不知道,也无需在意正在观察自己的到底是DigitObserver类的实例还是GraphObserver类的实例。不过它知道在它的observers字段中所保存的观察者们都实现了Observer接口,一定可以调用它们的update方法。另一方面,DigitObserver类也无需在意自己正在观察的是RandomNumberGenerator类的实例还是其他XXXNumberGenerator类的实例。不过,DigitObserver类知道它们是NumberGenerator类的子类的实例,并持有getNumber方法。

  • 利用抽象类和接口从具体类中抽出抽象方法
  • 在将实例作为参数传递至类中,或者在类的字段中保存实例时,不使用具体类型,而是使用抽象类型和接口。

这样实现方式可以帮助我们轻松替换具体类。

18、保存对象状态Memento模式

Memento有“备忘录”的意思,使用面向对象编程的方式实现撤销功能时,需要事先保存实例的相关状态信息。然后在撤销时,还需要根据所保存的信息将实例恢复至原来的状态。

这是一个通过掷骰子来收集水果和获取金钱的游戏,骰子为1,金钱增加;骰子为2,金钱减少;骰子为6,获得水果。

Memento类是记录游戏玩家Gamer的状态的类。

public class Memento {
    //所持金钱
    private int money;
    //获得的水果
    private List<String> fruits = new ArrayList();
    public int getMoney() {
        return money;
    }
    public Memento(int money) {
        this.money = money;
    }

    /**
     * 添加水果
     * @param fruit
     */
    public void addFruit(String fruit) {
        this.fruits.add(fruit);
    }

    /**
     * 获取当前所持所有水果
     * @return
     */
    public List<String> getFruits() {
        return (List<String>) ((ArrayList<String>)fruits).clone();
    }
}

Gamer类是表示游戏玩家的类

public class Gamer {
    //所持金钱
    private int money;
    //获得的水果
    private List<String> fruits = new ArrayList();
    //随机数生成器
    private Random random = new Random();
    //表示水果种类的数组
    private static String[] fruitsname = {"苹果","葡萄","香蕉","橘子"};
    public Gamer(int money) {
        this.money = money;
    }

    /**
     * 获得当前所持金钱
     * @return
     */
    public int getMoney() {
        return money;
    }

    /**
     * 掷骰子进行游戏
     */
    public void bet() {
        //掷骰子
        int dice = random.nextInt(6) + 1;
        if (dice == 1) {
            money += 100;
            System.out.println("所持金钱增加了");
        }else if (dice == 2) {
            money /= 2;
            System.out.println("所持金钱减半了");
        }else if (dice == 6) {
            String f = getFruit();
            System.out.println("获得了水果(" + f + ")。");
            fruits.add(f);
        }else {
            System.out.println("什么都没有发生");
        }
    }

    /**
     * 拍摄快照
     * @return
     */
    public Memento createMemento() {
        Memento m = new Memento(money);
        Iterator<String> it = fruits.iterator();
        while (it.hasNext()) {
            String f = it.next();
            //只保存好吃的水果
            if (f.startsWith("好吃的")) {
                m.addFruit(f);
            }
        }
        return m;
    }

    /**
     * 撤销
     * @param memento
     */
    public void restoreMemento(Memento memento) {
        this.money = memento.getMoney();
        this.fruits = memento.getFruits();
    }

    /**
     * 获得一个水果
     * @return
     */
    public String getFruit() {
        String prefix = "";
        if (random.nextBoolean()) {
            prefix = "好吃的";
        }
        return prefix + fruitsname[random.nextInt(fruitsname.length)];
    }

    @Override
    public String toString() {
        return "[money = " + money + ", fruits = " + fruits + "]";
    }
}
public class MenentoMain {
    public static void main(String[] args) {
        //最初的所持金钱为100
        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);
            }
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("");
        }
    }
}

运行结果(部分)

====0 当前状态:[money = 100, fruits = []] 什么都没有发生 所持金钱为100元。

====1 当前状态:[money = 100, fruits = []] 什么都没有发生 所持金钱为100元。

====2 当前状态:[money = 100, fruits = []] 所持金钱增加了 所持金钱为200元。 (所持金钱增加许多,因此保存游戏当前的状态)

====3 当前状态:[money = 200, fruits = []] 所持金钱减半了 所持金钱为100元。

====4 当前状态:[money = 100, fruits = []] 所持金钱减半了 所持金钱为50元。 (所持金钱减少了许多,因此游戏恢复至以前的状态)

====5 当前状态:[money = 200, fruits = []] 什么都没有发生 所持金钱为200元。

====6 当前状态:[money = 200, fruits = []] 什么都没有发生 所持金钱为200元。

====7 当前状态:[money = 200, fruits = []] 获得了水果(葡萄)。 所持金钱为200元。

====8 当前状态:[money = 200, fruits = [葡萄]] 什么都没有发生 所持金钱为200元。

====9 当前状态:[money = 200, fruits = [葡萄]] 获得了水果(苹果)。 所持金钱为200元。

====10 当前状态:[money = 200, fruits = [葡萄, 苹果]] 获得了水果(好吃的葡萄)。 所持金钱为200元。

====11 当前状态:[money = 200, fruits = [葡萄, 苹果, 好吃的葡萄]] 什么都没有发生 所持金钱为200元。

====12 当前状态:[money = 200, fruits = [葡萄, 苹果, 好吃的葡萄]] 什么都没有发生 所持金钱为200元。

====13 当前状态:[money = 200, fruits = [葡萄, 苹果, 好吃的葡萄]] 所持金钱增加了 所持金钱为300元。 (所持金钱增加许多,因此保存游戏当前的状态)

====14 当前状态:[money = 300, fruits = [葡萄, 苹果, 好吃的葡萄]] 什么都没有发生 所持金钱为300元。

====15 当前状态:[money = 300, fruits = [葡萄, 苹果, 好吃的葡萄]] 什么都没有发生 所持金钱为300元。

====16 当前状态:[money = 300, fruits = [葡萄, 苹果, 好吃的葡萄]] 所持金钱增加了 所持金钱为400元。 (所持金钱增加许多,因此保存游戏当前的状态)

====17 当前状态:[money = 400, fruits = [葡萄, 苹果, 好吃的葡萄]] 获得了水果(好吃的香蕉)。 所持金钱为400元。

====18 当前状态:[money = 400, fruits = [葡萄, 苹果, 好吃的葡萄, 好吃的香蕉]] 所持金钱减半了 所持金钱为200元。

====19 当前状态:[money = 200, fruits = [葡萄, 苹果, 好吃的葡萄, 好吃的香蕉]] 所持金钱增加了 所持金钱为300元。

====20 当前状态:[money = 300, fruits = [葡萄, 苹果, 好吃的葡萄, 好吃的香蕉]] 获得了水果(葡萄)。 所持金钱为300元。

====21 当前状态:[money = 300, fruits = [葡萄, 苹果, 好吃的葡萄, 好吃的香蕉, 葡萄]] 什么都没有发生 所持金钱为300元。

====22 当前状态:[money = 300, fruits = [葡萄, 苹果, 好吃的葡萄, 好吃的香蕉, 葡萄]] 什么都没有发生 所持金钱为300元。

====23 当前状态:[money = 300, fruits = [葡萄, 苹果, 好吃的葡萄, 好吃的香蕉, 葡萄]] 什么都没有发生 所持金钱为300元。

====24 当前状态:[money = 300, fruits = [葡萄, 苹果, 好吃的葡萄, 好吃的香蕉, 葡萄]] 什么都没有发生 所持金钱为300元。

====25 当前状态:[money = 300, fruits = [葡萄, 苹果, 好吃的葡萄, 好吃的香蕉, 葡萄]] 所持金钱增加了 所持金钱为400元。

====26 当前状态:[money = 400, fruits = [葡萄, 苹果, 好吃的葡萄, 好吃的香蕉, 葡萄]] 所持金钱增加了 所持金钱为500元。 (所持金钱增加许多,因此保存游戏当前的状态)

====27 当前状态:[money = 500, fruits = [葡萄, 苹果, 好吃的葡萄, 好吃的香蕉, 葡萄]] 所持金钱增加了 所持金钱为600元。 (所持金钱增加许多,因此保存游戏当前的状态)

====28 当前状态:[money = 600, fruits = [葡萄, 苹果, 好吃的葡萄, 好吃的香蕉, 葡萄]] 什么都没有发生 所持金钱为600元。

====29 当前状态:[money = 600, fruits = [葡萄, 苹果, 好吃的葡萄, 好吃的香蕉, 葡萄]] 什么都没有发生 所持金钱为600元。

====30 当前状态:[money = 600, fruits = [葡萄, 苹果, 好吃的葡萄, 好吃的香蕉, 葡萄]] 获得了水果(好吃的苹果)。 所持金钱为600元。

====31 当前状态:[money = 600, fruits = [葡萄, 苹果, 好吃的葡萄, 好吃的香蕉, 葡萄, 好吃的苹果]] 什么都没有发生 所持金钱为600元。

====32 当前状态:[money = 600, fruits = [葡萄, 苹果, 好吃的葡萄, 好吃的香蕉, 葡萄, 好吃的苹果]] 所持金钱增加了 所持金钱为700元。 (所持金钱增加许多,因此保存游戏当前的状态)

====33 当前状态:[money = 700, fruits = [葡萄, 苹果, 好吃的葡萄, 好吃的香蕉, 葡萄, 好吃的苹果]] 所持金钱减半了 所持金钱为350元。

19、用类表示状态State模式

State的意思是”状态“。以类来表示状态后,我们就能通过切换类来方便地改变对象的状态。当需要增加新的状态时,如何修改代码这个问题也会很明确。

这是一个警戒状态每小时会改变一次的金库警报系统,以1秒来对应现实中的1小时。

State接口是表示金库状态的接口。在State接口中定义了以下事件对应的API。

  • 设置时间
  • 使用金库
  • 按下警铃
  • 正常通话
public interface State {
    /**
     * 设置时间
     * @param context
     * @param hour
     */
    public void doClock(Context context,int hour);

    /**
     * 使用金库
     * @param context
     */
    public void doUse(Context context);

    /**
     * 按下警铃
     * @param context
     */
    public void doAlarm(Context context);

    /**
     * 正常通话
     * @param context
     */
    public void doPhone(Context context);
}

DayState类是表示白天的状态,是State接口的实现类,是一个单例模式

public class DayState implements State {
    private static DayState singleton = new DayState();
    private DayState() {

    }
    public static State getInstance() {
        return singleton;
    }
    public void doClock(Context context, int hour) {
        if (hour < 9 || 17 <= hour) {
            context.changeState(NightState.getInstance());
        }
    }

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

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

    public void doPhone(Context context) {
        context.callSecurityCenter("正常通话(白天)");
    }

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

NightState类是表示晚上的状态,也是State接口的实现类,也是一个单例模式。

public class NightState implements State {
    private static NightState singleton = new NightState();
    private NightState() {

    }
    public static State getInstance() {
        return singleton;
    }
    public void doClock(Context context, int hour) {
        if (9 <= hour && hour < 17) {
            context.changeState(DayState.getInstance());
        }
    }

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

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

    public void doPhone(Context context) {
        context.recordLog("晚上的通话录音");
    }

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

Context接口是负责管理状态和联系警报中心的接口,它要完成4个动作

  • 设置时间
  • 改变状态
  • 联系警报中心
  • 在警报中心留下记录
public interface Context {
    /**
     * 设置时间
     * @param hour
     */
    public void setClock(int hour);

    /**
     * 改变状态
     * @param state
     */
    public void changeState(State state);

    /**
     * 联系警报中心
     * @param msg
     */
    public void callSecurityCenter(String msg);

    /**
     * 在警报中心留下记录
     * @param msg
     */
    public void recordLog(String msg);
}

SafeFrame类是使用GUI实现警报系统界面的类(safe有”金库“的意思),它实现了Context接口。这里并没有先去判断当前时间是白天还是晚上,也没有判断金库的状态,而是直接调用了doUse方法。这就是State模式的特点。

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("Use Safe");
    //按下警铃按钮
    private Button buttonAlarm = new Button("Press the alarm bell");
    //正常通话按钮
    private Button buttonPhone = new Button("Normal call");
    //结束按钮
    private Button buttonExit = new Button("End");
    //当前的状态
    private State state = DayState.getInstance();

    public SafeFrame(String title) throws HeadlessException {
        super(title);
        setBackground(Color.lightGray);
        setLayout(new BorderLayout());
        //配置textClock
        add(textClock,BorderLayout.NORTH);
        textClock.setEditable(false);
        //配置textScreen
        add(textScreen,BorderLayout.CENTER);
        textScreen.setEditable(false);
        //为界面添加按钮
        Panel panel = new Panel();
        panel.add(buttonUse);
        panel.add(buttonAlarm);
        panel.add(buttonPhone);
        panel.add(buttonExit);
        //显示界面
        add(panel,BorderLayout.SOUTH);
        pack();
        show();
        //设置监听器
        buttonUse.addActionListener(this);
        buttonAlarm.addActionListener(this);
        buttonPhone.addActionListener(this);
        buttonExit.addActionListener(this);
    }

    /**
     * 设置时间
     * @param hour
     */
    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);
    }

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

    /**
     * 联系警报中心
     * @param msg
     */
    public void callSecurityCenter(String msg) {
        textScreen.append("call! " + msg + "\n");
    }

    /**
     * 在警报中心留下记录
     * @param msg
     */
    public void recordLog(String msg) {
        textScreen.append("record ..." + msg + "\n");
    }

    /**
     * 按钮被按下后该方法会被调用
     * @param e
     */
    public void actionPerformed(ActionEvent e) {
        System.out.println(e.toString());
        //金库使用按钮
        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("?");
        }
    }
}

在该时序图中展示了状态改变前后的doUse方法的调用流程。最初调用的是DayState类的doUse方法,当changeState后,变为了调用NightState类的doUse方法。

public class StateMain {
    public static void main(String[] args) {
        SafeFrame frame = new SafeFrame("State Sample");
        while (true) {
            for (int hour = 0;hour < 24;hour++) {
                frame.setClock(hour);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

运行结果

在State模式中,我们用类来表示状态,并为每一种具体的状态都定义一个相应的类。这样问题就被分解了,当状态非常多的时候,State模式的优势就会非常明显。在使用State模式时,需要注意应当是谁来管理状态迁移。在本实例中的缺点是每个状态类都必须知道其他的状态类,彼此依赖过强,如果想解除这种强依赖可以使用Mediator模式(仲裁者模式)。

20、共享对象,避免浪费Flyweight模式

Flyweight是“轻量级”的意思。在Java中,一般是用new来给对象分配空间,当程序中需要大量对象时,如果都使用new关键字来分配内存,将会消耗大量内存空间。关于Flyweight模式,一言以蔽之就是“通过尽量共享实例来避免new出实例”。

BigChar类是从文件中读取字符串(包括多行),放入到fontdata中。

public class BigChar {
    //字符名字
    private char charname;
    //大型字符对应的字符串(由'#','.','\n'组成)
    private String fontdata;
    public BigChar(char charname) {
        this.charname = charname;
        try {
            BufferedReader reader = new BufferedReader(new FileReader("big" + charname + ".txt"));
            String line;
            StringBuffer buf = new StringBuffer();
            while ((line = reader.readLine()) != null) {
                buf.append(line);
                buf.append("\n");
            }
            reader.close();
            this.fontdata = buf.toString();
        } catch (IOException e) {
            this.fontdata = charname + "?";
        }
    }
    public void print() {
        System.out.print(fontdata);
    }
}

BigCharFactory类是生成BigChar类的实例的工厂。它使用的是单例模式。它的作用是在HashMap中获取已经存入的实例,如果没有该实例则生成新的实例。

public class BigCharFactory {
    //管理已经生成的BigChar的实例
    private Map<String,BigChar> pool = new HashMap();
    //单例模式
    private static BigCharFactory singleton = new BigCharFactory();
    private BigCharFactory() {

    }
    public static BigCharFactory getInstance() {
        return singleton;
    }

    /**
     * 生成(共享)BigChar类的实例
     * @param charname
     * @return
     */
    public synchronized BigChar getBigChar(char charname) {
        BigChar bc = pool.get("" + charname);
        if (bc == null) {
            bc = new BigChar(charname); //生成BigChar的实例
            pool.put("" + charname,bc);
        }
        return bc;
    }
}

BigString类表示由BigChar组成的“大型字符串”的类。在其构造函数中,bigchars数组并不是被new出来的,而是通过factory.getBigChar()获取的。

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();
        }
    }
}
public class FlyWeightMain {
    public static void main(String[] args) {
        if (args.length == 0) {
            System.out.println("Usage: java Main digits");
            System.out.println("Example: java Main 1212123");
            System.exit(0);
        }
        BigString bs = new BigString(args[0]);
        bs.print();
    }
}

我们有一个big3.txt的文件

我们在main方法的参数中配置一个3

运行结果

.........#######....# ######........###.... ............#######..

不要让被共享的实例被垃圾回收器回收了,在java程序中可以通过new关键字分配内存空间。如果分配了过多内存,就会导致内存不足。JVM的垃圾回收原理是寻找Java对象内未被引用的对象,就会被回收掉,而在BigCharFactory中,只要让HashMap pool来管理的BigChar的实例,就不会被看做是垃圾,即使该BigChar的实例已经不再被BigString类的实例所使用。相反,要想让实例可以被垃圾回收器回收掉,只需要从pool中移除该实例的Entry,就删除了对该实例的引用。

21、只在必要时生成实例Proxy模式

Proxy是“代理人”的意思,在面向对象编程中,“本人”和“代理人”都是对象。如果“本人”对象太忙了,有些工作无法亲自完成,就将其交给“代理人”对象负责。

Printable接口用于使PrinterProxy类(代理人)和Printer类(本人)具有一致性。

public interface Printable {
    /**
     * 设置名字
     * @param name
     */
    public void setPrinterName(String name);

    /**
     * 获取名字
     * @return
     */
    public String getPrinterName();

    /**
     * 显示文字(打印输出)
     * @param string
     */
    public void print(String string);
}

Printer类是表示“本人”的类,我们让他做一些所谓的“重活”(heavyJob)

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

    /**
     * 设置名字
     * @param name
     */
    public void setPrinterName(String name) {
        this.name = name;
    }

    /**
     * 获取名字
     * @return
     */
    public String getPrinterName() {
        return name;
    }

    /**
     * 显示带打印机名字的文字
     * @param string
     */
    public void print(String string) {
        System.out.println("=== " + name + " ===");
        System.out.println(string);
    }

    /**
     * 重活
     * @param msg
     */
    private void heavyJob(String msg) {
        System.out.print(msg);
        for (int i = 0;i < 5;i++) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.print(".");
        }
        System.out.println("结束。");
    }
}

PrinterProxy是扮演“代理人”角色的类。不论setPrinterName方法和getPrinterName方法被调用多少次,都不会生成Printer类的实例。Printer类并不知道PrinterProxy类的存在,即Printer类并不知道自己到底是通过PrinterProxy被调用的还是直接被调用的。PrinterProxy类是知道Printer类的,它有一个Printer类的real字段。

@NoArgsConstructor
public class PrinterProxy implements Printable {
    //名字
    private String name;
    //本人
    private Printer real;
    public PrinterProxy(String name) {
        this.name = name;
    }

    /**
     * 设置名字
     * @param name
     */
    public synchronized void setPrinterName(String name) {
        if (real != null) {
            real.setPrinterName(name);//同时设置"本人"的名字
        }
        this.name = name;
    }

    /**
     * 获取名字
     * @return
     */
    public String getPrinterName() {
        return name;
    }

    /**
     * 显示
     * @param string
     */
    public void print(String string) {
        realize();
        real.print(string);
    }

    /**
     * 生成"本人"
     */
    private synchronized void realize() {
        if (real == null) {
            real = new Printer(name);
        }
    }
}
public class ProxyMain {
    public static void main(String[] args) {
        Printable p = new PrinterProxy("Alice");
        System.out.println("现在的名字是" + p.getPrinterName() + "。");
        p.setPrinterName("Bob");
        System.out.println("现在的名字是" + p.getPrinterName() + "。");
        p.print("Hello,world");
    }
}

运行结果

现在的名字是Alice。 现在的名字是Bob。 Printer的实例生成中(Bob).....结束。 === Bob === Hello,world

示例程序的时序图

代理模式是将轻活交给代理类去做,重活留给本人去做,分工合作,可以让本人更加专注。代理类本身通过"委托"本人,将重点工作交给本人去完成。而且代理类和本人都实现了同一个接口,因此调用者完全不必在意调用的究竟是哪个类。无论是直接使用“本人”类还是通过代理类间接地使用“本人”类都可以。

22、命令也是类Command模式

我们把对一个类调用自己或是其他类的方法的过程,用一个类来表示“请进行这项工作“的“命令”,在设计模式中,我们称这样的命令为Command模式。Command有时也被称为事件。它与“事件驱动编程”中的事件是一样的意思。当发生动作时,我们可以先将这些事件做成实例,然后按照发生顺序放入队列中。接着再依次去处理它们。

该程序是一个在Windows窗口中画红色原点的程序。

Command接口是表示“命令”的接口。

package com.guanjian.command.command;

/**
 * Created by Administrator on 2019/1/9.
 */
public interface Command {
    public void execute();
}

MacroCommand类表示“由多条命令整合成的命令”。该类实现了Command接口。

package com.guanjian.command.command;

import java.util.Iterator;
import java.util.Stack;

/**
 * Created by Administrator on 2019/1/9.
 */
public class MacroCommand implements Command {
    //命令的集合
    private Stack<Command> commands = new Stack();

    /**
     * 执行
     */
    public void execute() {
        Iterator<Command> it = commands.iterator();
        while (it.hasNext()) {
            it.next().execute();
        }
    }

    /**
     * 添加命令
     * @param cmd
     */
    public void append(Command cmd) {
        if (cmd != this) {
            commands.push(cmd);
        }
    }

    /**
     * 删除最后一条命令
     */
    public void undo() {
        if (!commands.empty()) {
            commands.pop();
        }
    }

    /**
     * 删除所有命令
     */
    public void clear() {
        commands.clear();
    }
}

Drawable接口表示绘制一个坐标点

package com.guanjian.command.drawer;

/**
 * Created by Administrator on 2019/1/9.
 */
public interface Drawable {
    public void draw(int x,int y);
}

DrawCommand类实现了Command接口,表示“绘制一个点的命令”。

package com.guanjian.command.drawer;

import com.guanjian.command.command.Command;
import lombok.AllArgsConstructor;

import java.awt.*;

/**
 * Created by Administrator on 2019/1/9.
 */
@AllArgsConstructor
public class DrawCommand implements Command {
    //绘制对象
    protected Drawable drawable;
    //绘制位置
    private Point position;

    /**
     * 执行
     */
    public void execute() {
        drawable.draw(position.x,position.y);
    }
}

DrawCanvas类是Drawable的实现类,是java.awt.Canvas的子类。

package com.guanjian.command.drawer;

import com.guanjian.command.command.MacroCommand;

import java.awt.*;

/**
 * Created by Administrator on 2019/1/9.
 */
public class DrawCanvas extends Canvas implements Drawable {
    //颜色
    private Color color = Color.red;
    //要绘制的原点的半径
    private int radius = 6;
    //命令的历史记录
    private MacroCommand history;
    public DrawCanvas(int width,int height,MacroCommand history) {
        setSize(width,height);
        setBackground(Color.white);
        this.history = history;
    }

    /**
     * 重新全部绘制
     * @param g
     */
    public void paint(Graphics g) {
        history.execute();
    }

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

CommandMain类是启动应用程序的类。

package com.guanjian.command;

import com.guanjian.command.command.Command;
import com.guanjian.command.command.MacroCommand;
import com.guanjian.command.drawer.DrawCanvas;
import com.guanjian.command.drawer.DrawCommand;

import javax.swing.*;
import java.awt.event.*;

/**
 * Created by Administrator on 2019/1/9.
 */
public class CommandMain extends JFrame implements ActionListener,MouseMotionListener,WindowListener {
    //绘制的历史记录
    private MacroCommand history = new MacroCommand();
    private MacroCommand command = new MacroCommand();
    //绘制区域
    private DrawCanvas canvas = new DrawCanvas(400,400,history);
    //删除按钮
    private JButton clearButton = new JButton("clear");
    //重新绘制按钮
    private JButton reDraw = new JButton("redraw");

    public CommandMain(String title) {
        super(title);
        this.addWindowListener(this);
        canvas.addMouseMotionListener(this);
        clearButton.addActionListener(this);
        reDraw.addActionListener(this);
        Box buttonBox = new Box(BoxLayout.X_AXIS);
        buttonBox.add(clearButton);
        buttonBox.add(reDraw);
        Box mainBox = new Box(BoxLayout.Y_AXIS);
        mainBox.add(buttonBox);
        mainBox.add(canvas);
        getContentPane().add(mainBox);
        pack();
        show();
    }

    /**
     * 清除所有绘制,重新绘制
     * @param e
     */
    public void actionPerformed(ActionEvent e) {
        //清除绘制
        if (e.getSource() == clearButton) {
            history.clear();
            canvas.setHistory(history);
            canvas.repaint();
        }
        //重新绘制
        if (e.getSource() == reDraw) {
            if (command != null) {
                canvas.setHistory(command);
                canvas.repaint();
            }
        }
    }

    /**
     * 当鼠标按住拖动时绘制
     * @param e
     */
    public void mouseDragged(MouseEvent e) {
        Command cmd = new DrawCommand(canvas,e.getPoint());
        history.append(cmd);
        command.append(cmd);
        cmd.execute();
    }

    public void mouseMoved(MouseEvent e) {

    }

    public void windowOpened(WindowEvent e) {

    }

    public void windowClosing(WindowEvent e) {
        System.exit(0);
    }

    public void windowClosed(WindowEvent e) {

    }

    public void windowIconified(WindowEvent e) {

    }

    public void windowDeiconified(WindowEvent e) {

    }

    public void windowActivated(WindowEvent e) {

    }

    public void windowDeactivated(WindowEvent e) {

    }

    public static void main(String[] args) {
        new CommandMain("Command Pattern Sample");
    }
}

时序图如下:

运行结果

我们用鼠标在上面画图时,可以随便画出线条。

点clear可以清除绘制,点redraw可以重新绘制。

关于“命令”中应该包含哪些信息,并没有绝对的答案。命令的目的不同,应该包含的信息也不同。

23、语法规则也是类Interpreter模式

Interpreter是“解释器”的意思,在Interpreter模式中,程序要解决的问题会被用非常简单的“迷你语言”表述出来,即用“迷你语言”编写的“迷你程序”把具体的问题表述出来。当问题发生变化时,我们无需修改Java代码,而是直接修改用迷你语言编写的迷你程序即可。

假设有这么一段迷你代码我们需要对其进行语法解析

program end program go end program go right go right go right go right end program repeat 4 go right end end program repeat 4 repeat 3 go right go left end right end end

Node类是一个抽象类,它是语法树中各个部分(节点)中最顶层的类。它有一个解析语法的抽象方法parse.

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

其中Context是表示语法解析上下文的类。它的主要功能是得到我们需要的每一个迷你命令。

public class Context {
    //字符串拆解,拆解标志" ","\t","\n","\r","\f"
    private StringTokenizer tokenizer;
    //需要的字符串
    private String currentToken;
    public Context(String text) {
        tokenizer = new StringTokenizer(text);
        nextToken();
    }
    public String nextToken() {
        if (tokenizer.hasMoreElements()) {
            currentToken = tokenizer.nextToken();
        }else {
            currentToken = null;
        }
        return currentToken;
    }
    public String currentToken() {
        return currentToken;
    }

    /**
     * 跳过某个字符串
     * @param token
     * @throws ParseException
     */
    public void skipToken(String token) throws ParseException {
        if (!token.equals(currentToken)) {
            throw new ParseException("Warning:" + token + " is expected,but " + currentToken + " is found.");
        }
        nextToken();
    }

    /**
     * 获取数字
     * @return
     * @throws ParseException
     */
    public int currentNumber() throws ParseException {
        int number = 0;
        try {
            number = Integer.parseInt(currentToken);
        } catch (NumberFormatException e) {
            throw new ParseException("Warning: " + e);
        }
        return number;
    }
}

由于这些迷你命令都是以program开头,所以我们有一个ProgramNode类,用以跳过这个开头。

public class ProgramNode extends Node {
    private Node commandListNode;
    @Override
    public void parse(Context context) throws ParseException {
        context.skipToken("program"); //解析时跳过program开头
        commandListNode = new CommandListNode();
        commandListNode.parse(context);
    }

    @Override
    public String toString() {
        return "[program " + commandListNode + "]";
    }
}

CommandListNode类用以解析中间的命令,每个命令又以end结尾。

public class CommandListNode extends Node {
    private List<Node> list = 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");  //递归终结于end
                break;
            }else {
                //递归解析上下文
                Node commandNode = new CommandNode();
                commandNode.parse(context);
                list.add(commandNode);
            }
        }
    }

    @Override
    public String toString() {
        return list.toString();
    }
}

中间的命令可能有很多个,所以我们又需要对每单个命令进行解析。

public class CommandNode extends 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重复命令,我们需要拿取到它的重复次数

public class RepeatCommandNode extends Node {
    private int number;
    private Node commandListNode;
    @Override
    public void parse(Context context) throws ParseException {
        context.skipToken("repeat");
        //拿取重复次数
        number = context.currentNumber();
        //递归解析后面的上下文
        context.nextToken();
        commandListNode = new CommandListNode();
        commandListNode.parse(context);
    }

    @Override
    public String toString() {
        return "[repeat " + number +
                " " + commandListNode +
                "]";
    }
}

除了重复命令,其他的都是原始命令,我们就使用原始命令类PrimitiveCommandNode进行解析。原始命令包括go,left,right.

public class PrimitiveCommandNode extends Node {
    //原始命令名
    private String name;
    @Override
    public String toString() {
        return name;
    }

    @Override
    public void parse(Context context) throws ParseException {
        name = context.currentToken();
        context.skipToken(name);
        if (!name.equals("go") && !name.equals("right") && !name.equals("left")) {
            throw new ParseException(name + " is undefinded");
        }
    }
}

ParseExceprion是一个异常类,当遇到异常时抛出。

public class ParseException extends Exception {
    public ParseException(String message) {
        super(message);
    }
}
public class InterpreterMain {
    public static void main(String[] args) {
        try {
            BufferedReader reader = new BufferedReader(new FileReader("program.txt"));
            String text;
            while ((text = reader.readLine()) != null) {
                System.out.println("text = \"" + text + "\"");
                Node node = new ProgramNode();
                node.parse(new Context(text));
                System.out.println("node = " + node);
            }
            reader.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

这个program.txt就是我们之前写出来的迷你命令。

运行结果

text = "program end" node = [program []] text = "program go end" node = [program [go]] text = "program go right go right go right go right end" node = [program [go, right, go, right, go, right, go, right]] text = "program repeat 4 go right end end" node = [program [[repeat 4 [go, right]]]] text = "program repeat 4 repeat 3 go right go left end right end end" node = [program [[repeat 4 [[repeat 3 [go, right, go, left]], right]]]]

Interpreter模式主要是在一些表达式(正则表达式,检索表达式)以及一些批处理语言中使用的比较多。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 浅析类装载 顶

    [Loaded com.guanjian.Parent from file:/E:/classload/out/production/classload/] ...

    算法之名
  • 使用动态代理只代理接口(非实现类)

    假设现在我们有一个已知的算法,我们需要写任意一个接口打上我们特有的标签,那么这个接口的方法都可以执行这个算法,好比Mybatis的Dao,或者Feign的接口。...

    算法之名
  • 浅谈mybatis的日志适配模式 顶

    Java开发中经常用到的日志框架有很多,Log4j、Log4j2、slf4j等等,Mybatis定义了一套统一的日志接口供上层使用,并为上述常用的日志框架提供了...

    算法之名
  • java编程思想第四版第十章习题

    用户7798898
  • JAVA中的23种设计模式(GOF)

    volatile 保证数据的可见性,让线程内存数据的变化立刻显示到主存中,而且有序性可以避免指令重排,但是不保证原子性。

    HcodeBlogger
  • 10(01)总结形式参数,包,修饰符,内部类

    类,抽象类,接口的综合小练习 /* 教练和运动员案例(学生分析然后讲解) 乒乓球运动员和篮球运动员。 乒乓球教练和篮球教练。 为了出国交流,跟乒乓球相关...

    Java帮帮
  • 超详细:常用的设计模式汇总

    简单点说,就是一个应用程序中,某个类的实例对象只有一个,你没有办法去new,因为构造器是被private修饰的,一般通过getInstance()的方法来获取它...

    Java团长
  • 来自 BAT 大牛总结的常用设计模式汇总

    原文链接:https://cnblogs.com/chenshuyong/p/9998164.html

    业余草
  • java编程思想第四版第九章习题

    用户7798898
  • 设计模式详解

    需要说明的一点是,文中的 UML 类图和规范的 UML 类图不大相同,其中组合关系使用以下箭头表示:

    琯琯

扫码关注云+社区

领取腾讯云代金券