完整教程:图解设计模式【2】

本系列共分为三篇文章,其中包含的设计模式如下表:

名称设计模式
图解设计模式【1】Iterator、Adapter、Template Method、Factory Method、Singleton、Prototype、 Builder、Abstract Factory、 Bridge、 Strategy
图解设计模式【2】Composite、 Decorator、 Visitor、 Chain of Responsibility、 Facade、 Mediator、 Observer、 Memento
图解设计模式【3】State、 Flyweight、 Proxy、 Command、 Interpreter、

Composite模式

Composite模式用于创造出容器结构、递归结构。能够使容器与内容具有一致性,创造出递归结构。

示例

«abstract»
Entry
getName()
getSize()
printList()
add()
File
name
size
getName()
getSize()
printList()
Directory
name
directory
getName()
getSize()
printList()
add()

Entry类是一个表示目录条目的抽象类。其子类为File类和Directory类。

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);
// 为一览加上前缀
public String toString() {
// 显示代表类的文字
return getName() + " (" + getSize() + ")";
}
}

File类是表示文件的类,他是Entry类的子类。

public class File
extends Entry {
private String name;
private int size;
public File(String name, int size) {
this.name = name;
this.size = size;
}
public String getName() {
return name;
}
public int getSize() {
return size;
}
protected void printList(String prefix) {
System.out.println(prefix + "/" + this);
}
}

Directory类是表示文件夹的类,它也是Entry类的子类。不论其中entry是哪个类的实例,都可以通过getSize方法得到它的大小,这就是Composite模式的特征——容器与内容的一致性。

import java.util.Iterator;
import java.util.ArrayList;
public class Directory
extends Entry {
private String name;
// 文件夹的名字
private ArrayList directory = new ArrayList();
// 文件夹中目录条目的集合
public Directory(String name) {
// 构造函数
this.name = name;
}
public String getName() {
// 获取名字
return name;
}
public int getSize() {
// 获取大小
int size = 0;
Iterator it = directory.iterator();
while (it.hasNext()) {
Entry entry = (Entry)it.next();
size += entry.getSize();
}
return size;
}
public Entry add(Entry entry) {
// 增加目录条目
directory.add(entry);
return this;
}
protected void printList(String prefix) {
// 显示目录条目一览
System.out.println(prefix + "/" + this);
Iterator it = directory.iterator();
while (it.hasNext()) {
Entry entry = (Entry)it.next();
entry.printList(prefix + "/" + name);
}
}
}

FileTreatMentException类

public class FileTreatmentException
extends RuntimeException {
public FileTreatmentException() {
}
public FileTreatmentException(String msg) {
super(msg);
}
}

Main类负责执行调用。

public class Main
{
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.printList();
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.printList();
} catch (FileTreatmentException e) {
e.printStackTrace();
}
}
}

解析

  • Leaf

    表示内容的角色,该角色不能放入其他对象

  • Composite

    表示容器的角色,可以放入Leaf和Composite

  • Component

    使Leaf和Composite具有一致性的角色。Component是Leaf和Composite的父类。

  • Client

    使用Composite模式的角色。

Uses
Client
«abstract»
Component
method1()
method2()
add()
remove()
getChild()
Leaf
method1()
method2()
Composite
children
method1()
method2()
add()
remove()
getChild()

使用Composite模式可以使容器与内容具有一致性,也可以称其为多个和单个的一致性,即将多个对象结合在一起,当作一个对象进行处理。

Decorator模式

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

示例

«abstract»
Display
getColumns()
getRows()
getRowText()
show()
StringDisplay
getColumns()
getRows()
getRowText()
«abstract»
Border
display()
SideBorder
borderChar
getColumns()
getRows()
getRowText()
FullBorder
getColumns()
getRows()
getRowText()
makeLine()

Display类是可以显示多行字符串的抽象类。

public abstract class Display
{
public abstract int getColumns();
// 获取横向字符数
public abstract int getRows();
// 获取纵向行数
public abstract String getRowText(int row);
// 获取第row行的字符串
public void show() {
// 全部显示
for (int i = 0; i <
getRows(); i++) {
System.out.println(getRowText(i));
}
}
}

StringDisplay类是用于显示当行字符串的类。

public class StringDisplay
extends Display {
private String string;
// 要显示的字符串
public StringDisplay(String string) {
// 通过参数传入要显示的字符串
this.string = string;
}
public int getColumns() {
// 字符数
return string.getBytes().length;
}
public int getRows() {
// 行数是1
return 1;
}
public String getRowText(int row) {
// 仅当row为0时返回值
if (row == 0) {
return string;
} else {
return null;
}
}
}

Border类是装饰边框的抽象类。虽然它所表示的是装饰边框,但它也是Display类的子类。通过继承,装饰边框与被装饰物具有了相同的方法。

public abstract class Border
extends Display {
protected Display display;
// 表示被装饰物
protected Border(Display display) {
// 在生成实例时通过参数指定被装饰物
this.display = display;
}
}

SideBorder类是一种具体的装饰边框,是Border类的子类。

public class SideBorder
extends Border {
private char borderChar;
// 表示装饰边框的字符
public SideBorder(Display display, char ch) {
// 通过构造函数指定Display和装饰边框字符 
super(display);
this.borderChar = ch;
}
public int getColumns() {
// 字符数为字符串字符数加上两侧边框字符数 
return 1 + display.getColumns() + 1;
}
public int getRows() {
// 行数即被装饰物的行数
return display.getRows();
}
public String getRowText(int row) {
// 指定的那一行的字符串为被装饰物的字符串加上两侧的边框的字符 
return borderChar + display.getRowText(row) + borderChar;
}
}

FullBorder类与SideBorder类一样,也是Border类的子类。

public class FullBorder
extends Border {
public FullBorder(Display display) {
super(display);
}
public int getColumns() {
// 字符数为被装饰物的字符数加上两侧边框字符数
return 1 + display.getColumns() + 1;
}
public int getRows() {
// 行数为被装饰物的行数加上上下边框的行数
return 1 + display.getRows() + 1;
}
public String getRowText(int row) {
// 指定的那一行的字符串
if (row == 0) {
// 上边框
return "+" + makeLine('-', display.getColumns()) + "+";
} else if (row == display.getRows() + 1) {
// 下边框
return "+" + makeLine('-', display.getColumns()) + "+";
} else {
// 其他边框
return "|" + display.getRowText(row - 1) + "|";
}
}
private String makeLine(char ch, int count) {
// 生成一个重复count次字符ch的字符串 
StringBuffer buf = new StringBuffer();
for (int i = 0; i < count; i++) {
buf.append(ch);
}
return buf.toString();
}
}

Main类是用于测试程序行为的类。

public class Main
{
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 FullBorder(
new SideBorder(
new FullBorder(
new StringDisplay("你好,世界。")
),
'*'
)
)
),
'/'
);
b4.show();
}
}

解析

  • Component

    增加功能时的核心角色。

  • ConcreteComponent

    实现了Component所定义的接口

  • Decorator

    具有与Component相同的接口。它内部保存了被装饰对象——Component角色。

  • ConcreteDecorator

    具体的Decorator角色。

ConcreteComponent
method1()
method2()
method3()
Component
method1()
method2()
method3()
Decorator
component
ConcreteDecorator
method1()
method2()
method3()

在Decorator模式中,装饰边框与被装饰物具有一致性。表示装饰边框的Border类是表示被装饰物的Display类的子类,这就体现了它们之间的一致性。也就是说,Border类与表示被装饰物的Display类具有相同的接口。得益于API的透明性,Decorator模式中也形成了类似于Composite模式中的递归结构。也就是说,装饰边框里面的”被装饰物“实际上又是别的物体的”装饰边框“。Decorator模式的主要目的是通过添加装饰物来增加对象的功能。

在Decorator模式中,装饰边框与被装饰物具有相同的接口。虽然接口时相同的,但是越装饰,功能则越多。可以实现不修改被装饰的类来新增功能。Decorator模式使用了委托。对”装饰边框“提出的要求会被转交给”被装饰物“去处理。

Decorator模式中用到了委托,它使类之间形成了弱关联关系。因此,不用改变框架代码,就可以生成一个与其他对象具有不同关系的新对象。

使用Decorator模式可以为程序添加许多功能。只要准备一些装饰边框,即使这些装饰边框只具有非常简单的功能,也可以将他们自由组合成为新的对象。

Decorator模式的一个缺点是会导致程序中增加许多功能类似的很小的类。

Visitor模式

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

示例

Uses
Uses
Uses
«abstract»
Visitor
visit(File)
visit(Directory)
ListVisitor
currentdir
visit(File)
visit(Directory)
Main
File
name
size
accpet()
getName()
getSize()
Directory
name
dir
iterator
accept()
getName()
getSize()
add()
«abstract»
Entry
getName()
getSize()
add()
iterator()
«interface»
Element
accept()

Visitor类是表示访问者的抽象类。

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

Element接口是接受访问者访问的接口。

public interface Element {
public abstract void accept(Visitor v);
}

Entry类本质上与Composite模式中Entry类是一样的。实现了Element接口。

import java.util.Iterator;
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 {
// 生成Iterator
throw new FileTreatmentException();
}
public String toString() {
// 显示字符串
return getName() + " (" + getSize() + ")";
}
}

File类也与Composite模式中的File类一样。在Visitor模式中要注意理解它是如何实现accept接口的。

public class File
extends Entry {
private String name;
private int size;
public File(String name, int size) {
this.name = name;
this.size = size;
}
public String getName() {
return name;
}
public int getSize() {
return size;
}
public void accept(Visitor v) {
v.visit(this);
}
}

Directory类是表示文件夹的类。与Composite模式中的Directory类相比,此处增加了iteratoraccept方法。

import java.util.Iterator;
import java.util.ArrayList;
public class Directory
extends Entry {
private String name;
// 文件夹名字
private ArrayList dir = new ArrayList();
// 目录条目集合
public Directory(String name) {
// 构造函数
this.name = name;
}
public String getName() {
// 获取名字
return name;
}
public int getSize() {
// 获取大小
int size = 0;
Iterator it = dir.iterator();
while (it.hasNext()) {
Entry entry = (Entry)it.next();
size += entry.getSize();
}
return size;
}
public Entry add(Entry entry) {
// 增加目录条目
dir.add(entry);
return this;
}
public Iterator iterator() {
// 生成Iterator
return dir.iterator();
}
public void accept(Visitor v) {
// 接受访问者的访问
v.visit(this);
}
}

ListVisitor类是Visitor类的子类,作用是访问数据结构并显示元素一览。

import java.util.Iterator;
public class ListVisitor
extends Visitor {
private String currentdir = "";
// 当前访问的文件夹的名字
public void visit(File file) {
// 在访问文件时被调用
System.out.println(currentdir + "/" + file);
}
public void visit(Directory directory) {
// 在访问文件夹时被调用
System.out.println(currentdir + "/" + directory);
String savedir = currentdir;
currentdir = currentdir + "/" + directory.getName();
Iterator it = directory.iterator();
while (it.hasNext()) {
Entry entry = (Entry)it.next();
entry.accept(this);
}
currentdir = savedir;
}
}
public class FileTreatmentException
extends RuntimeException {
public FileTreatmentException() {
}
public FileTreatmentException(String msg) {
super(msg);
}
}

Main类使用了访问者ListVisitor类的实例来显示Directory中的内容。

public class Main
{
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();
}
}
}

解析

  • Visitor

    Visitor负责对数据结构中每个具体的元素(ConcreteElement)角色声明一个用于访问XXXX的visit(XXXX)方法。

  • ConcreteVisitor

    ConcreteVisitor负责实现Visitor所定义的接口(API)。要实现所有的visit(XXXX)方法,即实现如何处理每个ConcreteElement角色。

  • Element

    Element表示Visitor的访问对象。它声明了接受访问者的accept方法。

  • ConcreteElement

    ConcreteElement负责实现Element所定义的接口。

  • ObjectStructure

    ObjectStructure负责处理Element的集和。

«abstract»
Visitor
visit(ConcreteElementA)
visit(ConcreteElementB)
ConcreteVisitor
visit(ConcreteElementA)
visit(ConcreteElementB)
«abstract»
Element
accept*
«abstract»
ConcreteElementA
accpet()
«abstract»
ConcreteElementB
accept()
ObjectStructure

accpet的方法的调用方式如下:element.accept(visitor),而visitor方法的调用方式如下:visitor.visit(element),对比发现,它们是相反的关系。在Visitor模式中,ConcreteElement和ConcreteVisitor这两个角色共同决定了实际进行的处理。这种消息分发的方式被称为双重分发。

Visitor模式的目的是将处理从数据结构中分离出来。数据结构很重要,它能将元素集合和关联在一起。但是需要注意的是,保存数据结构与以数据结构为基础进行处理是两种不同的东西。

开闭原则:对扩展是开放的,对修改时关闭的。在不修改现有代码的前提下进行扩展

Chain of Responsibiltiymo模式

Chain of responsibility模式考虑将多个对象组成一条职责链,然后按照它们在职责链上的顺序一个一个地找出到底应该谁来负责处理。

使用Chain of responsibility模式可以弱化“请求方”和“处理方“之间的关联关系,让双方各自都成为可以独立复用的组件。

示例

Request
Main
«abstract»
Support
-name
-next
+support()
+setNext()
#resoulve()
NoSupport
#resolve()
LimitSupport
-limit
#resolve()
OddSupport
#reolve()
SpecialSupport
-number
#resolve()

Trouble类是表示问题发生的类

public class Trouble
{
private int number;
// 问题编号
public Trouble(int number) {
// 生成问题
this.number = number;
}
public int getNumber() {
// 获取问题编号
return number;
}
public String toString() {
// 代表问题的字符串
return "[Trouble " + number + "]";
}
}

Support类是用来解决问题的抽象类,它是职责链上的对象。support方法调用了方法resolve,因此属于Template Mehtod模式。

public abstract class Support
{
private String name;
// 解决问题的实例的名字
private Support next;
// 要推卸给的对象
public Support(String name) {
// 生成解决问题的实例
this.name = name;
}
public Support setNext(Support next) {
// 设置要推卸给的对象
this.next = next;
return next;
}
public void support(Trouble trouble) {
// 解决问题的步骤
if (resolve(trouble)) {
done(trouble);
} else if (next != null) {
next.support(trouble);
} else {
fail(trouble);
}
}
public String toString() {
// 显示字符串
return "[" + name + "]";
}
protected abstract boolean resolve(Trouble trouble);
// 解决问题的方法
protected void done(Trouble trouble) {
// 解决
System.out.println(trouble + " is resolved by " + this + ".");
}
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);
}
protected boolean resolve(Trouble trouble) {
// 解决问题的方法
return false;
// 自己什么也不处理
}
}

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

public class LimitSupport
extends Support {
private int limit;
// 可以解决编号小于limit的问题
public LimitSupport(String name, int limit) {
// 构造函数
super(name);
this.limit = limit;
}
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);
}
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;
}
protected boolean resolve(Trouble trouble) {
// 解决问题的方法
if (trouble.getNumber() == number) {
return true;
} else {
return false;
}
}
}

Main类生成解决实例。

public class Main
{
public static void main(String[] args) {
Support alice = new NoSupport("Alice");
Support bob = new LimitSupport("Bob", 100);
Support charlie = new SpecialSupport("Charlie", 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));
}
}
}

解析

  • Handler

    Handler定义了处理请求的API。Handler知道下一个处理者是谁,如果自己无法处理请求,就会将请求转交给下一个处理者。

  • ConcreteHandler

    ConcreteHandler是处理请求的具体角色。

  • Client

    Client是向第一个ConcreteHandler角色发送请求的角色。

Request
Client
Handler
next
request()
ConcreteHandler1
request()
ConcreteHandler2
request()

Chan of Responsibility 模式的最大优点就在于它弱化了发出请求的人和处理请求的人之间的关系。Client向第一个ConcreteHandler发出请求,然后请求会在职责链中传播,直到某个ConcreteHandler处理该请求。如果不使用该模式,就必须有某个伟大的角色知道”谁应该处理什么请求“,让”发出请求的人“知道”谁应该处理该请求“并不明智,因为如果发出请求的人不得不知道处理请求的人各自的责任分担情况,就会降低其作为可复用的组件的独立性。

使用Chain of Responsiblity模式,通过委托推卸责任,就可以根据情况变化动态地重组职责链。

通过推卸责任,可以使每个对象更专注自己的工作,即每个ConcreteHandler都专注于自己所负责的处理。当自己无法处理时,就会干脆地交给下一个处理者。不过使用Chain of Responsibility模式会导致处理延迟。

Facade模式

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

示例

Uses
Uses
Uses
Main
PageMaker
makeWelcomePage()
HtmlWriter
writer
title()
paragraph()
link()
mailto()
close()
Database
getProperties()

Database类可获取指定数据库名所对应的Properties的实例。

package pagemaker;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Properties;
public class Database
{
private Database() {
// 防止外部new出Database的实例,所以声明为private方法
}
public static Properties getProperties(String dbname) {
// 根据数据库名获取Properties
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;
}
}

HtmlWriter类用于编写简单的Web页面。

package pagemaker;
import java.io.Writer;
import java.io.IOException;
public class HtmlWriter
{
private Writer writer;
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();
}
}

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

package pagemaker;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Properties;
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();
}
}
}

Main类使用了PageMaker类,具体内容只有一行。

import pagemaker.PageMaker;
public class Main
{
public static void main(String[] args) {
PageMaker.makeWelcomePage("hyuki@hyuki.com", "welcome.html");
}
}

解析

  • Facade

    Facade代表构成系统的许多其它角色的简单窗口。Facade向系统外部提供高层API。

  • 构成系统的许多其他角色

    这些角色各自完成自己的工作,并不知道Facade角色。Facade调用其他角色进行工作,但是其他角色不会调用Facade角色。

  • Client

    Client负责调用Facade角色,此角色不包含在Facade模式中。

Facade模式可以让复杂的东西看起来简单,让我们不用在意后台工作的类之间的关系和他们的使用方法。使得API变少。程序中如果有很多类和方法,在决定到底应该使用哪个类或是方法时,就会很迷茫。有时,类和方法的调用顺序也很容易弄错,必须格外注意。

API变少还意味着程序和外部的关联关系弱化,这样更容易使包作为组件复用。

假设现在有几个持有Facade的类的集和。那么可以通过整合这几个集和来引入新的Facade,递归调用Facde模式。

Mediator模式

当发生麻烦事情时,通知仲裁者。当发生涉及全体组员的事情时,也通知仲裁者。当仲裁者下达指示时,组员会立即执行。团队组员之间不再互相沟通并私自做出决定,而是发生任何事情都向仲裁者报告。另一方面,仲裁者站在整个团队的角度上对组员上报的事情做出决定。这就是Mediator模式。

当要调整多个对象之间的关系时,就需要用到Mediator模式。不让各个对象之间互相通信,增加一个仲裁者角色,让他们各自与仲裁者进行通信。然后,将控制显示的逻辑处理交给仲裁者负责。

示例

Frame
LoginFrame
checkGuest
checkLogin
textUser
textPass
buttonOk
buttonCancel
createColleagues()
colleagueChanged()
userpassChanged()
actionPerformed()
«interface»
Mediator
createColleagues()
colleagueChanged()
«interface»
Colleague
setMediator()
setConlleagueEnabled()
ColleagueButton
mediator
setMediator()
setConlleagueEnabled()
ColleagueTextField
mediator
setMediator()
setColleagueEnabled()
textValueChanged()
ColleagueCheckbox
mediator
setMediator()
setColleagueEnabled()
itemStateChanged()
Button
TextField
Checkbox

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实现了Colleague接口,与LoginFrame共同工作

import java.awt.Button;
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);
}
}

ColleagueTextField类实现了Colleague接口

import java.awt.TextField;
import java.awt.Color;
import java.awt.event.TextListener;
import java.awt.event.TextEvent;
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();
}
}

ColleagueCheckbox类作为单选按钮使用

import java.awt.Checkbox;
import java.awt.CheckboxGroup;
import java.awt.event.ItemListener;
import java.awt.event.ItemEvent;
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();
}
}

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

import java.awt.Frame;
import java.awt.Label;
import java.awt.Color;
import java.awt.CheckboxGroup;
import java.awt.GridLayout;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
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);
}
}

Main类生成了LoginFrame的实例。

import java.awt.*;
import java.awt.event.*;
public class Main
{
static public void main(String args[]) {
new LoginFrame("Mediator Sample");
}
}

解析

  • Mediator

    定义与Colleague进行通信和做出决定的API。

  • ConcreteMediator

    实现Mediator的API,负责实际做出决定。

  • Colleague

    负责定义与Mediator进行通信的API。

  • ConcreteColleague

    实现Colleague的API

«abstract»
Mediator
createColleagues()
colleagueChanged()
Colleague
mediator
setMediator()
controlColleague()
ConcreteMediator
concreteColleague1
concreteColleague2
concreteColleague3
createCollagues()
colleagueChange()
ConcreteColleague1
controlColleague()
ConcreteColleague2
controlColleague()
ConcreteColleague3
controlColleague()

即使colleagueChanged方法中发生了Bug,由于其他地方没有控制控件的启用/禁用状态的逻辑处理,因此只要调试该方法就能很容易地找出Bug的原因。

Observer模式

在Observer模式中,当观察对象的状态发生变化时,会通知给观察者。Observer模式适用于根据对象状态进行相应处理的场景。

示例

NumberGenerator
observers
addObserver()
deleteObserver()
notifyObservers()
getNumber()
execute()
RandomNumberGenerator
random
number
getNumber()
execute()
«interface»
Observer
update()
DigitObserver
update()
GraphObserver
update()

Observer接口是表示观察者的接口。具体的观察者会实现这个接口。

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

NumberGenerator类是用于生成数值的抽象类。

import java.util.ArrayList;
import java.util.Iterator;
public abstract class NumberGenerator
{
private ArrayList observers = new ArrayList();
// 保存Observer们
public void addObserver(Observer observer) {
// 注册Observer
observers.add(observer);
}
public void deleteObserver(Observer observer) {
// 删除Observer
observers.remove(observer);
}
public void notifyObservers() {
// 向Observer发送通知
Iterator it = observers.iterator();
while (it.hasNext()) {
Observer o = (Observer)it.next();
o.update(this);
}
}
public abstract int getNumber();
// 获取数值
public abstract void execute();
// 生成数值
}

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

import java.util.Random;
public class RandomNumberGenerator
extends NumberGenerator {
private Random random = new Random();
// 随机数生成器
private int number;
// 当前数值
public int getNumber() {
// 获取当前数值
return number;
}
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) {
}
}
}

GraphObserver类实现了Observer接口。

public class GraphObserver
implements Observer {
public void update(NumberGenerator generator) {
System.out.print("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) {
}
}
}

Main类生成了一个RandomNumberGenerator类的两个实例和两个观察者。

public class Main
{
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();
}
}

解析

  • Subject

    Subject表示观察对象。其中定义了注册观察者和删除观察者的方法。此外,还声明了“获取现在状态”的方法。

  • ConcreteSubject

    ConcreteSubject表示具体的被观察对象。当自身状态发生变化后,会通知所有已经注册的Observer角色。

  • Observer

    Observer负责接收来自Subject角色的状态变化的通知。

  • ConcreteObserver

    ConcreteObserver表示具体的Observer。

ConcreteSubject
getSubjectStatus()
«interface»
Subject
observers
addObserver()
deleteObserver()
notifyObservers()
getSubjectStatus()
«abstract»
Observer
update()
ConcreteObserver
update()

使用设计模式的目的之一就是使类成为可复用的组件。在Observer模式中,有带状态的ConcreteSubject角色和接收状态变化通知的ConcreteObserver角色。连接这两个角色的就是他们的APi Subject角色和Observer角色。

Subject中注册有多个Observer角色。需要注意Observer的update方法的调用顺序,不能因为update方法的调用顺序发生改变而产生问题。

Memento模式

使用面向对象编程的方式实现撤销功能时,需要实现保存实例的相关信息。然后,在撤销时,还需要根据所保存的信息将实例恢复至原来的状态。要想恢复实例,需要一个可以自由访问实力内部结构的权限。但是,如果稍不注意,又可能会将依赖于实例内部结构的代码分散地编写在程序中的各个地方看,导致程序变得难以维护。这种情况就是破坏了封装性

通过引入表示实例状态的角色,可以在保存和恢复实例时有效地防止对象的封装性遭到破坏。

使用Memento模式可以实现如下功能:

  • undo
  • redo
  • history
  • snapshot

示例

Requests
Creates
Main
Gamer
-money
-fruits
-random
-fruitsname$
+getMoney()
+bet()
+createMemento()
+restoreMemento()
+tostring()
-getFruit()
Memento
<money
<fruits
+getMoney()
~Memento()
~addFruit()

Memento是表示Gamer状态的类。

package game;
import java.util.*;
public class Memento
{
int money;
// 所持金钱
ArrayList fruits;
// 当前获得的水果
public int getMoney() {
// 获取当前所持金钱(narrow interface)
return money;
}
Memento(int money) {
// 构造函数(wide interface)
this.money = money;
this.fruits = new ArrayList();
}
void addFruit(String fruit) {
// 添加水果(wide interface)
fruits.add(fruit);
}
List getFruits() {
// 获取当前所持所有水果(wide interface)
return (List)fruits.clone();
}
}

Gamer类是表示游戏主人公的类。

package game;
import java.util.*;
public class Gamer
{
private int money;
// 所持金钱
private List fruits = new ArrayList();
// 获得的水果
private Random random = new Random();
// 随机数生成器
private static String[] fruitsname = {
// 表示水果种类的数组
"苹果", "葡萄", "香蕉", "橘子",
};
public Gamer(int money) {
// 构造函数
this.money = money;
}
public int getMoney() {
// 获取当前所持金钱
return money;
}
public void bet() {
// 投掷骰子进行游戏
int dice = random.nextInt(6) + 1;
// 掷骰子
if (dice == 1) {
// 骰子结果为1…增加所持金钱
money += 100;
System.out.println("所持金钱增加了。");
} else if (dice == 2) {
// 骰子结果为2…所持金钱减半
money /= 2;
System.out.println("所持金钱减半了。");
} else if (dice == 6) {
// 骰子结果为6…获得水果
String f = getFruit();
System.out.println("获得了水果(" + f + ")。");
fruits.add(f);
} else {
// 骰子结果为3、4、5则什么都不会发生
System.out.println("什么都没有发生。");
}
}
public Memento createMemento() {
// 拍摄快照
Memento m = new Memento(money);
Iterator it = fruits.iterator();
while (it.hasNext()) {
String f = (String)it.next();
if (f.startsWith("好吃的")) {
// 只保存好吃的水果
m.addFruit(f);
}
}
return m;
}
public void restoreMemento(Memento memento) {
// 撤销
this.money = memento.money;
this.fruits = memento.getFruits();
}
public String toString() {
// 用字符串表示主人公状态
return "[money = " + money + ", fruits = " + fruits + "]";
}
private String getFruit() {
// 获得一个水果
String prefix = "";
if (random.nextBoolean()) {
prefix = "好吃的";
}
return prefix + fruitsname[random.nextInt(fruitsname.length)];
}
}

Main类生成了一个Gamer类的实例并进行游戏。它会重复调用Gamer的bet方法,并显示Gamer的所持金钱。

import game.Memento;
import game.Gamer;
public class Main
{
public static void main(String[] args) {
Gamer gamer = new Gamer(100);
// 最初的所持金钱数为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() + "元。");
// 决定如何处理Memento
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) {
}
System.out.println("");
}
}
}

解析

  • Orginator

    Originator会在保存自己的最新状态时生成Memento角色。当把以前保存的Memento角色传递给Originator时,它会将自己恢复至生成该Memento角色时的状态。

  • Memento

    Memento会将Originator的内部信息整合在一起。

    • wide interface

      Memento提供wide interface 是指所有用于恢复对象状态信息的方法的集和。

    • narrow interface

      Memento为外部的Caretaker提供了 narrow interface。可以通过narrow interface 获取的Memento的内部信息非常有限。

    • Caretaker

      当Caretaker想要保存当前的Originator的状态时,会通知Originator。Originator在接收到通知后会生成Memento的实例并将其返回给Caretaker。Caretaker只能使用Memento的两种接口中的narrow interface。

Requests
Creates
Caretaker
Originator
createMemento()
restoreMemento()
Memento
~getProtectedInfo()
+getPublicInfo()

Reference

图解设计模式 【日】结成浩 著 杨文轩 译

posted @ 2025-09-11 20:51  wzzkaifa  阅读(7)  评论(0)    收藏  举报