設計模式整理

一、Iterator模式html

Iterator模式能夠幫助咱們分離具體的集合跟遍歷,就是在代碼中更換了集合,也能夠不須要從新調用新集合的方法。java

該圖中,Aggregate爲一個集合的接口,他有一個公共方法iterator(),返回一個迭代器接口,也就是說他能夠返回任何該迭代器接口的具體實現類。node

具體代碼以下git

/**
 * 集合接口
 */
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編程

二、Adapter模式canvas

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電壓讓自來水管出水同樣。

三、交給子類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.|
+------------+
+------------------+
|你好,世界。|
|你好,世界。|
|你好,世界。|
|你好,世界。|
|你好,世界。|
+------------------+

咱們理解類的層次時,一般是站在子類的角度進行思考,容易着眼於如下幾點。

  • 在子類中可使用父類中定義的方法。
  • 能夠經過在子類中增長方法以實現新的功能。
  • 在子類中重寫父類的方法能夠改變程序的行爲。

站在父類的角度思考,咱們聲明瞭抽象方法,將該方法的實現交給了子類。聲明抽象方法達到如下目的。

  • 期待子類去實現抽象方法。
  • 要求子類去實現抽象方法。

四、將實例的生成交給子類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卡。

五、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.

六、經過複製生成實例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. /
////////////////

七、組裝複雜的實例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文件,用瀏覽器打開以下

八、將關聯零件組裝成產品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參數

運行結果

九、將類的功能層次結構與實現層次結構分離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類則不須要作任何修改。

十、總體的替換算法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方法中使用哪一個實現類來實現罷了。

十一、容器與內容的一致性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模式可使容器與內容具備一致性,也能夠稱其爲多個和單個的一致性,即將多個對象結合在一塊兒,當作一個對象進行處理。

十二、裝飾邊框與被裝飾物的一致性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類型。

1三、訪問數據結構並處理數據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類。

開閉原則——對擴展開放,對修改關閉

在不修改現有的代碼的前提下進行擴展,這就是開閉原則。

1四、推卸責任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模式會更好。

1五、簡單窗口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角色了。

1六、只有一個仲裁者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角色中。依賴於特定應用程序就意味着難以複用。

1七、發送狀態變化通知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方法。

  • 利用抽象類和接口從具體類中抽出抽象方法
  • 在將實例做爲參數傳遞至類中,或者在類的字段中保存實例時,不使用具體類型,而是使用抽象類型和接口。

這樣實現方式能夠幫助咱們輕鬆替換具體類。

1八、保存對象狀態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元。

1九、用類表示狀態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,就刪除了對該實例的引用。

2一、只在必要時生成實例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

示例程序的時序圖

代理模式是將輕活交給代理類去作,重活留給本人去作,分工合做,可讓本人更加專一。代理類自己經過"委託"本人,將重點工做交給本人去完成。並且代理類和本人都實現了同一個接口,所以調用者徹底沒必要在乎調用的到底是哪一個類。不管是直接使用「本人」類仍是經過代理類間接地使用「本人」類均可以。

2二、命令也是類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能夠從新繪製。

關於「命令」中應該包含哪些信息,並無絕對的答案。命令的目的不一樣,應該包含的信息也不一樣。

2三、語法規則也是類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模式主要是在一些表達式(正則表達式,檢索表達式)以及一些批處理語言中使用的比較多。

相關文章
相關標籤/搜索