一、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模式
具体代码以下:
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之间的相互调用
在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模式主要是在一些表达式(正则表达式,检索表达式)以及一些批处理语言中使用的比较多。