设计模式
迭代器模式(Iterator)
场景
定义一个书架BookShelf去放置书籍Book,然后再定义个迭代器Iterator用于去遍历这个书架的书籍。
UML图
类和接口一览表
该模式下登场的角色
- Iterator(迭代器)
该角色负责定义按顺序逐个遍历元素的接口( API)。在示例程序中,由Iterator接口扮演这个角色,它定义了hasNext和next两个方法。其中,hasNext 方法用于判断是否存在下一个元素,next方法则用于获取该元素。
- Concretelterator (具体的迭代器)
该角色负责实现Iterator角色所定义的接口( API)。在示例程序中,由BookShelfIterator类 扮演这个角色。该角色中包含了遍历集合所必需的信息。在示例程序中,BookShelf类的实例保 存在bookShelf字段中,被指向的书的下标保存在index字段中。
- Aggregate(集合)
该角色负责定义创建Iterator角色的接口(API)。这个接口(API)是一个方法,会创建出“按顺序访问保存在我内部元素的人”。在示例程序中,由Aggregate接口扮演这个角色,它里面定义了iterator方法。
- ConcreteAggregate(具体的集合)
该角色负责实现Aggregate角色所定义的接口(API)。它会创建出具体的Iterator角色,即Concretelterator角色。在示例程序中,由BookShelf类扮演这个角色,它实现了iterator 方法。
代码实现
Aggregate
public interface Aggregate {
Iterator iterator();
}
Iterator
public interface Iterator {
boolean hasNext();
Object next();
}
Book
public class Book {
private String bookName;
public String getBookName() {
return this.bookName;
}
public void setBookName(String bookName) {
this.bookName = bookName;
}
public Book(String bookName) {
this.bookName = bookName;
}
@Override
public String toString() {
return "{" +
" bookName='" + getBookName() + "'" +
"}";
}
}
Bookshelf
public class BookShelf implements Aggregate{
private List<Book> books=new ArrayList<>();
private int last;
public BookShelf(int maxsize){
this.last=0;
}
public void appendBook(Book book){
books.add(book);
}
public Book getBookAt(int cursor){
return books.get(cursor);
}
public int getLength(){
return books.size();
}
public int getLast(){
return last;
}
@Override
public Iterator iterator() {
return new BookIterator(this);
}
}
BookShelfIterator
public class BookIterator implements Iterator{
private BookShelf bookShelf;
private int cursor;
public BookIterator(BookShelf bookShelf){
this.bookShelf=bookShelf;
this.cursor=0;
}
@Override
public boolean hasNext() {
return this.cursor<bookShelf.getLength();
}
@Override
public Object next() {
return bookShelf.getBookAt(this.cursor++);
}
}
Main
public class IteratorPattern {
public static void main(String[] args) {
BookShelf bookShelf=new BookShelf(3);
Book a=new Book("A");
Book b=new Book("B");
Book c=new Book("C");
bookShelf.appendBook(a);
bookShelf.appendBook(b);
bookShelf.appendBook(c);
Iterator it=bookShelf.iterator();
while(it.hasNext()){
Book book=(Book)it.next();
System.out.println(book.getBookName());
}
}
}
使用该设计模式的好处
不管实现如何变化,都可以使用Iterator
对于遍历BookShelf中的books,为啥不直接for循环遍历books?而使用Iterator来遍历books?
一个重要的理由是,引入Iterator后可以将遍历与实现分离开来。
while(it.hasNext()){
Book book=(Book)it.next();
System.out.println(book.getBookName());
}
这里只使用了Iterator的hasNext和next方法,并没有直接调用BookShelf的方法。也就是说,这里的while循环并不依赖与BookShelf的实现。
如果BookShelf不使用arrayList来存储book了,换成数组来存储,会怎么样呢?若是直接对books进行for循环遍历的情况下,需要对for循环遍历代码重新修改,而使用Iterator进行遍历books的话,则不需要修改while循环中的代码,因为Iterator不依赖于BookShelf的实现。
适配器模式(Adapter)
场景
Banner类有将字符串用括号括起来的方法和用星号括起来的方法,Print接口申明了两种方法,一种弱化字符的打印方法和一种强化字符串的方法。使用一个适配器PrintBanner去将Banner的加括号和加星号的方法分别适配于Print弱化和强化字体的这两个方法,再给请求者去请求这两个方法以达到目的。
有两种适配模式:类适配模式(使用继承来适配),对象适配模式(使用委托来适配)
UML图
- 类适配模式(使用继承来适配)
- 对象适配模式(使用委托来适配)
类和接口一览表(共用)
该模式下登场的角色
-
Target(对象)
该角色负责定义所需的方法,请求者会调用其定义的方法。正如print接口(使用继承时)或print类(使用委托时)扮演此角色
-
Client(请求者)
该角色负责使用Target角色所定义的方法进行处理。正如Main类扮演此角色。
-
Adaptee(被适配者)
该角色的方法会让适配器调用其方法适配Target对应的方法。换句话说如果Adaptee的方法与Target的方法一致,那就不需要接下来的Adapter角色了。正如Banner扮演此角色。
-
Adapter(适配器)
该角色通过调用Adaptee的方法调用方法去适配Target的方法。正如PrintBanner类扮演此角色。
代码实现
- 类适配模式(使用继承来适配)
Banner
public class Banner {
private String string;
public Banner(String string){
this.string=string;
}
public void showWithParen(){
System.out.println("("+string+")");
}
public void showWithAster(){
System.out.println("*"+string+"*");
}
}
public interface Print {
void printWeak();
void printStrong();
}
printBannerAdapter
public class BannerPrintAdapter extends Banner implements Print{
public BannerPrintAdapter(String string) {
super(string);
}
@Override
public void printWeak() {
this.showWithParen();
}
@Override
public void printStrong() {
this.showWithAster();
}
}
AdapterPattern
public class AdapterPattern {
public static void main(String[] args) {
Print print=new BannerPrintAdapter("content");
print.printStrong();
print.printWeak();
}
}
- 对象适配模式(使用委托来适配)
与上面的区别在于:若Print并不是接口而是一个类,java无法继承多个类,所以通过组合去适配Banner,来调用其方法。这两种的方法目的都是能适配Banner来调用其方法。
只有Print和PrintBannerAdapter与上面有区别
public abstract class Print {
public abstract void printWeak();
public abstract void printStrong();
}
PrintBannerAdapter
public class BannerPrintAdapter extends Print{
private Banner banner;
public BannerPrintAdapter(String string) {
this.banner= new Banner(string);
}
@Override
public void printWeak() {
this.banner.showWithParen();
}
@Override
public void printStrong() {
this.banner.showWithAster();
}
}
使用该设计模式的好处
-
当现有的类已经被充分测试过了,Bug很少,而且已经被用于其他软件中,此时有新需求需要在这个类的上修改功能,我们便可以使用适配器模式来将这个原有的类作为组件复用,只需在适配器这个类中进行修改即可完成需求;同时在出现bug时,由于原有的类(adaptee)之前已被充分测试过,在找bug时我们可以优先在adapter类中寻找问题。
-
当涉及版本升级与兼容性时,如果能够完全抛弃旧版本,那么软件的维护工作将会轻松很多,但是现实往往无法这样做。这时可以使用适配器模式,让新版本来扮演Adaptee这个角色,旧版本扮演Target角色。然后编写一个Adapter角色的类,去使用新版本的类来实现旧版本的方法,从而达到两个版本的兼容。
模板模式(Template)
场景
在AbstractDisplay类中定义了display方法,而且在该方法中依次调用了open、print、close这3个方法。虽然这3个方法已经在AbstractDisplay中被声明了,但都是没有实体的抽象方法。这里,调用抽象方法的display方法就是模板方法。
而实际上实现了open、print、close这3个抽象方法的是AbstractDisplay的子类CharDisplay类和StringDisplay类。
UML图
类和接口一览表
该模式下登场的角色
-
AbstractClass(抽象类)
AbstractClass角色不仅负责实现模板方法,还负责声明在模板方法中所使用到的抽象方法。这些抽象方法由子类ConcreteClass角色负责实现。在示例程序中,由AbstractDisplay类扮演此角色。
-
ConcreteClass(具体类)
该角色负责具体实现AbstractClass角色中定义的抽象方法。这里实现的方法将会在AbstractClass角色的模板方法中被调用。在示例程序中,由CharDisplay类和StringDisplay类扮演此角色。
代码实现
- AbstractDisplay
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();
}
}
- CharDisplay
public class CharDisplay extends AbstractDisplay {
private char c;
public CharDisplay(char c) {
this.c = c;
}
@Override
public void open() {
System.out.print("<<");
}
@Override
public void print() {
System.out.print(c);
}
@Override
public void close() {
System.out.println(">>");
}
}
- StringDisplay
public class StringDisplay extends AbstractDisplay {
private String str;
public StringDisplay(String str) {
this.str = str;
}
@Override
public void open() {
printLine();
}
@Override
public void print() {
System.out.println("|"+str+"|");
}
@Override
public void close() {
printLine();
}
private void printLine() {
System.out.print("+");
for (int i = 0; i < str.length(); i++) {
System.out.print("-");
}
System.out.println("+");
}
}
- TemplatePattern
public class TemplatePattern {
public static void main(String[] args) {
AbstractDisplay charDisplay=new CharDisplay('a');
AbstractDisplay strDisplay=new StringDisplay("Hello,world");
charDisplay.display();
strDisplay.display();
}
}
使用该设计模式的好处
优点在于由于在父类的模板方法中编写了算法,因此无需在每个子类再编写算法,当我们在模板方法中发现Bug时,只需要修改模板方法即可解决问题。
工厂方法模式(FactoryMethod)
场景
制作身份证的场景,Product类和Factory类属于framework包。这两个类组成了生成实例的框架。IDCard类和IDCardFactory类负责实际的加工处理,它们属于idcard包。Main类是用于测试程序行为的类。
UML图
类和接口一览表
该模式下登场的角色
-
Product(产品)
Product角色属于框架这一方,是-一个抽象类。它定义了在Factory Method模式中生成的那些实例所持有的接口( API),但具体的处理则由子类ConcreteProduct角色决定。在示例程序中,由Product类扮演此角色。 -
Creator (创建者)
Creator角色属于框架这一方,它是负责生成Product角色的抽象类,但具体的处理则由子类ConcreteCreator角色决定。在示例程序中,由Factory类扮演此角色。 -
ConcreteProduct (具体的产品)
ConcreteProduct角色属于具体加工这一方,它决定了具体的产品。在示例程序中,由IDCard类扮演此角色。 -
ConcreteCreator (具体的创建者)
ConcreteCreator角色属于具体加工这一方,它负责生成具体的产品。在示例程序中,由IDCardFactory类扮演此角色。
代码实现
- frame.Factory
public abstract class Factory {
public abstract Product createProduct(String owner);
public abstract void registerProduct(Product p);
public Product create(String owner) {
Product p = createProduct(owner);
registerProduct(p);
return p;
}
}
- frame.Product
public abstract class Product {
public abstract void use();
}
- idcard.IdcardFactory
public class IdcardFactory extends Factory {
private List<String> owners = new ArrayList<>();
@Override
public Product createProduct(String owner) {
System.out.println(owner + " is creating idcard");
return new IdcardProduct(owner);
}
@Override
public void registerProduct(Product p) {
owners.add(((IdcardProduct) p).getOwner());
}
public List<String> getOwners() {
return this.owners;
}
}
- idcard.IdcardProduct
public class IdcardProduct extends Product {
private String owner;
public IdcardProduct(String owner) {
this.owner = owner;
}
public String getOwner() {
return this.owner;
}
@Override
public void use() {
System.out.println(owner + " is using idcard");
}
}
- FactoryPattern
public class FactoryPattern {
public static void main(String[] args) {
Factory factory=new IdcardFactory();
Product a=factory.create("a");
Product b=factory.create("b");
Product c=factory.create("c");
a.use();
b.use();
c.use();
}
}
使用该设计模式的好处
在framework包中我们并没有引入idcard包。在Product类和Factory 类中,并没有出现IDCard和IDCardFactory等具体类的名字。因此,即使用已有的框架生成全新的类时,也完全不需要对framework进行修改,若有新的产品,如手机,那就添加一个phone包,在这个包里只需继承或实现Product和Factory即可完成添加一个产品和产品的创建。
单例模式(Sington)
场景
当一个类只要有一个实例时的时候,需要用到单例模式。
UML图
类和接口一览表
该模式下登场的角色
- Singleton
在Singleton模式中,只有Singleton这一个角色。Singleton 角色中有一个返回唯一实例的static方法。该方法总是会返回同一个实例。
代码实现
- Singleton
public class Singleton {
public String str;
public static Singleton singleton = new Singleton();
private Singleton() {
}
public static Singleton getSingleton() {
return singleton;
}
}
- SingletonPattern
public class SingletonPattern {
public static void main(String[] args) {
Singleton s1 = Singleton.getSingleton();
s1.str = "hello,world";
Singleton s2 = Singleton.getSingleton();
System.out.println(s2.str);
}
}
使用该设计模式的好处
Singleton模式对实例的数量设置了限制。当存在多个实例时,实例之间互相影响,可能会产生意想不到的bug。但是,如果我们可以确保只有一个实例,就可以在这个前提条件下放心地编程了。
原型模式(Prototype)
场景
首先让我们来看-段使用了Prototype 模式的示例程序。以下这段示例程序的功能是将字符串放入方框中显示出来或是加上下划线显示出来。
Product 接口和Manager类属于framework包,负责复制实例。虽然Manager类会调用createClone方法,但是对于具体要复制哪个类一无所知。不过,只要是实现了Product接口的类,调用它的createClone方法就可以复制出新的实例。
MessageBox类和UnderLinePen类是两个实现了Product接口的类。只要事先将这两个类的实例“注册”到Manager类中,就可以随时复制新的实例。
UML图
类和接口一览表
该模式下登场的角色
- Prototype (原型)
Product角色负责定义用于复制现有实例来生成新实例的方法。在示例程序中,由Product接口扮演此角色。 - ConcretePrototype (具体的原型)
ConcretePrototype角色负责实现复制现有实例并生成新实例的方法。在示例程序中,由MessageBox类和UnderlinePen类扮演此角色。 - Client (使用者)
Client角色负责使用复制实例的方法生成新的实例。在示例程序中,由Manager类扮演此角色。
代码实现
- frame.Product
public interface Product extends Cloneable {
void use(String content);
Product createClone();
}
- frame.Manager
public class Manager {
private Map<String, Product> showcase = new HashMap<>();
public void register(String productName, Product product) {
showcase.put(productName, product);
}
public Product create(String ProductName) {
Product p = showcase.get(ProductName);
return p.createClone();
}
}
- MessageBox
public class MessageBox implements Product {
private char charcode;
public MessageBox(char charcode) {
this.charcode = charcode;
}
@Override
public void use(String content) {
for (int i = 0; i < content.length(); i++) {
System.out.print(charcode);
}
System.out.println();
System.out.println(content);
for (int i = 0; i < content.length(); i++) {
System.out.print(charcode);
}
System.out.println();
}
@Override
public Product createClone() {
try {
return (Product) this.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
return null;
}
}
}
- UnderLinePen
public class UnderLinePen implements Product {
private char charcode;
public UnderLinePen(char charcode) {
this.charcode = charcode;
}
@Override
public void use(String content) {
System.out.println(content);
for (int i = 0; i < content.length(); i++) {
System.out.print(charcode);
}
System.out.println();
}
@Override
public Product createClone() {
try {
return (Product) this.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
return null;
}
}
}
- PrototypePattern
public class PrototypePattern {
public static void main(String[] args) {
MessageBox b1 = new MessageBox('-');
MessageBox b2 = new MessageBox('*');
UnderLinePen uPen = new UnderLinePen('_');
Manager manager = new Manager();
manager.register("lineMessageBox", b1);
manager.register("starMessageBox", b2);
manager.register("uPen", uPen);
Product lineMessageBox = manager.create("lineMessageBox");
Product starMessageBox = manager.create("starMessageBox");
Product underLinepen = manager.create("uPen");
lineMessageBox.use("content");
starMessageBox.use("content");
underLinepen.use("content");
}
}
使用该设计模式的好处
-
难以根据类生成实例时
当要一个类要经过复杂的操作才可生成一个实例时,可使用该设计模式,复制实例,从而避免复杂的操作去创建实例。
-
想解耦框架与生成实例时
在示例程序中,我们将复制( clone )实例的部分封装在framework包中了。
在Manager类的create方法中,我们并没有使用类名,取而代之使用了"strongmessage"和"slash box" 等字符串为生成的实例命名。与Java语言自带的生成实例的new Something()方式相比,这种方式具有更好的通用性,而且将框架从类名的束缚中解脱出来了。
建造者模式(Builder)
场景
Builder类中定义了决定文档结构的方法,然后Director类使用该方法编写一个具体的文档。Builder是抽象类,它并没有进行任何实际的处理,仅仅声明了抽象方法。Builder类的子类决定了用来编写文档的具体处理。
UML图
类和接口一览表
该模式下登场的角色
-
Builder(建造者)
Builder角色负责定义用于生成实例的接口(API )。Builder 角色中准备了用于生成实例的方法。由 Builder类扮演此角色。
-
ConcreteBuilder (具体的建造者)
ConcreteBuilder角色是负责实现 Builder角色的接口的类(API)。这里定义了在生成实例时实际被调用的方法。此外,在ConcreteBuilder角色中还定义了获取最终生成结果的方法。由TextBuilder类扮演此角色。
-
Director(监工)
Director角色负责使用Builder 角色的接口(API)来生成实例。它并不依赖于ConcreteBuilder角色。为了确保不论ConcreteBuilder角色是如何被定义的,Director角色都能正常工作,它只调用在Builder角色中被定义的方法。在示例程序中,由 Director类扮演此角色。
代码实现
- Builder
public interface Builder {
void makeTitle(String title);
void makeString(String content);
void makeItems(String[] items);
void close();
}
- Director
public class Director {
private Builder builder;
public Director(Builder bulider) {
this.builder = bulider;
}
public void constructor() {
// 编写文档
builder.makeTitle("Greeting"); // 标题
builder.makeString("从早上至下午"); // 字符串
builder.makeItems(new String[]{ // 条目
"早上好。",
"下午好。",
});
builder.makeString("晚上"); // 其他字符串
builder.makeItems(new String[]{ // 其他条目
"晚上好。",
"晚安。",
"再见。",
});
builder.close(); // 完成文档
}
}
- TextBuilder
public class TextBuilder implements 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 content) {
buffer.append('■' + content + "\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 this.buffer.toString();
}
}
- BuilderPattern
public class BuilderPattern {
public static void main(String[] args) {
TextBuilder textBuilder = new TextBuilder();
Director director = new Director(textBuilder);
director.constructor();
System.out.println(textBuilder.getResult());
}
}
使用该设计模式的好处
当要组装具有复杂结构的实例时,可以使用该设计模式。组装的具体过程会隐藏在Director角色中,创建无需关心该实例是如何组装,只需调用Director中的方法,即可完成实例的组装。
抽象工厂模式(Abstract Factory)
场景
在Abstract Factory模式中将会出现抽象工厂,它会将抽象零件组装为抽象产品。也就是说,我们并不关心零件的具体实现,而是只关心接口(API )。我们仅使用该接口(API)将零件组装成为产品。
本次例子是:是将带有层次关系的链接的集合制作成HTML文件。
UML图
类和接口一览表
该模式下登场的角色
-
AbstractProduct(抽象产品)
AbstractProduct角色负责定义AbstractFactory 角色所生成的抽象零件和产品的接口(API)。在示例程序中,由 Link类、Tray类和 Page类扮演此角色。
-
AbstractFactory(抽象工厂)
AbstractFactory角色负责定义用于生成抽象产品的接口(API )。在示例程序中,由Factory类扮演此角色。
-
Client(委托者)
Client角色仅会调用AbstractFactory角色和AbstractProduct角色的接口(API)来进行工作,对于具体的零件、产品和工厂一无所知。在示例程序中,由Main类扮演此角色。
-
ConcreteProduct(具体产品)
ConcreteProduct角色负责实现AbstractProduct角色的接口(API )。在示例程序中,由以下包中的以下类扮演此角色。
- listfactory包:ListLink类、ListTray类和ListPage类
-
ConcreteFactory(具体工厂)
ConcreteFactory 角色负责实现AbstractFactory角色的接口(API )。在示例程序中,由以下包中的以下类扮演此角色。
- listfactory 包:Listfactory类
代码实现
- factory.Tray
public abstract class Tray extends Item {
protected ArrayList tray = new ArrayList();
public Tray(String caption) {
super(caption);
}
public void add(Item item) {
tray.add(item);
}
}
- factory.Page
public abstract class Page {
protected String title;
protected String author;
protected ArrayList 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();
}
- factory.Link
public abstract class Link extends Item {
protected String url;
public Link(String caption, String url) {
super(caption);
this.url = url;
}
}
- factory.Item
public abstract class Item {
protected String caption;
public Item(String caption) {
this.caption = caption;
}
public abstract String makeHTML();
}
- factory.Factory
public abstract class Factory {
public static Factory getFactory(String classname) {
Factory factory = null;
try {
factory = (Factory)Class.forName(classname).newInstance();
} catch (ClassNotFoundException e) {
System.err.println("没有找到 " + classname + "类。");
} catch (Exception e) {
e.printStackTrace();
}
return factory;
}
public abstract Link createLink(String caption, String url);
public abstract Tray createTray(String caption);
public abstract Page createPage(String title, String author);
}
- listfactory.ListTray
public class ListTray extends Tray {
public ListTray(String caption) {
super(caption);
}
public String makeHTML() {
StringBuffer buffer = new StringBuffer();
buffer.append("<li>\n");
buffer.append(caption + "\n");
buffer.append("<ul>\n");
Iterator 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();
}
}
- listfactory.ListPage
public class ListPage extends Page {
public ListPage(String title, String author) {
super(title, author);
}
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 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();
}
}
- listfactory.ListLink
public class ListLink extends Link {
public ListLink(String caption, String url) {
super(caption, url);
}
public String makeHTML() {
return " <li><a href=\"" + url + "\">" + caption + "</a></li>\n";
}
}
- listsfactory.ListFactory
public class ListFactory extends Factory {
public Link createLink(String caption, String url) {
return new ListLink(caption, url);
}
public Tray createTray(String caption) {
return new ListTray(caption);
}
public Page createPage(String title, String author) {
return new ListPage(title, author);
}
}
- Main
public class Main {
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", "yzy");
page.add(traynews);
page.add(traysearch);
page.output();
}
}
使用该设计模式的好处
在Abstract Factory模式中增加具体的工厂是非常容易的。这里说的“容易”指的是需要编写哪些类和需要实现哪些方法都非常清楚。
假设现在我们要在示例程序中增加新的具体工厂,那么需要做的就是编写Factory、Link,Tray、Page这4个类的子类,并实现它们定义的抽象方法。也就是说将factory包中的抽象部分全部具体化即可。
这样一来,无论要增加多少个具体工厂(或是要修改具体工厂的Bug ),都无需修改抽象工和Main部分。
桥接模式(Bridge)
场景
Bridge的意思是“桥梁”。就像在现实世界中,桥梁的功能是将河流的两侧连接起来一样,Bridge模式的作用也是将两样东西连接起来,它们分别是类的功能层次结构和类的实现层次结构
类的功能层次结构:假设现在有一个类something。当我们想在Something中增加新功能时(想增加一个具体方法时),会编写一个 Something类的子类(派生类),即 SomethingGood类。这样就构成了类的功能层次结构。
类的实现层次结构:抽象类声明了一些方法,定义了接口(API ),然后子类负责去实现这些抽象方法。父类的任务是通过声明抽象方法的方式定义接口(API),而子类的任务是实现抽象方法。正是由于父类和子类的这种任务分担,我们才可以编写出具有高可替换性的类。这样就构成了类的实现层次结构
UML图
类和接口一览表
该模式下登场的角色
-
Abstraction(抽象化)
该角色位于“类的功能层次结构”的最上层。它使用Implementor角色的方法定义了基本的功能。该角色中保存了Implementor 角色的实例。在示例程序中,由Display类扮演此角色。
-
RefinedAbstraction(改善后的抽象化)
在Abstraction角色的基础上增加了新功能的角色。在示例程序中,由CountDisplay类扮演此角色。 -
lmplementor(实现者)
该角色位于“类的实现层次结构”的最上层。它定义了用于实现Abstraction角色的接口(API )的方法。在示例程序中,由 DisplayImpl 类扮演此角色。 -
Concretelmplementor(具体实现者)
该角色负责实现在Implementor 角色中定义的接口(API)。在示例程序中,由stringDisplayImpl类扮演此角色。
Bridge模式的类图如图9-3所示。左侧的两个类构成了“类的功能层次结构”,右侧两个类构成了“类的实现层次结构”。类的两个层次结构之间的桥梁是impl字段。
代码实现
- Display
public class Display {
private DisplayImpl impl;
public Display(DisplayImpl impl){
this.impl=impl;
}
public void open() {
impl.rawOpen();
}
public void print() {
impl.rawPrint();
}
public void close() {
impl.rawClose();
}
public void display(){
open();
print();
close();
}
}
- countDisplay
public class CountDisplay extends Display {
public CountDisplay(DisplayImpl impl) {
super(impl);
}
public void countDisplay(int times) {
open();
for (int i = 0; i < times; i++) {
print();
}
close();
}
}
- DisplayImpl
public abstract class DisplayImpl {
public abstract void rawOpen();
public abstract void rawPrint();
public abstract void rawClose();
}
- StringDisplayImpl
public class StringDisplayImpl extends DisplayImpl {
private String string; // 要显示的字符串
private int width; // 以字节单位计算出的字符串的宽度
public StringDisplayImpl(String string) { // 构造函数接收要显示的字符串string
this.string = string; // 将它保存在字段中
this.width = string.getBytes().length; // 把字符串的宽度也保存在字段中,以供使用。
}
public void rawOpen() {
printLine();
}
public void rawPrint() {
System.out.println("|" + string + "|"); // 前后加上"|"并显示
}
public void rawClose() {
printLine();
}
private void printLine() {
System.out.print("+"); // 显示用来表示方框的角的"+"
for (int i = 0; i < width; i++) { // 显示width个"-"
System.out.print("-"); // 将其用作方框的边框
}
System.out.println("+"); // 显示用来表示方框的角的"+"
}
}
- BridgePattern
public class BridgePattern {
public static void main(String[] args) {
Display display = new Display(new StringDisplayImpl("一次输出"));
CountDisplay countDisplay = new CountDisplay(new StringDisplayImpl("多次输出"));
display.display();
countDisplay.countDisplay(3);
}
}
使用该模式的好处
Bridge模式的特征是将“类的功能层次结构”与“类的实现层次结构”分离开了。将类的这两个层次结构分离开有利于独立地对它们进行扩展。
当想要增加功能时,只需要在“类的功能层次结构”一侧增加类即可,不必对“类的实现层次结构”做任何修改。而且,增加后的功能可以被“所有的实现”使用
策略模式(Strategy)
场景
我们考虑了两种猜拳的策略。第一种策略是“如果这局猜拳获胜,那么下一局也出一样的势”(winningstrategy),这是一种稍微有些笨的策略;另外一种策略是“根据上一局的手势概率上计算出下一局的手势”( Probstrategy)。
UML图
类和接口一览表
该模式下登场的角色
-
Strategy(策略)
Strategy角色负责决定实现策略所必需的接口(API)。在示例程序中,由strategy接口扮演此角色。 -
ConcreteStrategy(具体的策略)
ConcreteStrategy角色负责实现Strategy 角色的接口(API ),即负责实现具体的策略(战略、方向、方法和算法)。在示例程序中,由winningstrategy类和Probstrategy类扮演此角色 -
Context(上下文)
负责使用Strategy角色。Context角色保存了ConcreteStrategy角色的实例,并使用ConcreteStrategy角色去实现需求(总之,还是要调用Strategy角色的接口(API))。在示例程序中,由Player类扮演此角色。
代码实现
- Hand
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 = { // 表示猜拳中3种手势的实例
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;
}
}
public String toString() { // 转换为手势值所对应的字符串
return name[handvalue];
}
}
- Strategy
public interface Strategy {
public abstract Hand nextHand();
public abstract void study(boolean win);
}
- ProbStrategy
public class ProbStrategy implements Strategy {
private Random random;
private int prevHandValue = 0;
private int currentHandValue = 0;
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);
}
private int getSum(int hv) {
int sum = 0;
for (int i = 0; i < 3; i++) {
sum += history[hv][i];
}
return sum;
}
public void study(boolean win) {
if (win) {
history[prevHandValue][currentHandValue]++;
} else {
history[prevHandValue][(currentHandValue + 1) % 3]++;
history[prevHandValue][(currentHandValue + 2) % 3]++;
}
}
}
- WinningStrategy
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;
}
}
- Player
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++;
}
public String toString() {
return "[" + name + ":" + gamecount + " games, " + wincount + " win, " + losecount + " lose" + "]";
}
}
- StrategyPattern
public class Main {
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());
}
}
使用该模式的好处
当我们想要通过改善算法来提高算法的处理速度时,如果使用了Strategy模式,就不必修改Strategy角色的接口(API)了,仅仅修改ConcreteStrategy角色即可。而且,使用委托这种弱关联关系可以很方便地整体替换算法。例如,如果想比较原来的算法与改进后的算法的处理速度有多大区别,简单地替换下算法即可进行测试。
组合模式(Composite)
场景
在计算机的文件系统中,有“文件夹”的概念(在有些操作系统中,也称为“目录”)。文件夹里面既可以放入文件,也可以放入其他文件夹(子文件夹)。在子文件夹中,一样地既可以放入文件,也可以放入子文件夹。可以说,文件夹是形成了一种容器结构、递归结构。(Composite)
UML图
类和接口一览图
该模式下登场的角色
-
Leaf(树叶)
表示“内容”的角色。在该角色中不能放入其他对象。在示例程序中,由File类扮演此角色。 -
Composite(复合物)
表示容器的角色。可以在其中放入Leaf角色和Composite角色。在示例程序中,由Directory类扮演此角色。 -
Component
使Leaf角色和Composite 角色具有一致性的角色。Composite角色是Leaf角色和 Composite角色的父类。在示例程序中,由Entry类扮演此角色。 -
Client
使用Composite模式的角色。在示例程序中,由 Main类扮演此角色。
代码实现
- Entry
public abstract class Entry {
public abstract String getName();
public abstract int getSize();
public String toString() {
return getName() + ":" + getSize();
}
public abstract void printList(String prefix);
public void printList() {
printList("");
}
public void add(Entry entry) throws FileTreatementException {
throw new FileTreatementException();
}
}
- File
public class File extends Entry {
private String name;
private int size;
public File(String name, int size) {
this.name = name;
this.size = size;
}
@Override
public String getName() {
return this.name;
}
@Override
public int getSize() {
return this.size;
}
@Override
public void printList(String prefix) {
System.out.println(prefix + "/" + this);
}
}
- FileTreatementException
public class FileTreatementException extends RuntimeException {
public FileTreatementException() {
}
public FileTreatementException(String msg) {
super(msg);
}
}
- Directory
public class Directory extends Entry {
private String name;
private List<Entry> entryList = new ArrayList<>();
public Directory(String name) {
this.name = name;
}
@Override
public String getName() {
return this.name;
}
public void add(Entry entry) {
entryList.add(entry);
}
@Override
public int getSize() {
int size = 0;
for (Entry entry : entryList) {
size += entry.getSize();
}
return size;
}
@Override
public void printList(String prefix) {
System.out.println(prefix + "/" + this);
for (Entry entry : entryList) {
entry.printList(prefix + "/" + name);
}
}
}
- Composite
public class CompositePattern {
public static void main(String[] args) {
Directory root = new Directory("root");
Directory bin = new Directory("bin");
Directory etc = new Directory("etc");
Directory var = new Directory("var");
root.add(bin);
root.add(etc);
root.add(var);
etc.add(new File("nginx.conf",100));
bin.add(new File("a.java",108));
root.printList();
}
}
使用该模式的好处
使用Composite模式可以使容器与内容具有一致性,也可以称其为多个和单个的一致性,即将多个对象结合在一起,当作一个对象进行处理。
装饰器模式(Decorator)
场景
本章中的示例程序的功能是给文字添加装饰边框。这里所谓的装饰边框是指用“-“ ”+” “I”等字符组成的边框。
UML图
类和接口一览图
该模式下登场的角色
- Component
增加功能时的核心角色。以本章开头的例子来说,装饰前的蛋糕就是Component角色。Component角色只是定义了蛋糕的接口( API)。在示例程序中,由Display类扮演此角色。 - ConcreteComponent
该角色是实现了Component角色所定义的接口(API)的具体蛋糕。在示例程序中,由StringDisplay类扮演此角色。 - Decorator (装饰物)
该角色具有与Component角色相同的接口(API)。在它内部保存了被装饰对象一Component角色。Decorator 角色知道自己要装饰的对象。在示例程序中,由Border类扮演此角色。 - ConcreteDecorator (具体的装饰物)
该角色是具体的Decorator角色。在示例程序中,由SideBorder类和FullBorder类扮演此角色。
代码实现
- Border
public abstract class Border extends Display {
protected Display display;
public Border(Display display) {
this.display = display;
}
}
- SildeBorder
public class SideBorder extends Border {
private char charCode;
public SideBorder(Display display, char charCode) {
super(display);
this.charCode = charCode;
}
@Override
public int getColumns() {
return 1 + this.display.getColumns() + 1;
}
@Override
public int getRows() {
return this.display.getRows();
}
@Override
public String getRowText(int row) {
return charCode + this.display.getRowText(row) + charCode;
}
}
- FullBorder
public class FullBorder extends Border {
public FullBorder(Display display) {
super(display);
}
@Override
public int getColumns() {
return 1 + this.display.getColumns() + 1;
}
@Override
public int getRows() {
return 1 + this.display.getRows() + 1;
}
@Override
public String getRowText(int row) {
if (row == 0) {
return "+" + makeLine('-', this.display.getColumns()) + "+";
} else if (row == this.display.getRows() + 1) {
return "+" + makeLine('-', this.display.getColumns()) + "+";
} else {
return "|" + this.display.getRowText(row - 1) + "|";
}
}
private String makeLine(char ch, int count) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < count; i++) {
sb.append(ch);
}
return sb.toString();
}
}
- Display
public abstract class Display {
public abstract int getColumns();
public abstract int getRows();
public abstract String getRowText(int row);
public final void show() {
int rowNum = this.getRows();
for (int i = 0; i < rowNum; i++) {
System.out.println(getRowText(i));
}
}
}
- StringDisplay
public class StringDisplay extends Display {
private String content;
public StringDisplay(String content) {
this.content = content;
}
@Override
public int getColumns() {
return this.content.length();
}
@Override
public int getRows() {
return 1;
}
@Override
public String getRowText(int row) {
return this.content;
}
}
- DecoratorPattern
public class DecoratorPattern {
public static void main(String[] args) {
Display display = new SideBorder(
new FullBorder(
new FullBorder(
new FullBorder(
new SideBorder(
new StringDisplay("hello world"),
'*')
))
), '/'
);
display.show();
}
}
使用该模式的好处
- 在不改变被装饰物的前提下增加功能
在Decorator模式中,装饰边框与被装饰物具有相同的接口(API)。虽然接口( API)是相同的,但是越装饰,功能则越多。例如,用SideBorder装饰Display后,就可以在字符串的左右两侧加上装饰字符。如果再用FullBorder装饰,那么就可以在字符串的四周加上边框。此时,我们完全不需要对被装饰的类做任何修改。这样,我们就实现了不修改被装饰的类即可增加功能。
Decorator模式使用了委托。对“装饰边框”提出的要求(调用装饰边框的方法)会被转交(委托)给“被装饰物”去处理。以示例程序来说,就是SideBor der类的getColumns方法调用了display. getColumns()。除此以外,getRows方法也调用了display .getRows ()。
- 只需要一些装饰物即可添加许多功能
使用Decorator模式可以为程序添加许多功能。只要准备- - 些装饰边框( ConcreteDecorator角色),即使这些装饰边框都只具有非常简单的功能,也可以将它们自由组合成为新的对象。
观察者模式(Visitor)
场景
在Visitor模式中,数据结构与处理被分离开来。我们编写一个表示“访问者”的类来访问数据结构中的元素,并把对各元素的处理交给访问者类。这样,当需要增加新的处理时,我们只需要编写新的访问者,然后让数据结构可以接受访问者的访问即可。
使用Composite模式中用到的那个文件和文件夹的例子作为访问者要访问的数据结构。访问者会访问由文件和文件夹构成的数据结构,然后显示出文件和文件夹的一览。
UML图
类和接口一览图
该模式下登场的角色
- Visitor(访问者)
Visitor角色负责对数据结构中每个具体的元素(ConcreteElement角色)声明一个用于访问XXXXx的visit(XXXXX)方法。visit(XXXXX)是用于处理XXXXx的方法,负责实现该方法的是ConcreteVisitor 角色。在示例程序中,由visitor类扮演此角色。 - ConcreteVisitor(具体的访问者)
ConcreteVisitor角色负责实现 Visitor角色所定义的接口(API)。它要实现所有的visit (XXXXX)方法,即实现如何处理每个ConcreteElement角色。在示例程序中,由Listvisitor类扮演此角色。如同在ListVisitor中,currentdir字段的值不断发生变化一样,随着visit ( XXXXxX)处理的进行,Concrete Visitor角色的内部状态也会不断地发生变化。 - Element(元素)
Element角色表示Visitor角色的访问对象。它声明了接受访问者的accept方法。accept方法接收到的参数是 Visitor角色。在示例程序中,由Element 接口扮演此角色。 - ConcreteElement
ConcreteElement角色负责实现 Element角色所定义的接口(API )。在示例程序中,由File类和 Directory类扮演此角色。 - ObjectStructure (对象结构)
ObjectStructur角色负责处理 Element角色的集合。ConcreteVisitor角色为每个Element角色都准备了处理方法。在示例程序中,由Directory类扮演此角色(一人分饰两角)。为了让ConcreteVisitor角色可以遍历处理每个Element角色,在示例程序中,我们在Directory类中实现了iterator方法。
代码实现
- FileTreatementException
public class FileTreatementException extends RuntimeException {
public FileTreatementException() {
}
public FileTreatementException(String msg) {
super(msg);
}
}
- Element
public interface Element {
void accept(Visitor visitor);
}
- Entry
public abstract class Entry implements Element{
public abstract String getName();
public abstract int getSize();
public String toString() {
return getName() + ":" + getSize();
}
public void add(Entry entry) throws FileTreatementException {
throw new FileTreatementException();
}
}
- File
public class File extends Entry {
private String name;
private int size;
public File(String name, int size) {
this.name = name;
this.size = size;
}
@Override
public String getName() {
return this.name;
}
@Override
public int getSize() {
return this.size;
}
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
}
- Directory
public class Directory extends Entry {
private String name;
private List<Entry> entryList = new ArrayList<>();
public Directory(String name) {
this.name = name;
}
@Override
public String getName() {
return this.name;
}
public void add(Entry entry) {
entryList.add(entry);
}
@Override
public int getSize() {
int size = 0;
for (Entry entry : entryList) {
size += entry.getSize();
}
return size;
}
public Iterator<Entry> iterator() {
return entryList.iterator();
}
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
}
- Visitor
public interface Visitor {
void visit(Directory directory);
void visit(File file);
}
- ListVisitor
public class ListVisitor implements Visitor {
private String currentPath = "";
@Override
public void visit(Directory directory) {
System.out.println(currentPath + "/" + directory.getName());
String savePath = currentPath;
currentPath = currentPath + "/" + directory.getName();
Iterator<Entry> entryIterator = directory.iterator();
while (entryIterator.hasNext()) {
Entry entry = entryIterator.next();
entry.accept(this);
}
currentPath = savePath;
}
@Override
public void visit(File file) {
System.out.println(currentPath + "/" + file.getName());
}
}
- VisitorPattern
public class VisitorPattern {
public static void main(String[] args) {
Directory root = new Directory("root");
Directory etc = new Directory("etc");
Directory var = new Directory("var");
Directory usr = new Directory("usr");
root.add(etc);
root.add(var);
root.add(usr);
usr.add(new File("a.java", 888));
var.add(new File("b.java", 999));
root.accept(new ListVisitor());
}
}
使用该模式的好处
Visitor模式的目的是将处理从数据结构中分离出来。Visitor模式提高了File类和Directo类作为组件的独立性。如果将进行处理的方法定义在File类和 Directory类中,当每次要扩展功能,增加新的“处理”时,就不得不去修改File类和Directory类。
责任链模式(Chain of Responsibility)
场景
使用Chain of Responsibility模式可以弱化“请求方”和“处理方”之间的关联关系,让双方各自都成为可独立复用的组件。此外,程序还可以应对其他需求,如根据情况不同,负责处理的对象也会发生变化的这种需求。
当一个人被要求做什么事情时,如果他可以做就自己做,如果不能做就将“要求”转给另外一个人。下一个人如果可以自己处理,就自己做;如果也不能自己处理,就再转给另外一个人……这就是Chain of Responsibility模式。
UML图
类和接口一览图
该模式下登场的角色
- Handler(处理者)
Handler 角色定义了处理请求的接口(API)。Handler 角色知道“下一个处理者”是谁,如果自己无法处理请求,它会将请求转给“下一个处理者”。当然,“下一个处理者”也是Handler 角色。在示例程序中,由 Support类扮演此角色。负责处理请求的是support方法。 - ConcreteHandler(具体的处理者)
ConcreteVisitor角色是处理请求的具体角色。在示例程序中,由NoSupport、LimitSupportOddsupport. Specialsupport等各个类扮演此角色。 - Client(请求者)
Client角色是向第一个ConcreteHandler角色发送请求的角色。在示例程序中,由Main类扮演此角色。
代码实现
- Support
public abstract class Support {
protected String name;
protected Support next;
public Support(String name) {
this.name = name;
}
public Support setNext(Support support) {
this.next = support;
return next;
}
public abstract boolean resolve(Trouble trouble);
public void support(Trouble trouble) {
if (this.resolve(trouble)) {
done(trouble);
} else if (next != null) {
next.support(trouble);
} else {
fail(trouble);
}
}
private void done(Trouble trouble) {
System.out.println(trouble + "is resolved by " + this + ".");
}
private void fail(Trouble trouble) {
System.out.println(trouble + "can't resolve");
}
@Override
public String toString() {
return "name='" + name;
}
}
- NoSupport
public class NoSupport extends Support {
public NoSupport(String name) {
super(name);
}
@Override
public boolean resolve(Trouble trouble) {
return false;
}
}
- LimitSupport
public class LimitSupport extends Support {
private int limitNum;
public LimitSupport(String name, int limitNum) {
super(name);
this.limitNum = limitNum;
}
@Override
public boolean resolve(Trouble trouble) {
return limitNum >= trouble.getNum();
}
}
- OddSupport
public class OddSupport extends Support {
public OddSupport(String name) {
super(name);
}
@Override
public boolean resolve(Trouble trouble) {
return trouble.getNum() % 2 != 0;
}
}
- SpecialSupport
public class SpecialSupport extends Support {
private int specialNum;
public SpecialSupport(String name, int specialNum) {
super(name);
this.specialNum = specialNum;
}
@Override
public boolean resolve(Trouble trouble) {
return trouble.getNum() == specialNum;
}
}
- Trouble
public class Trouble {
private int num;
public Trouble(int num) {
this.num = num;
}
public int getNum(){
return this.num;
}
@Override
public String toString() {
return "Trouble{" +
"num=" + num +
'}';
}
}
- ChainOfResponsibilityPattern
public class ChainOfResponsibilityPattern {
public static void main(String[] args) {
NoSupport noSupport = new NoSupport("Bod");
LimitSupport limitSupport = new LimitSupport("yzy", 66);
SpecialSupport specialSupport = new SpecialSupport("shouyaya", 88);
noSupport.setNext(limitSupport).setNext(specialSupport);
for (int i = 0; i < 100; i++) {
noSupport.support(new Trouble(i));
}
}
}
使用该模式的好处
Chain of Responsibility模式的最大优点就在于它弱化了发出请求的人(Client角色)和处理请求的人(ConcreteHandler 角色)之间的关系。Client角色向第一个 ConcreteHandler 角色发出请求,然后请求会在职责链中传播,直到某个ConcreteHandler角色处理该请求。
如果不使用该模式,就必须有某个伟大的角色知道“谁应该处理什么请求”,这有点类似中央集权制。而让“发出请求的人”知道“谁应该处理该请求”并不明智,因为如果发出请求的人不得不知道处理请求的人各自的责任分担情况,就会降低其作为可复用的组件的独立性。
简单窗口模式(Facade)
场景
一个用于从邮件地址中获取用户名字的数据库类( Database ),一个用于编写HTML文件的类(HtmlWriter),以及一个扮演Facade角色并提供高层接口(API)的类(PageMaker )。
UML图
类和接口一览图
该模式下登场的角色
-
Facade(窗口)
Facade角色是代表构成系统的许多其他角色的“简单窗口”。Facade角色向系统外部提供高层接口(API )。在示例程序中、由 PageMaker类扮演此角色。 -
构成系统的许多其他角色
这些角色各自完成自己的工作,它们并不知道Facade角色。Facade角色调用其他角色进行工作,但是其他角色不会调用Facade角色。在示例程序中,由 Database类和 Htmlwriter类扮演此角色。 -
Client(请求者)
Client角色负责调用Facade角色(在GoF书(请参见附录E[GoF])中,Client角色并不包含在Facade模式中)。在示例程序中,由 Main类扮演此角色。
代码实现
- pagemake.Database
public class Database {
private Database() {
}
protected static Properties getProperties(String dbName) {
String fileName = dbName + ".txt";
Properties properties = new Properties();
try {
properties.load(new FileInputStream(fileName));
} catch (IOException e) {
System.out.println("can't find file");
}
return properties;
}
}
- pagemake.HtmlWriter
public class HtmlWriter {
private Writer writer;
private HtmlWriter() {
}
public HtmlWriter(Writer writer) { // 构造函数
this.writer = writer;
}
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");
}
public void paragraph(String msg) throws IOException { // 输出段落
writer.write("<p>" + msg + "</p>\n");
}
public void link(String href, String caption) throws IOException { // 输出超链接
paragraph("<a href=\"" + href + "\">" + caption + "</a>");
}
public void mailto(String mailaddr, String username) throws IOException { // 输出邮件地址
link("mailto:" + mailaddr, username);
}
public void close() throws IOException { // 结束输出HTML
writer.write("</body>");
writer.write("</html>\n");
writer.close();
}
}
- pagemake.Pagemaker
public class PageMaker {
private PageMaker() { // 防止外部new出PageMaker的实例,所以声明为private方法
}
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 + "的主页。");
writer.paragraph("等着你的邮件哦!");
writer.mailto(mailaddr, username);
writer.close();
System.out.println(filename + " is created for " + mailaddr + " (" + username + ")");
} catch (IOException e) {
e.printStackTrace();
}
}
}
- FacadePattern
public class FacadePattern {
public static void main(String[] args) {
PageMaker.makeWelcomePage("hanako@hyuki.com", "welcome.html");
}
}
使用该模式的好处
Facade模式可以让复杂的东西看起来简单。那么,这里说到的“复杂的东西”到底是什么呢?其实就是在后台工作的这些类之间的关系和它们的使用方法。使用Facade模式可以让我们不必在意这些复杂的东西。
这里的重点是接口(API)变少了。程序中如果有很多类和方法,我们在决定到底应该使用哪个类或是方法时就很容易迷茫。有时,类和方法的调用顺序也很容易弄错,必须格外注意。因此如果有一个能够使接口(API)变少的 Facade角色是一件多么美好的事情啊。
在设计类时,我们还需要考虑将哪些方法的可见性设为public。如果公开的方法过多,会致类的内部的修改变得困难。字段也是一样的,如果不小心将某个字段公开出去了,那么其他类可能会读取或是修改这个字段,导致难以修改该类。
中介者模式(Mediator)
场景
请大家想象一下一个乱糟糟的开发小组的工作状态。小组中的10个成员虽然一起协同工作,但是意见难以统一,总是互相指挥,导致工作始进度始终滞后。不仅如此,他们还都十分在意编码细节,经常为此争执不下。这时,我们就需要-一个中立的仲裁者站出来说:“各位,请大家将情况报告给我,我来负责仲裁。我会从团队整体出发进行考虑,然后下达指示。但我不会评价大家的工作细节。”这样,当出现争执时大家就会找仲裁者进行商量,仲裁者会负责统一 大家的意见。
UML图
类和接口一览图
该模式下登场的角色
- Mediator(仲裁者、中介者)
Mediator角色负责定义与Colleague角色进行通信和做出决定的接口(API)。在示例程序中由Mediator接口扮演此角色。 - ConcreteMediator(具体的仲裁者、中介者)
ConcreteMediator 角色负责实现 Mediator角色的接口(API),负责实际做出决定。在示例程序中、由LoginFrame类扮演此角色。 - Colleague (同事)
Colleague角色负责定义与Mediator角色进行通信的接口(API)。在示例程序中,由colleague接口扮演此角色。 - ConcreteColleague (具体的同事)
ConcreteColleague角色负责实现Colleague角色的接口(API )。在示例程序中,由colleagueButton类、colleagueTextField类和colleagueCheckbox类扮演此角色
代码实现
- Mediator
public interface Mediator {
public abstract void createColleagues();
public abstract void colleagueChanged();
}
- Colleague
public interface Colleague {
public abstract void setMediator(Mediator mediator);
public abstract void setColleagueEnabled(boolean enabled);
}
- ColleagueButton
public class ColleagueButton extends Button implements Colleague {
private Mediator mediator;
public ColleagueButton(String caption) {
super(caption);
}
public void setMediator(Mediator mediator) { // 保存Mediator
this.mediator = mediator;
}
public void setColleagueEnabled(boolean enabled) { // Mediator下达启用/禁用的指示
setEnabled(enabled);
}
}
- ColleagueCheckbox
public class ColleagueCheckbox extends Checkbox implements ItemListener, Colleague {
private Mediator mediator;
public ColleagueCheckbox(String caption, CheckboxGroup group, boolean state) { // 构造函数
super(caption, group, state);
}
public void setMediator(Mediator mediator) { // 保存Mediator
this.mediator = mediator;
}
public void setColleagueEnabled(boolean enabled) { // Mediator下达启用/禁用指示
setEnabled(enabled);
}
public void itemStateChanged(ItemEvent e) { // 当状态发生变化时通知Mediator
mediator.colleagueChanged();
}
}
- ColleagueTextField
public class ColleagueTextField extends TextField implements TextListener, Colleague {
private Mediator mediator;
public ColleagueTextField(String text, int columns) { // 构造函数
super(text, columns);
}
public void setMediator(Mediator mediator) { // 保存Mediator
this.mediator = mediator;
}
public void setColleagueEnabled(boolean enabled) { // Mediator下达启用/禁用的指示
setEnabled(enabled);
setBackground(enabled ? Color.white : Color.lightGray);
}
public void textValueChanged(TextEvent e) { // 当文字发生变化时通知Mediator
mediator.colleagueChanged();
}
}
- LoginFrame
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后,显示对话框。
public LoginFrame(String title) {
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() {
if (checkGuest.getState()) { // Guest mode
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);
}
}
- MediatorPattern
public class MediatorPattern {
public static void main(String[] args) {
new LoginFrame("Mediator Sample");
}
}
使用该模式的好处
假设现在有A和B这2个实例,它们之间互相通信(相互之间调用方法),那么通信线路有两条,即A→B和A一B。如果是有A、B和C这3个实例,那么就会有6条通信线路,即A→B、A→B、B→C、B→C、C→A和C→A。如果有4个实例,会有12条通信线路; 5个实例就会有20条通信线路,而6个实例则会有30条通信线路。如果存在很多这样的互相通信的实例,那么程序结构会变得非常复杂。
可能会有读者认为,如果实例很少就不需要Mediator模式了。但是需要考虑到的是,即使最初实例很少,很可能随着需求变更实例数量会慢慢变多,迟早会暴露出问题。
观察者模式(ObServer)
场景
在Observer模式中,当观察对象的状态发生变化时,会通知给观察者。Observer模式适用于根据对象状态进行相应处理的场景。这是一段简单的示例程序,观察者将观察一个会生成数值的对象,并将它生成的数值结果显示出来。不过,不同的观察者的显示方式不一样。DigitObserver会以数字形式显示数值,而GraphObserver则会以简单的图示形式来显示数值。
UML图
类和接口一览表
该模式下登场的角色
- Subject(观察对象)
Subject角色表示观察对象。Subject角色定义了注册观察者和删除观察者的方法。此外,它还声明了“获取现在的状态”的方法。在示例程序中,由 NumberGenerator类扮演此角色。 - ConcreteSubject(具体的观察对象)
ConcreteSubject角色表示具体的被观察对象。当自身状态发生变化后,它会通知所有已经注册的Observer角色。在示例程序中,由RandonNumberGenerator类扮演此角色。 - Observer(观察者)
Observer角色负责接收来自Subject角色的状态变化的通知。为此,它声明了update方法。在示例程序中,由observer接口扮演此角色。 - ConcreteObserver(具体的观察者)
ConcreteObserver角色表示具体的Observer。当它的update方法被调用后,会去获取要观察的对象的最新状态。在示例程序中,由Digit0bserver类和Graph0bserver类扮演此角色。
代码实现
- Observer
public interface Observer {
void update(NumberGenerator numberGenerator);
}
- NumberGenerator
public abstract class NumberGenerator {
private List<Observer> observerList = new ArrayList<>();
public void addObServer(Observer observer) {
observerList.add(observer);
}
public void removeObserver(Observer observer) {
observerList.remove(observer);
}
public void notifyObservers() {
for (Observer observer : observerList) {
observer.update(this);
}
}
public abstract int getNumber();
public abstract void exec();
}
- RandomNumberGenerator
public class RandomNumberGenerator extends NumberGenerator {
private int number;
private Random random = new Random();
@Override
public int getNumber() {
return number;
}
@Override
public void exec() {
for (int i = 0; i < 20; i++) {
number=random.nextInt(50);
notifyObservers();
}
}
}
- NumberObserver
public class NumberObserver implements Observer{
@Override
public void update(NumberGenerator numberGenerator) {
System.out.println(numberGenerator.getNumber());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
- GraphObserver
public class GraphObserver implements Observer{
@Override
public void update(NumberGenerator numberGenerator) {
int time = numberGenerator.getNumber();
for (int i = 0; i < time; i++) {
System.out.print("*");
}
System.out.println();
}
}
- ObserverPattern
public class ObserverPattern {
public static void main(String[] args) {
RandomNumberGenerator randomNumberGenerator = new RandomNumberGenerator();
randomNumberGenerator.addObServer(new NumberObserver());
randomNumberGenerator.addObServer(new GraphObserver());
randomNumberGenerator.exec();
}
}
使用该模式的好处
Observer模式适用于根据对象状态进行相应处理的场景。
Observer本来的意思是“观察者”,但实际上Observer角色并非主动地去观察,而是被动地受来自Subject角色的通知。因此,Observer模式也被称为Publish-Subscribe(发布-订阅)模式,Publish(发布)和 Subscribe(订阅)这个名字可能更加合适。
备忘录模式(Memento)
场景
在程序中,如果金钱增加,为了方便将来恢复状态,我们会生成Memento类的实例,将现在的状态保存起来。所保存的数据为当前持有的金钱和水果。如果不断掷出了会导致金钱减少的点数,为了防止金钱变为0而结束游戏,我们会使用Memento的实例将游戏恢复至之前的状态。
UML图
类和接口一览表
该模式下登场的角色
- Originator(生成者)
Originator角色会在保存自己的最新状态时生成Memento角色。当把以前保存的 Memento角色传递给Originator角色时,它会将自己恢复至生成该Memento角色时的状态。在示例程序中,由Gamer类扮演此角色。 - Memento(纪念品)
Memento角色会将Originator角色的内部信息整合在一起。在 Memento角色中虽然保存了Originator角色的信息,但它不会向外部公开这些信息。
Memento角色有以下两种接口(API )。
wide interface——宽接口(API)
Memento角色提供的“宽接口(API)”是指所有用于获取恢复对象状态信息的方法的集合。由于宽接口(API)会暴露所有Memento角色的内部信息,因此能够使用宽接口(API)的只有Originator角色。
narrowinterface——窄接口(API )
Memento角色为外部的Caretaker角色提供了“窄接口(API)”。可以通过窄接口(API )获取的Memento角色的内部信息非常有限,因此可以有效地防止信息泄露。
通过对外提供以上两种接口(API ),可以有效地防止对象的封装性被破坏。在示例程序中,由Memento类扮演此角色。
Originator 角色和 Memento角色之间有着非常紧密的联系。 - Caretaker(负责人)
当Caretaker角色想要保存当前的Originator 角色的状态时,会通知 Originator角色。Originator角色在接收到通知后会生成 Memento角色的实例并将其返回给Caretaker 角色。由于以后可能会Memento实例来将Originator恢复至原来的状态,因此Caretaker角色会一直保存 Memento实例。在示例程序中,由Main类扮演此角色。
不过,Caretaker角色只能使用Memento角色两种接口(API)中的窄接口(API),也就是说它无法访问 Memento角色内部的所有信息。它只是将Originator角色生成的Memento 角色当作一个黑盒子保存起来。
虽然Originator角色和 Memento角色之间是强关联关系,但Caretaker 角色和 Memento角色之间是弱关联关系。Memento角色对Caretaker角色隐藏了自身的内部信息。
代码实现
- game.Gamer
public class Gamer {
private int money;
private List<String> fruits = new ArrayList<>();
private String[] fruitNames = new String[]{"苹果", "草莓", "蓝莓"};
private Random random = new Random();
public Gamer(int money) {
this.money = money;
}
public void bet() {
int dice = random.nextInt(6) + 1;
if (dice == 1) {
money += 100;
System.out.println("增加了100金钱");
} else if (dice == 2) {
money /= 2;
System.out.println("金钱减半了");
} else if (dice == 3) {
fruits.add(getFruit());
System.out.println("增加了水果");
} else {
System.out.println("什么事都没有发生");
}
}
public Memento createMemento() {
Memento memento = new Memento(money);
for (String fruit : fruits)
if (fruit.startsWith("好吃的")) {
memento.addFruit(fruit);
}
return memento;
}
public void restoreMemento(Memento memento) {
this.money = memento.money;
this.fruits = memento.getFruits();
}
private String getFruit() {
String prefix = "";
if (random.nextBoolean()) {
prefix += "美味的";
}
return prefix + fruitNames[random.nextInt(fruitNames.length)];
}
public int getMoney() {
return money;
}
@Override
public String toString() {
return "Gamer{" +
"money=" + money +
", fruits=" + fruits +
'}';
}
}
- game.Memento
public class Memento {
int money;
ArrayList<String> fruits = new ArrayList<>();
Memento(int money) {
this.money = money;
}
public int getMoney() {
return money;
}
void addFruit(String fruit) {
fruits.add(fruit);
}
List<String> getFruits() {
return (List<String>) fruits.clone();
}
}
- MementoPattern
public class MementoPattern {
public static void main(String[] args) {
Gamer gamer = new Gamer(100);
Memento memento = gamer.createMemento();
for (int i = 0; i < 20; i++) {
System.out.println("=====" + i);
gamer.bet();
System.out.println(gamer);
if (gamer.getMoney() > memento.getMoney()) {
System.out.println("金钱增多了,所以存档");
memento = gamer.createMemento();
} else if (gamer.getMoney() < memento.getMoney()) {
System.out.println("金钱减少了,所以读档");
gamer.restoreMemento(memento);
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
使用该模式的好处
如果是要实现撤销功能,直接在Originator角色中实现不就好了吗?为什么要这么麻烦地引入 Memento模式呢?
Caretaker 角色的职责是决定何时拍摄快照,何时撤销以及保存Memento角色。
另一方面,Originator角色的职责则是生成Memento角色和使用接收到的 Memento角色来恢复自己的状态。
以上就是Caretaker角色与Originator角色的职责分担。有了这样的职责分担,当我们需要对应以下需求变更时,就可以完全不用修改Originator角色。
状态模式(State)
场景
在State模式中,我们用类来表示状态。State的意思就是“状态”。在现实世界中,我们会考虑各种东西的“状态”,但是几乎不会将状态当作“东西”看待。以类来表示状态后,我们就能通过切换类来方便地改变对象的状态。当需要增加新的状态时,如何修改代码这个问题也会很明确。
UML图
类和接口一览表
该模式下登场的角色
- State(状态)
State角色表示状态,定义了根据不同状态进行不同处理的接口(API)。该接口(API)是那些处理内容依赖于状态的方法的集合。在示例程序中,由state接口扮演此角色。 - ConcreteState(具体状态)
ConcreteState角色表示各个具体的状态,它实现了state接口。在示例程序中,由 Daystate类和NightState类扮演此角色。 - Context(状况、前后关系、上下文)
Context角色持有表示当前状态的ConcreteState角色。此外,它还定义了供外部调用者使用State模式的接口(API )。在示例程序中,由Context接口和SafeFrame类扮演此角色。
这里稍微做一下补充说明。在示例程序中,Context 角色的作用被context接口和SafeFrame类分担了。具体而言,context接口定义了供外部调用者使用State模式的接口(API),而 safeFrame类则持有表示当前状态的ConcreteState角色。
代码实现
- Context
public interface Context {
public abstract void setClock(int hour); // 设置时间
public abstract void changeState(State state); // 改变状态
public abstract void callSecurityCenter(String msg); // 联系警报中心
public abstract void recordLog(String msg); // 在警报中心留下记录
}
- State
public interface State {
public abstract void doClock(Context context, int hour); // 设置时间
public abstract void doUse(Context context); // 使用金库
public abstract void doAlarm(Context context); // 按下警铃
public abstract void doPhone(Context context); // 正常通话
}
- DayState
public class DayState implements State {
private static DayState singleton = new DayState();
private DayState() { // 构造函数的可见性是private
}
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("正常通话(白天)");
}
public String toString() { // 显示表示类的文字
return "[白天]";
}
}
- NightState
public class NightState implements State {
private static NightState singleton = new NightState();
private NightState() { // 构造函数的可见性是private
}
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("晚上的通话录音");
}
public String toString() { // 显示表示类的文字
return "[晚上]";
}
}
- SafeFrame
public class SafeFrame extends Frame implements ActionListener, Context {
private TextField textClock = new TextField(60); // 显示当前时间
private TextArea textScreen = new TextArea(10, 60); // 显示警报中心的记录
private Button buttonUse = new Button("使用金库"); // 金库使用按钮
private Button buttonAlarm = new Button("按下警铃"); // 按下警铃按钮
private Button buttonPhone = new Button("正常通话"); // 正常通话按钮
private Button buttonExit = new Button("结束"); // 结束按钮
private State state = DayState.getInstance(); // 当前的状态
// 构造函数
public SafeFrame(String title) {
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);
}
// 按钮被按下后该方法会被调用
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("?");
}
}
// 设置时间
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);
}
// 改变状态
public void changeState(State state) {
System.out.println("从" + this.state + "状態变为了" + state + "状态。");
this.state = state;
}
// 联系警报中心
public void callSecurityCenter(String msg) {
textScreen.append("call! " + msg + "\n");
}
// 在警报中心留下记录
public void recordLog(String msg) {
textScreen.append("record ... " + msg + "\n");
}
}
- StatePattern
public class StatePattern {
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) {
}
}
}
}
}
使用该模式的好处
在编程时,我们经常会使用分而治之的方针。它非常适用于大规模的复杂处理。当遇到庞大且复杂的问题,不能用一般的方法解决时,我们会先将该问题分解为多个小问题。如果还是不能解决这些小问题,我们会将它们继续划分为更小的问题,直至可以解决它们为止。分而治之,简单而言就是将一个复杂的大问题分解为多个小问题然后逐个解决。
在State模式中,我们用类来表示状态,并为每一种具体的状态都定义一个相应的类。这样,问题就被分解了。开发人员可以在编写一个ConcreteState 角色的代码的同时,在头脑中(一定程度上)考虑其他的类。在金库警报系统的示例程序中,只有“白天”和“晚上”两个状态,可能大家对此感受不深,但是当状态非常多的时候,State模式的优势就会非常明显了。
享元模式(FlyWeight)
场景
为了能够在计算机中保存该对象,需要分配给其足够的内存空间。当程序中需要大量对象时,如果都使用new关键字来分配内存,将会消耗大量内存空间。
关于Flyweight模式,一言以蔽之就是“通过尽量共享实例来避免new出实例”。
UML图
类和接口一览表
该模式下登场的角色
- Flyweight (轻量级)
按照通常方式编写程序会导致程序变重,所以如果能够共享实例会比较好,而Flyweight角色
表示的就是那些实例会被共享的类。在示例程序中,由BigChar类扮演此角色。 - FlyweightFactory (轻量级工厂)
FlyweightFactory角色是生成Flyweight角色的工厂。在厂中生成Flyweight角色可以实现共
享实例。在示例程序中,由BigCharFactory类扮演此角色。 - Client (请求者)
Client角色使用FlyweightFactory角色来生成Flyweight角色。在示例程序中,由BigString
类扮演此角色。
代码实现
- BigChar
public class BigChar {
private String charName;
private String fontData;
public BigChar(String charName) {
this.charName = charName;
try {
BufferedReader reader = new BufferedReader(new FileReader("u_flyweight/big" + charName + ".txt"));
StringBuffer sb = new StringBuffer();
String line;
while ((line = reader.readLine()) != null) {
sb.append(line);
sb.append("\n");
}
reader.close();
fontData=sb.toString();
} catch (IOException e) {
e.printStackTrace();
}
}
public void print() {
System.out.print(fontData);
}
}
- BigCharFactory
public class BigCharFactory {
private static BigCharFactory bigCharFactory = new BigCharFactory();
private Map<String, BigChar> bigCharMap = new HashMap<>();
private BigCharFactory() {
}
public static BigCharFactory getInstance() {
return bigCharFactory;
}
public synchronized BigChar getBigCharByCharName(String charName) {
BigChar bigChar = bigCharMap.get(charName);
if (bigChar == null) {
bigChar = new BigChar(charName);
bigCharMap.put(charName, bigChar);
}
return bigChar;
}
}
- BigString
public class BigString {
private BigChar[] bigChars;
public BigString(String str){
BigCharFactory bigCharFactory = BigCharFactory.getInstance();
bigChars=new BigChar[str.length()];
for (int i = 0; i < str.length(); i++) {
bigChars[i]=bigCharFactory.getBigCharByCharName(String.valueOf(str.charAt(i)));
}
}
public void print(){
for (int i = 0; i < bigChars.length; i++) {
bigChars[i].print();
}
}
}
- FlyWeightPattern
public class FlyWeightPattern {
public static void main(String[] args) {
BigString bigString = new BigString("123");
bigString.print();
}
}
使用该模式的好处
应当共享的信息被称作Intrinsic信息。Intrinsic 的意思是“本质的”“固有的”。换言之,它指
的是不论实例在哪里、不论在什么情况下都不会改变的信息,或是不依赖于实例状态的信息。在示
例程序中,BigChar的字体数据不论在BigString中的哪个地方都不会改变。因此,BigChar
的字体数据属于Intrinsic 信息。
另一方面,不应当共享的信息被称作Extrinsic信息。Extrinsic 的意思是“外在的”“非本质
的”。也就是说,它是当实例的位置、状况发生改变时会变化的信息,或是依赖于实例状态的信息。
在示例程序中,BigChar的实例在BigString中是第几个字符这种信息会根据BigChar在
BigString中的位置变化而发生变化。因此,不应当在BigChar中保存这个信息,它属于
Extrinsic信息。
代理模式(Proxy)
场景
这段示例程序实现了一个“带名字的打印机”。说是打印机,其实只是将文字显示在界面上而已。在Main类中会生成PrinterProxy类的实例(即“代理人”)。首先我们会给实例赋予名字Alice并在界面中显示该名字。接着会将实例名字改为Bob,然后显示该名字。在设置和获取名字时,都不会生成真正的Printer类的实例(即本人),而是由PrinterProxy类代理。最后,直到我们调用print方法,开始进入实际打印阶段后,PrinterProxy类才会生成Printer类的实例。
UML图
类和接口一览表
该模式下登场的角色
-
Subject(主体)
Subject角色定义了使Proxy角色和RealSubject角色之间具有一致性的接口。由于存在Subject角色,所以Client角色不必在意它所使用的究竟是Proxy角色还是RealSubject角色。在示例程序中,由 Printable接口扮演此角色。 -
Proxy(代理人)
Proxy角色会尽量处理来自Client角色的请求。只有当自己不能处理时,它才会将工作交给RealSubject角色。Proxy角色只有在必要时才会生成RealSubject角色。Proxy角色实现了在Subjec角色中定义的接口(API)。在示例程序中,由 PrinterProxy类扮演此角色。 -
RealSubject(实际的主体)
“本人"RealSubject角色会在“代理人"Proxy角色无法胜任工作时出场。它与Proxy角色一样也实现了在Subject角色中定义的接口(API )。在示例程序中,由Printer类扮演此角色。 -
Client(请求者)
使用Proxy模式的角色。Client角色并不包含在Proxy模式中。在示例程序中,由Main类扮演此角色。
代码实现
- Printable
public interface Printable {
String getPrintName();
void setPrintName(String name);
void print(String str);
}
- PrintProxy
public class PrintProxy implements Printable {
private String name;
private Printable real;
public PrintProxy(String name) {
this.name = name;
}
@Override
public String getPrintName() {
return name;
}
@Override
public synchronized void setPrintName(String name) {
if (real != null) {
real.setPrintName(name);
}
this.name = name;
}
@Override
public void print(String str) {
realize();
real.print(str);
}
private synchronized void realize() {
if (real == null) {
real = new Printer(name);
}
}
}
- Printer
public class Printer implements Printable {
private String name;
public Printer(String name) {
this.name = name;
heavyJob("正在生成printer实例");
}
@Override
public String getPrintName() {
return name;
}
@Override
public void setPrintName(String name) {
this.name = name;
}
@Override
public void print(String str) {
System.out.println("===" + name + "===");
System.out.println(str);
}
private void heavyJob(String info) {
System.out.print(info);
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.print(".");
}
System.out.println("结束");
}
}
- ProxyPattern
public class ProxyPattern {
public static void main(String[] args) {
Printable proxy = new PrintProxy("shouyaya");
System.out.println(proxy.getPrintName());
proxy.setPrintName("yzy");
proxy.print("zzzzzzzzz");
}
}
使用该模式的好处
- 使用代理人的好处
示例程序中的耗时处理的消耗时间并不算太长,大家可能感受不深。请大家试想一下,假如在一个大型系统的初始化过程中,存在大量的耗时处理。如果在启动系统时连那些暂时不会被使用的功能也初始化了,那么应用程序的启动时间将会非常漫长,这将会引发用户的不满。而如果我们只在需要使用某个功能时才将其初始化,则可以帮助我们改善用户体验。
- 透明性
PrinterProxy类和Printer类都实现了Printable接口,因此Main类可以完全不必在意调用的究竟是PrinterProxy类还是Printer类。无论是直接使用Printer类还是通过PrinterProxy类间接地使用Printer类都可以。
命令模式(command)
场景
这段示例程序是一个画图软件,它的功能很简单,即用户拖动鼠标时程序会绘制出红色圆点,点击clear按钮后会清除所有的圆点。用户每拖动一次鼠标,应用程序都会为“在这个位置画一个点”这条命令生成一个DrawCommand类的实例。只要保存了这条命令,以后有需要时就可以重新绘制。
UML图
类和接口一览表
该模式下登场的角色
- Command(命令)
Command角色负责定义命令的接口(API )。在示例程序中,由Command接口扮演此角色。 - ConcreteCommand(具体的命令)
ConcreteCommand角色负责实现在Command角色中定义的接口(API)。在示例程序中,由MacroCommand类和 DrawCommand类扮演此角色。 - Receiver(接收者)
Receiver角色是Command角色执行命令时的对象,也可以称其为命令接受者。在示例程序中,由DrawCanvas类接DrawCommand的命令。 - Client(请求者)
Client角色负责生成ConcreteCommand角色并分配Receiver角色。在示例程序中,由Main类扮演此角色。在响应鼠标拖拽事件时,它生成了DrawCommand类的实例,并将扮演Receiver角色的 DrawCanvas类的实例传递给了DrawCommand类的构造函数。 - Invoker (发动者)
Invoker角色是开始执行命令的角色,它会调用在Command角色中定义的接口(API)。在示例程序中,由Main类和DrawCanvas类扮演此角色。这两个类都调用了Command接口中的execute方法。Main类同时扮演了Client角色和 Invoker角色。
代码实现
- command.Command
public interface Command {
public abstract void execute();
}
- command.MacroCommand
public class MacroCommand implements Command {
// 命令的集合
private Stack commands = new Stack();
// 执行
public void execute() {
Iterator it = commands.iterator();
while (it.hasNext()) {
((Command)it.next()).execute();
}
}
// 添加命令
public void append(Command cmd) {
if (cmd != this) {
commands.push(cmd);
}
}
// 删除最后一条命令
public void undo() {
if (!commands.empty()) {
commands.pop();
}
}
// 删除所有命令
public void clear() {
commands.clear();
}
}
- drawer.Drawable
public interface Drawable {
public abstract void draw(int x, int y);
}
- drawer.DrawCanvas
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;
}
// 重新全部绘制
public void paint(Graphics g) {
history.execute();
}
// 绘制
public void draw(int x, int y) {
Graphics g = getGraphics();
g.setColor(color);
g.fillOval(x - radius, y - radius, radius * 2, radius * 2);
}
}
- drawer.DrawCommand
public class DrawCommand implements Command {
// 绘制对象
protected Drawable drawable;
// 绘制位置
private Point position;
// 构造函数
public DrawCommand(Drawable drawable, Point position) {
this.drawable = drawable;
this.position = position;
}
// 执行
public void execute() {
drawable.draw(position.x, position.y);
}
}
- CommandPattern
public class CommandPattern extends JFrame implements ActionListener, MouseMotionListener, WindowListener {
// 绘制的历史记录
private MacroCommand history = new MacroCommand();
// 绘制区域
private DrawCanvas canvas = new DrawCanvas(400, 400, history);
// 删除按钮
private JButton clearButton = new JButton("clear");
// 撤销按钮
private JButton undoButton = new JButton("undo");
// 构造函数
public CommandPattern(String title) {
super(title);
this.addWindowListener(this);
canvas.addMouseMotionListener(this);
clearButton.addActionListener(this);
undoButton.addActionListener(this);
Box buttonBox = new Box(BoxLayout.X_AXIS);
buttonBox.add(clearButton);
buttonBox.add(undoButton);
Box mainBox = new Box(BoxLayout.Y_AXIS);
mainBox.add(buttonBox);
mainBox.add(canvas);
getContentPane().add(mainBox);
pack();
show();
}
// ActionListener接口中的方法
public void actionPerformed(ActionEvent e) {
if (e.getSource() == clearButton) {
history.clear();
canvas.repaint();
} else if (e.getSource() == undoButton) {
history.undo();
canvas.repaint();
}
}
// MouseMotionListener接口中的方法
public void mouseMoved(MouseEvent e) {
}
public void mouseDragged(MouseEvent e) {
Command cmd = new DrawCommand(canvas, e.getPoint());
history.append(cmd);
cmd.execute();
}
// WindowListener接口中的方法
public void windowClosing(WindowEvent e) {
System.exit(0);
}
public void windowActivated(WindowEvent e) {
}
public void windowClosed(WindowEvent e) {
}
public void windowDeactivated(WindowEvent e) {
}
public void windowDeiconified(WindowEvent e) {
}
public void windowIconified(WindowEvent e) {
}
public void windowOpened(WindowEvent e) {
}
public static void main(String[] args) {
new CommandPattern("Command Pattern Sample");
}
}
使用该模式的好处
在示例程序中,MacroCommand类的实例(history)代表了绘制的历史记录。在该字段中保存了之前所有的绘制信息。也就是说,如果我们将它保存为文件,就可以永久保存历史记录。同时也可以进行撤销和回退。
解释器模式(Interpreter)
场景
在 Interpreter模式中,程序要解决的问题会被用非常简单的“迷你语言”表述出来,即用“迷你语言”编写的“迷你程序”把具体的问题表述出来。迷你程序是无法单独工作的,我们还需要用Java语言编写一个负责“翻译”( interpreter )的程序。翻译程序会理解迷你语言,并解释和运行迷你程序。这段翻译程序也被称为解释器。这样,当需要解决的问题发生变化时,不需要修改Java 语言程序,只需要修改迷你语言程序即可应对。
UML图
类和接口一览表
该模式下登场的角色
- AbstractExpression(抽象表达式)
AbstractExpression角色定义了语法树节点的共同接口(API )。在示例程序中,由Node类扮演此角色。在示例程序中,共同接口(API)的名字是parse,不过在图23-12中它的名字是interpreter。 - TerminalExpression(终结符表达式)
TerminalExpression角色对应BNF中的终结符表达式。在示例程序中,由 PrimitiveCommandNode类扮演此角色。 - NonterminalExpression(非终结符表达式)
NonterminalExpression角色对应BNF中的非终结符表达式。在示例程序中,由 ProgramNode类、commandNode类、RepeatCommandNode类和commandListNode类扮演此角色。 - Context(文脉、上下文)
Context角色为解释器进行语法解析提供了必要的信息。在示例程序中,由Context类扮演此角色。 - Client(请求者)
为了推导语法树,Client角色会调用TerminalExpression角色和NonterminalExpression角色。在示例程序中,由 Main类扮演此角色。
代码实现
- Node
public abstract class Node {
public abstract void parse(Context context) throws ParseException;
}
- CommandNode
public class CommandNode extends Node {
private Node node;
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);
}
}
public String toString() {
return node.toString();
}
}
- CommandListNode
public class CommandListNode extends Node {
private ArrayList list = new ArrayList();
public void parse(Context context) throws ParseException {
while (true) {
if (context.currentToken() == null) {
throw new ParseException("Missing 'end'");
} else if (context.currentToken().equals("end")) {
context.skipToken("end");
break;
} else {
Node commandNode = new CommandNode();
commandNode.parse(context);
list.add(commandNode);
}
}
}
public String toString() {
return list.toString();
}
}
- Context
public class Context {
private StringTokenizer tokenizer;
private String currentToken;
public Context(String text) {
tokenizer = new StringTokenizer(text);
nextToken();
}
public String nextToken() {
if (tokenizer.hasMoreTokens()) {
currentToken = tokenizer.nextToken();
} else {
currentToken = null;
}
return currentToken;
}
public String currentToken() {
return currentToken;
}
public void skipToken(String token) throws ParseException {
if (!token.equals(currentToken)) {
throw new ParseException("Warning: " + token + " is expected, but " + currentToken + " is found.");
}
nextToken();
}
public int currentNumber() throws ParseException {
int number = 0;
try {
number = Integer.parseInt(currentToken);
} catch (NumberFormatException e) {
throw new ParseException("Warning: " + e);
}
return number;
}
}
- PrimitiveCommandNode
public class PrimitiveCommandNode extends Node {
private String name;
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 undefined");
}
}
public String toString() {
return name;
}
}
- ProgramNode
public class ProgramNode extends Node {
private Node commandListNode;
public void parse(Context context) throws ParseException {
context.skipToken("program");
commandListNode = new CommandListNode();
commandListNode.parse(context);
}
public String toString() {
return "[program " + commandListNode + "]";
}
}
- RepeatCommandNode
public class RepeatCommandNode extends Node {
private int number;
private Node commandListNode;
public void parse(Context context) throws ParseException {
context.skipToken("repeat");
number = context.currentNumber();
context.nextToken();
commandListNode = new CommandListNode();
commandListNode.parse(context);
}
public String toString() {
return "[repeat " + number + " " + commandListNode + "]";
}
}
- InterpreterPattern
public class InterpreterPattern {
public static void main(String[] args) {
try {
BufferedReader reader = new BufferedReader(new FileReader("x_interpreter/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);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
使用该模式的好处
Interpreter模式还可以处理批处理语言,即将基本命令组合在一起,并按顺序执行或是循环执行的语言。本章中的无线玩具车操控就是一种批处理语言。