jtLiBrain

任何伟大的事都不会一蹴而就,三分智慧,七分韧性

0%

定义

这是一种根据访问权限决定客户可否访问对象的代理。比方说,如果你有一个雇员对象,保护代理允许雇员调用对象上的某些方法,经理还可以多调用一些其它的方法(像setSalary()),而人力资源处的雇员可以调用对象上的所有方法。

Javajava.lang.reflect包中支持代理,利用java.lang.reflect包可以在运行时动态地创建一个代理类,实现一个或多个接口,并将方法调用转发到你所指定的类。因为实际的代理类是在运行时创建的,我们称之为:动态代理。

我们利用Java的动态代理创建我们的“保护代理”:

  1. Proxy0类是由Java创建的,它实现了完整的Subject接口;
  2. 我们使用XXXInvocationHandler来告诉Proxy0类你要代理的行为,它实现了接口InvocationHandler中的invoke()方法,Proxy0实现的Subject接口的方法全部被转发给XXXInvocationHandler.invoke()

场景

约会服务系统

场景描述

该约会服务系统的要求:

  1. 允许设置和获取一个人的信息;
  2. 不允许用户篡改别人的数据;
  3. 具有评鉴功能,“Hot”表示喜欢对方,“Not”表示不喜欢。

实现方案

服务系统涉及到一个Person接口,允许设置和获取一个人的信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
public interface Person {
String getName();
String getGender();
String getInterests();
int getGeekRating();

void setName(String name);
void setGender(String gender);
void setInterests(String interests);
void setGeekRating(int rating);
}

public class PersonImpl implements Person {
String name;
String gender;
String interests;
int rating;
int ratingCount = 0;

public String getName() {
return name;
}
public String getGender() {
return gender;
}
public String getInterests() {
return interests;
}
public int getGeekRating() {
if (ratingCount == 0) return 0;
return (rating / ratingCount);
}

public void setName(String name) {
this.name = name;
}
public void setGender(String gender) {
this.gender = gender;
}
public void setInterests(String interests) {
this.interests = interests;
}
public void setGeekRating(int rating) {
this.rating += rating;
ratingCount++;
}
}

创建两个InvocationHandler类,invoke()中实现具体的代理行为;其中,OwnerInvocationHandler给拥有者使用,NonOwnerInvocationHandler给非拥有者使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class OwnerInvocationHandler implements InvocationHandler {
Person person;

public OwnerInvocationHandler(Person person) {
this.person = person;
}

public Object invoke(Object proxy, Method method, Object[] args)
throws IllegalAccessException {

try {
if (method.getName().startsWith("get")) {
return method.invoke(person, args);
} else if (method.getName().equals("setGeekRating")) {
throw new IllegalAccessException();
} else if (method.getName().startsWith("set")) {
return method.invoke(person, args);
}
} catch (InvocationTargetException e) {
e.printStackTrace();
}
return null;
}
}

public class NonOwnerInvocationHandler implements InvocationHandler {
Person person;

public NonOwnerInvocationHandler(Person person) {
this.person = person;
}

public Object invoke(Object proxy, Method method, Object[] args)
throws IllegalAccessException {

try {
if (method.getName().startsWith("get")) {
return method.invoke(person, args);
} else if (method.getName().equals("setGeekRating")) {
return method.invoke(person, args);
} else if (method.getName().startsWith("set")) {
throw new IllegalAccessException();
}
} catch (InvocationTargetException e) {
e.printStackTrace();
}
return null;
}
}

实例化Proxy对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
Person getOwnerProxy(Person person) {
return (Person) Proxy.newProxyInstance(
person.getClass().getClassLoader(),
person.getClass().getInterfaces(),
new OwnerInvocationHandler(person));
}

Person getNonOwnerProxy(Person person) {
return (Person) Proxy.newProxyInstance(
person.getClass().getClassLoader(),
person.getClass().getInterfaces(),
new NonOwnerInvocationHandler(person));
}

参考

  1. https://wickedlysmart.com/head-first-design-patterns/

定义

虚拟代理作为创建开销大的对象的代表。虚拟代理经常直到我们真正需要一个对象的时候才创建它。当对象在创建前和创建中时,由虚拟代理来扮演对象的替身。对象创建后,代理就会将请求直接委托给对象。

场景

可以显示CD封面的Swing应用

场景介绍

建立一个应用程序,用来展现你最喜欢的CD封面。应用程序提供一个下拉菜单,然后从相应网站取得对应的CD封面。已知,限于连接带宽和网络负载,下载可能需要一些时间,所以在等待图像加载的时候,应该给出一些友好的提示。要求在等待图像时整个应用程序不要被挂起。一旦图像加载完成,Swing组件上显示相应的图像。

解决方案

ImageProxy代理用于隐藏创建开销大的对象(因为我们需要通过网络获取图像数据)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
import java.net.*;
import java.awt.*;
import javax.swing.*;

class ImageProxy implements Icon {
volatile ImageIcon imageIcon;
final URL imageURL;
Thread retrievalThread;
boolean retrieving = false;

public ImageProxy(URL url) { imageURL = url; }

public int getIconWidth() {
if (imageIcon != null) {
return imageIcon.getIconWidth();
} else {
return 800;
}
}

public int getIconHeight() {
if (imageIcon != null) {
return imageIcon.getIconHeight();
} else {
return 600;
}
}

synchronized void setImageIcon(ImageIcon imageIcon) {
this.imageIcon = imageIcon;
}

public void paintIcon(final Component c, Graphics g, int x, int y) {
if (imageIcon != null) {
imageIcon.paintIcon(c, g, x, y);
} else {
g.drawString("Loading album cover, please wait...", x+300, y+190);
if (!retrieving) {
retrieving = true;

retrievalThread = new Thread(new Runnable() {
public void run() {
try {
setImageIcon(new ImageIcon(imageURL, "Album Cover"));
c.repaint();
} catch (Exception e) {
e.printStackTrace();
}
}
});

retrievalThread.start();
}
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import java.awt.*;
import javax.swing.*;

class ImageComponent extends JComponent {
private static final long serialVersionUID = 1L;
private Icon icon;

public ImageComponent(Icon icon) {
this.icon = icon;
}

public void setIcon(Icon icon) {
this.icon = icon;
}

public void paintComponent(Graphics g) {
super.paintComponent(g);
int w = icon.getIconWidth();
int h = icon.getIconHeight();
int x = (800 - w)/2;
int y = (600 - h)/2;
icon.paintIcon(this, g, x, y);
}
}

状态模式的定义

状态模式允许对象在其内部状态改变时改变它的行为,该对象看来好像修改了它的类。

上述定义中前半句话的含义:该模式将状态封装成为独立的类,并将动作委托到代表当前状态的对象,我们知道行为会随着内部状态而改变。下面糖果机的例子中,当糖果机在NoQuarterStateHasQuarterState两种不同的状态时,你投入25分钱,就会得到不同的行为(机器接受25分钱、机器拒绝25分钱)。

上述定义中后半句话的含义:从客户的视角来看,如果你使用的对象能够完全改变它的行为,那么你会觉得,这个对象会是从一个别的类实例化而来。然而,我们是使用组合的方式,通过简单应用不同的状态对象来造成类行为改变的假象。

场景

自动售卖糖果机

场景描述

下面实现糖果机,利用实例变量持有当前状态,然后处理所有可能发生的动作、行为和状态的转换。这样的设计难以应对需求的变更,一旦我们需要加入新的状态,那么下面的每一个方法都必须加入一个新的条件判断。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
public class GumballMachine {
final static int SOLD_OUT = 0;
final static int NO_QUARTER = 1;
final static int HAS_QUARTER = 2;
final static int SOLD = 3;

int state = SOLD_OUT;
int count = 0;

public GumballMachine(int count) {
this.count = count;
if (count > 0) {
state = NO_QUARTER;
}
}

public void insertQuarter() {
if (state == HAS_QUARTER) {
System.out.println("You can't insert another quarter");
} else if (state == NO_QUARTER) {
state = HAS_QUARTER;
System.out.println("You inserted a quarter");
} else if (state == SOLD_OUT) {
System.out.println("You can't insert a quarter, the machine is sold out");
} else if (state == SOLD) {
System.out.println("Please wait, we're already giving you a gumball");
}
}

public void ejectQuarter() {
if (state == HAS_QUARTER) {
System.out.println("Quarter returned");
state = NO_QUARTER;
} else if (state == NO_QUARTER) {
System.out.println("You haven't inserted a quarter");
} else if (state == SOLD) {
System.out.println("Sorry, you already turned the crank");
} else if (state == SOLD_OUT) {
System.out.println("You can't eject, you haven't inserted a quarter yet");
}
}

public void turnCrank() {
if (state == SOLD) {
System.out.println("Turning twice doesn't get you another gumball!");
} else if (state == NO_QUARTER) {
System.out.println("You turned but there's no quarter");
} else if (state == SOLD_OUT) {
System.out.println("You turned, but there are no gumballs");
} else if (state == HAS_QUARTER) {
System.out.println("You turned...");
state = SOLD;
dispense();
}
}

private void dispense() {
if (state == SOLD) {
System.out.println("A gumball comes rolling out the slot");
count = count - 1;
if (count == 0) {
System.out.println("Oops, out of gumballs!");
state = SOLD_OUT;
} else {
state = NO_QUARTER;
}
} else if (state == NO_QUARTER) {
System.out.println("You need to pay first");
} else if (state == SOLD_OUT) {
System.out.println("No gumball dispensed");
} else if (state == HAS_QUARTER) {
System.out.println("No gumball dispensed");
}
}

// 其他方法
}

方案

将每个状态的行为局部化到它自己的类中,这样一来,如果我们针对某个状态做了改变,就不会把其他的代码给搞乱了:

  1. 定义状态接口State,所有的状态都需要实现这个接口,接口中定义了糖果机上所有可能发生动作所代表的方法。每个具体的状态类继承自接口State,它们只需要关注各自所关心的方法,例如,NoQuarterState只需要关注“投币”动作,并当“投币”动作发生后,将状态转换到HasQuarterState
  2. 糖果机GumballMachine类在实例化时,初始化全部状态;state属性为糖果机当前的状态,每当糖果机接收到外部动作,它会委托给state指向的状态对象的相应方法,这样就实现了糖果机的状态转换;
1
2
3
4
5
6
7
8
public interface State {
public void insertQuarter();
public void ejectQuarter();
public void turnCrank();
public void dispense();

public void refill();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public class NoQuarterState implements State {
GumballMachine gumballMachine;

public NoQuarterState(GumballMachine gumballMachine) {
this.gumballMachine = gumballMachine;
}

public void insertQuarter() {
System.out.println("You inserted a quarter");
gumballMachine.setState(gumballMachine.getHasQuarterState());
}

public void ejectQuarter() {
System.out.println("You haven't inserted a quarter");
}

public void turnCrank() {
System.out.println("You turned, but there's no quarter");
}

public void dispense() {
System.out.println("You need to pay first");
}

public void refill() { }

public String toString() {
return "waiting for quarter";
}
}

实现更多的状态… …

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
public class GumballMachine {
State soldOutState;
State noQuarterState;
State hasQuarterState;
State soldState;

State state;
int count = 0;

public GumballMachine(int numberGumballs) {
soldOutState = new SoldOutState(this);
noQuarterState = new NoQuarterState(this);
hasQuarterState = new HasQuarterState(this);
soldState = new SoldState(this);

this.count = numberGumballs;
if (numberGumballs > 0) {
state = noQuarterState;
} else {
state = soldOutState;
}
}

public void insertQuarter() {
state.insertQuarter();
}

public void ejectQuarter() {
state.ejectQuarter();
}

public void turnCrank() {
state.turnCrank();
state.dispense();
}

void releaseBall() {
System.out.println("A gumball comes rolling out the slot...");
if (count > 0) {
count = count - 1;
}
}

int getCount() {
return count;
}

void refill(int count) {
this.count += count;
System.out.println("The gumball machine was just refilled; its new count is: " + this.count);
state.refill();
}

void setState(State state) {
this.state = state;
}
public State getState() {
return state;
}

public State getSoldOutState() {
return soldOutState;
}

public State getNoQuarterState() {
return noQuarterState;
}

public State getHasQuarterState() {
return hasQuarterState;
}

public State getSoldState() {
return soldState;
}
}

状态模式VS策略模式

状态模式 策略模式
我们把状态模式想成是不用在对象中放置许多条件判断的替代方案。通过将行为包装进状态对象中,你可以通过在对象内简单地改变状态对象来改变该对象的行为。 我们把策略模式想成是除了继承之外的一种弹性替代方案。如果你使用继承定义了一个类的行为,你将被这个行为困住,甚至要修改它都很难。有了策略模式,你可以通过组合不同的对象来改变行为。

组合模式的定义

组合模式允许你将对象组合成树形结构来表现“整体/部分”层次结构。组合能让客户以一致的方式处理个别对象以及对象组合,而忽略对象组合和个别对象之间的差异。

场景

紧接迭代器模式,又有咖啡厅被合并进来。

场景描述

咖啡厅被合并进来用来供应晚餐,下面是咖啡菜单:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class CafeMenu implements Menu {
Hashtable menuItems = new Hashtable();
public CafeMenu() {
addItem(“Veggie Burger and Air Fries”,
“Veggie burger on a whole wheat bun, lettuce, tomato, and fries”,
true, 3.99);
addItem(“Soup of the day”,
“A cup of the soup of the day, with a side salad”,
false, 3.69);
addItem(“Burrito”,
“A large burrito, with whole pinto beans, salsa, guacamole”,
true, 4.29);
}
public void addItem(String name, String description,
boolean vegetarian, double price) {
MenuItem menuItem = new MenuItem(name, description, vegetarian, price);
menuItems.put(menuItem.getName(), menuItem);
}
public Hashtable getItems() {
return menuItems;
}
}

依靠已经介绍过的迭代器模式,我们可以很容易的将这个菜单整合进我们的框架,但是我们面临的问题是:每加入一种新菜单就需要改动Waitress类,并且printMenu(Iterator iterator) 会被每种菜单都调用一次,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public class Waitress {
Menu pancakeHouseMenu;
Menu dinerMenu;
Menu cafeMenu;

public Waitress(Menu pancakeHouseMenu, Menu dinerMenu, Menu cafeMenu) {
this.pancakeHouseMenu = pancakeHouseMenu;
this.dinerMenu = dinerMenu;
this.cafeMenu = cafeMenu;
}

public void printMenu() {
Iterator pancakeIterator = pancakeHouseMenu.createIterator();
Iterator dinerIterator = dinerMenu.createIterator();
Iterator cafeIterator = cafeMenu.createIterator();
System.out.println(“MENU\n----\nBREAKFAST”);
printMenu(pancakeIterator);
System.out.println(“\nLUNCH”);
printMenu(dinerIterator);
System.out.println(“\nDINNER”);
printMenu(cafeIterator);
}

private void printMenu(Iterator iterator) {
while (iterator.hasNext()) {
MenuItem menuItem = (MenuItem)iterator.next();
System.out.print(menuItem.getName() + “, “);
System.out.print(menuItem.getPrice() + “ -- “);
System.out.println(menuItem.getDescription());
}
}
}

并且,餐厅还希望能够加上一份餐后甜点的“子菜单”,这样就像餐厅菜单都会有一个的甜点子菜单。

方案

事情的发展已经到达了一个复杂级别,不可避免地要对菜单进行重新设计了。我们需要:

  1. 某种树形结构,可以容纳菜单、子菜单和菜单项;

  2. 需要确保我们有一种方法可以遍历到菜单中每个菜单项,至少要像现在迭代器一样方便;

  3. 希望能有一个更为灵活的遍历菜单项的方式:比如,我们可能需要仅迭代晚餐的甜点菜单,或者我们可能需要迭代晚餐的整个菜单(包含甜点菜单);

这意味着:如果我们有了一个树形结构的菜单、子菜单和可能还带有菜单项子菜单,那么任何一个菜单都是一种“组合”。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public abstract class MenuComponent {
/* 组合相关的方法:新增、删除、获取 */
public void add(MenuComponent menuComponent) {
throw new UnsupportedOperationException();
}
public void remove(MenuComponent menuComponent) {
throw new UnsupportedOperationException();
}
public MenuComponent getChild(int i) {
throw new UnsupportedOperationException();
}

/* 菜单项使用的方法 */
public String getName() {
throw new UnsupportedOperationException();
}
public String getDescription() {
throw new UnsupportedOperationException();
}
public double getPrice() {
throw new UnsupportedOperationException();
}
public boolean isVegetarian() {
throw new UnsupportedOperationException();
}

/* 菜单和菜单项都需要实现的方法 */
public void print() {
throw new UnsupportedOperationException();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
public class MenuItem extends MenuComponent {
String name;
String description;
boolean vegetarian;
double price;

public MenuItem(String name,
String description,
boolean vegetarian,
double price) {
this.name = name;
this.description = description;
this.vegetarian = vegetarian;
this.price = price;
}

public String getName() {
return name;
}
public String getDescription() {
return description;
}
public double getPrice() {
return price;
}
public boolean isVegetarian() {
return vegetarian;
}

public void print() {
System.out.print(" " + getName());
if (isVegetarian()) {
System.out.print("(v)");
}
System.out.println(", " + getPrice());
System.out.println(" -- " + getDescription());
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public class Menu extends MenuComponent {
ArrayList menuComponents = new ArrayList();
String name;
String description;

public Menu(String name, String description) {
this.name = name;
this.description = description;
}

public void add(MenuComponent menuComponent) {
menuComponents.add(menuComponent);
}
public void remove(MenuComponent menuComponent) {
menuComponents.remove(menuComponent);
}
public MenuComponent getChild(int i) {
return (MenuComponent)menuComponents.get(i);
}
public String getName() {
return name;
}
public String getDescription() {
return description;
}

public void print() {
System.out.print("\n" + getName());
System.out.println(", " + getDescription());
System.out.println("---------------------");
}
}
1
2
3
4
5
6
7
8
9
public class Waitress {
MenuComponent allMenus;
public Waitress(MenuComponent allMenus) {
this.allMenus = allMenus;
}
public void printMenu() {
allMenus.print();
}
}

组合迭代器

TODO

迭代器模式的定义

迭代器模式提供了一种方法来顺序地访问一个聚合对象中的各个元素,而又不暴露其底层的表现形式。

客户直接面向AggregateIterator进行编程,每个具体的聚合类持有一个对象的集合,并实现一个方法返回对其内部对象集合的迭代器。

场景

餐厅和煎饼屋合并

场景描述

餐厅和煎饼屋合并了,煎饼屋负责提供早餐,餐厅负责提供午餐。已知:a)煎饼屋和餐厅都有各自的菜单,菜单上有多个菜单项,菜单项是统一的(包含名称、描述、价格);b)但煎饼屋和餐厅的菜单存储菜单项的方式是不同的:煎饼屋使用的是ArrayList,餐厅使用的是数组。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public class PancakeHouseMenu {
List<MenuItem> menuItems;

public PancakeHouseMenu() {
menuItems = new ArrayList<MenuItem>();
addItem("K&B's Pancake Breakfast",
"Pancakes with scrambled eggs and toast",
true,
2.99);
addItem("Regular Pancake Breakfast",
"Pancakes with fried eggs, sausage",
false,
2.99);
addItem("Blueberry Pancakes",
"Pancakes made with fresh blueberries",
true,
3.49);
addItem("Waffles",
"Waffles with your choice of blueberries or strawberries",
true,
3.59);
}

public void addItem(String name, String description,
boolean vegetarian, double price) {
MenuItem menuItem = new MenuItem(name, description, vegetarian, price);
menuItems.add(menuItem);
}

public ArrayList<MenuItem> getMenuItems() {
return menuItems;
}

// other menu methods here
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public class DinerMenu {
static final int MAX_ITEMS = 6;
int numberOfItems = 0;
MenuItem[] menuItems;

public DinerMenu() {
menuItems = new MenuItem[MAX_ITEMS];
addItem("Vegetarian BLT",
"(Fakin') Bacon with lettuce & tomato on whole wheat", true, 2.99);
addItem("BLT",
"Bacon with lettuce & tomato on whole wheat", false, 2.99);
addItem("Soup of the day",
"Soup of the day, with a side of potato salad", false, 3.29);
addItem("Hotdog",
"A hot dog, with sauerkraut, relish, onions, topped with cheese",
false, 3.05);
// a couple of other Diner Menu items added here
}

public void addItem(String name, String description,
boolean vegetarian, double price) {
MenuItem menuItem = new MenuItem(name, description, vegetarian, price);
if (numberOfItems >= MAX_ITEMS) {
System.err.println("Sorry, menu is full! Can't add item to menu");
} else {
menuItems[numberOfItems] = menuItem;
numberOfItems = numberOfItems + 1;
}
}

public MenuItem[] getMenuItems() {
return menuItems;
}

// other menu methods here
}

既然已经合并了,需要统一所提供的菜单规格,否则如果女招待员想要进行打印菜单printMenu()之类的操作就会变得比较繁琐,需要如下步骤:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
PancakeHouseMenu pancakeHouseMenu = new PancakeHouseMenu();
ArrayList<MenuItem> breakfastItems = pancakeHouseMenu.getMenuItems();

DinerMenu dinerMenu = new DinerMenu();
MenuItem[] lunchItems = dinerMenu.getMenuItems();


// 打印早餐
for (int i = 0; i < breakfastItems.size(); i++) {
MenuItem menuItem = breakfastItems.get(i);
System.out.print(menuItem.getName() + " ");
System.out.println(menuItem.getPrice() + " ");
System.out.println(menuItem.getDescription());
}

// 打印午餐
for (int i = 0; i < lunchItems.length; i++) {
MenuItem menuItem = lunchItems[i];
System.out.print(menuItem.getName() + " ");
System.out.println(menuItem.getPrice() + " ");
System.out.println(menuItem.getDescription());
}

以上代码,总是需要处理两种菜单,用两个循环遍历这些菜单项。这只是实现printMenu()一个功能,可以想象其它类似方法的实现同样要进行两个这样的循环遍历。

方案

上例中变化的部分是由不同集合类型所造成的遍历。能否将遍历封装起来?

使用迭代器可以实现对各种对象集合进行迭代。在PancakeHouseMenu类可以实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
public interface Iterator {
boolean hasNext();
MenuItem next();
}

public class DinerMenuIterator implements Iterator {
MenuItem[] items;
int position = 0;

public DinerMenuIterator(MenuItem[] items) {
this.items = items;
}

public MenuItem next() {
MenuItem menuItem = items[position];
position = position + 1;
return menuItem;
}

public boolean hasNext() {
if (position >= items.length || items[position] == null) {
return false;
} else {
return true;
}
}
}

public class DinerMenu {
static final int MAX_ITEMS = 6;
int numberOfItems = 0;
MenuItem[] menuItems;

// constructor here

// addItem here

public MenuItem[] getMenuItems() {
return menuItems;
}

public Iterator createIterator() {
return new DinerMenuIterator(menuItems);
}

// other menu methods here
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public class Waitress {
PancakeHouseMenu pancakeHouseMenu;
DinerMenu dinerMenu;

public Waitress(PancakeHouseMenu pancakeHouseMenu, DinerMenu dinerMenu) {
this.pancakeHouseMenu = pancakeHouseMenu;
this.dinerMenu = dinerMenu;
}

public void printMenu() {
Iterator pancakeIterator = pancakeHouseMenu.createIterator();
Iterator dinerIterator = dinerMenu.createIterator();

System.out.println("MENU\n----\nBREAKFAST");
printMenu(pancakeIterator);

System.out.println("\nLUNCH");
printMenu(dinerIterator);
}

private void printMenu(Iterator iterator) {
while (iterator.hasNext()) {
MenuItem menuItem = iterator.next();
System.out.print(menuItem.getName() + ", ");
System.out.print(menuItem.getPrice() + " -- ");
System.out.println(menuItem.getDescription());
}
}

// other methods here
}

方案的改良

我们已经知道这两份菜单的接口完全一样,但没有为它们设计一个共同的接口,为了让女招待员的工作变得更加干净,我们下面抽象出了Menu接口。同时我们会尝试直接使用Java中的Iterator接口。这样,煎饼屋菜单和餐厅菜单都实现了Menu接口,女招待员可以利用接口(而不是具体类)引用每一个菜单对象。

模板方法的定义

模板方法模式:在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以在不改变算法结构的情况下,重新定义算法的某些步骤。

场景

Starbuzz饮品店培训

场景描述

Starbuzz内部有两种饮料:咖啡和茶。需要对新加入的师傅进行饮品制作的培训,已知:a)咖啡和茶都有各自的冲泡方式,b)咖啡和茶的冲泡有相同和不同的步骤。如何才能简化培训时的步骤:难道一定要培训完一遍咖啡的制作过程,再培训一遍茶的制作过程吗?

Starbuzz咖啡冲泡法 Starbuzz茶冲泡法
1.把水煮沸;
2.用沸水冲泡咖啡;
3.把咖啡倒进杯子;
4.加糖和牛奶;
1.把水煮沸;
2.用沸水浸泡茶叶;
3.把茶倒进杯子;
4.加柠檬;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class Coffee {
void prepareRecipe() {
boilWater();
brewCoffeeGrinds();
pourInCup();
addSugarAndMilk();
}
public void boilWater() {
System.out.println("Boiling water");
}
public void brewCoffeeGrinds() {
System.out.println("Dripping Coffee through filter");
}
public void pourInCup() {
System.out.println("Pouring into cup");
}
public void addSugarAndMilk() {
System.out.println("Adding Sugar and Milk");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class Tea {
void prepareRecipe() {
boilWater();
steepTeaBag();
pourInCup();
addLemon();
}
public void boilWater() {
System.out.println("Boiling water");
}
public void steepTeaBag() {
System.out.println("Steeping the tea");
}
public void addLemon() {
System.out.println("Adding Lemon");
}
public void pourInCup() {
System.out.println("Pouring into cup");
}
}

方案

注意这两种冲泡法都采用了相同的算法:

  1. 把水煮沸;
  2. 用热水泡咖啡或茶;
  3. 把饮料倒进杯子;
  4. 在饮料内加入适当调料;

prepareRecipe()方法进行如下抽象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public abstract class CaffeineBeverage {
final void prepareRecipe() {
boilWater();
brew();
pourInCup();
addCondiments();
}

abstract void brew();
abstract void addCondiments();

void boilWater() {
System.out.println("Boiling water");
}
void pourInCup() {
System.out.println("Pouring into cup");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Tea extends CaffeineBeverage {
public void brew() {
System.out.println("Steeping the tea");
}
public void addCondiments() {
System.out.println("Adding Lemon");
}
}

public class Coffee extends CaffeineBeverage {
public void brew() {
System.out.println("Dripping Coffee through filter");
}
public void addCondiments() {
System.out.println("Adding Sugar and Milk");
}
}

以上抽象类CaffeineBeverage中的prepareRecipe()方法就是模版方法

  1. 首先,它是一个方法;
  2. 它用作一个算法的模板,定义了算法的步骤,在这个例子中,算法就是制作咖啡因饮料;算法内每一个步骤都被一个方法代表了,例如boilWater()brew()pourInCup()addCondiments()
  3. 算法内的一些方法在超类中处理,一些方法则是在子类中处理;

模板方法模式VS策略模式

模板方法模式 策略模式
定义 定义一个算法大纲,由子类定义其中算法的某些步骤 封装可互换的行为,使用委托来决定要采用哪一个行为
实现 通过继承 通过对象组合

外观模式的定义

场景

观看家庭影院

场景描述

你组装了一套家庭影院,包含DVD播放器、投影机、自动屏幕、环绕立体声,甚至还有爆米花机,如下图:

现在你准备开始享受一部电影,你要做:

  1. 打开爆米花机;
  2. 开始爆米花;
  3. 将灯光调暗;
  4. 放下屏幕;
  5. … …(此处省略N步)

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
popper.on();
popper.pop();

lights.dim(10);

screen.down();

projector.on();
projector.setInput(dvd);
projector.wideScreenMode();

amp.on();
amp.setDvd(dvd);
amp.setSurroundSound();
amp.setVolume(5);

dvd.on();
dvd.play(movie);

可以看到,播放电影我们就要做很多事情;同样,看完电影,你还要把一切都要关掉,还有很多事情等着你要去做。

方案

使用外观模式,通过实现一个提供了更合理接口的外观类,你可以将一个复杂的子系统变得容易使用。如果你需要原复杂子系统的强大威力,仍然可以使用原来的复杂接口;但如果你需要的是一个方便使用的接口,那么就使用外观类。

为家庭影院系统创建一个外观类HomeTheaterFacade,该类对外暴露出了几个简单的方法:watchMovie()endMovie()listenToRadio()endRadio()。这个外观类将家庭影院的各个组件看作成一个子系统,通过这个子系统来实现外观类中的接口。这样,客户代码就可以调用此外观类所提供的方法,而不必再调用这个子系统中各个组件的方法。所以,想要看电影,只需要调用一个方法watchMovie()就行了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
public class HomeTheaterFacade {
Amplifier amp;
Tuner tuner;
StreamingPlayer player;
CdPlayer cd;
Projector projector;
TheaterLights lights;
Screen screen;
PopcornPopper popper;

public HomeTheaterFacade(Amplifier amp,
Tuner tuner,
StreamingPlayer player,
Projector projector,
Screen screen,
TheaterLights lights,
PopcornPopper popper) {

this.amp = amp;
this.tuner = tuner;
this.player = player;
this.projector = projector;
this.screen = screen;
this.lights = lights;
this.popper = popper;
}

public void watchMovie(String movie) {
System.out.println("Get ready to watch a movie...");
popper.on();
popper.pop();
lights.dim(10);
screen.down();
projector.on();
projector.wideScreenMode();
amp.on();
amp.setStreamingPlayer(player);
amp.setSurroundSound();
amp.setVolume(5);
player.on();
player.play(movie);
}


public void endMovie() {
System.out.println("Shutting movie theater down...");
popper.off();
lights.on();
screen.up();
projector.off();
amp.off();
player.stop();
player.off();
}

public void listenToRadio(double frequency) {
System.out.println("Tuning in the airwaves...");
tuner.on();
tuner.setFrequency(frequency);
amp.on();
amp.setVolume(5);
amp.setTuner(tuner);
}

public void endRadio() {
System.out.println("Shutting down the tuner...");
tuner.off();
amp.off();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public static void main(String[] args) {
Amplifier amp = new Amplifier("Amplifier");
Tuner tuner = new Tuner("AM/FM Tuner", amp);
StreamingPlayer player = new StreamingPlayer("Streaming Player", amp);
CdPlayer cd = new CdPlayer("CD Player", amp);
Projector projector = new Projector("Projector", player);
TheaterLights lights = new TheaterLights("Theater Ceiling Lights");
Screen screen = new Screen("Theater Screen");
PopcornPopper popper = new PopcornPopper("Popcorn Popper");

HomeTheaterFacade homeTheater =
new HomeTheaterFacade(amp, tuner, player,
projector, screen, lights, popper);

homeTheater.watchMovie("Raiders of the Lost Ark");
homeTheater.endMovie();
}

外观模式VS适配器模式

适配器模式的定义

将一个类中的接口转换成客户期望的另一个接口。适配器让原本接口不兼容的类可以合作无间。

场景描述

鸭子数量不够,使用火鸡冒充鸭子

方案

1
2
3
4
5
6
7
8
9
10
11
12
13
public interface Duck {
public void quack();
public void fly();
}

public class MallardDuck implements Duck {
public void quack() {
System.out.println("Quack");
}
public void fly() {
System.out.println("I'm flying");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
public interface Turkey {
public void gobble();
public void fly();
}

public class WildTurkey implements Turkey {
public void gobble() {
System.out.println("Gobble gobble");
}
public void fly() {
System.out.println("I'm flying a short distance");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class TurkeyAdapter implements Duck {
Turkey turkey;
public TurkeyAdapter(Turkey turkey) {
this.turkey = turkey;
}
public void quack() {
turkey.gobble();
}
public void fly() {
for(int i=0; i < 5; i++) {
turkey.fly();
}
}
}

适配器模式VS装饰者模式

适配器模式 装饰者模式
意图是将一个类的接口转换为另一个服务期望的接口,简而言之意图是进行接口转换。 意图是对于被包装的接口,加入新的行为或责任,而不是进行接口之间的转换。

命令模式的定义

命令模式将“请求”封装成对象,以便使用不同的请求、队列、或者日志来参数化其它对象。命令模式也支持可撤销的操作。

场景

家电自动化遥控器

场景描述

遥控器具有7个可编程的插槽,其中每个插槽都可以指定到不同的家电装置(如电灯、风扇、热水器、音响设备、等等),并且每个插槽都有对应的开关按钮。遥控器还具有一个整体的撤销按钮。现在,需要设计一组控制遥控器的API,让每个插槽都能够控制一个或一组装置(备注,需要能够控制目前的设备和任务未来可能出现的设备)。

下图是一些已知家电设备厂商的API:

方案

使用命令模式将动作的请求者和动作的执行者对象中解藕。请求者为遥控器,执行者对象是家电设备,利用命令对象,把请求(例如,打开电灯)封装成一个特定对象(例如,客厅电灯对象)。所以,如果每个按钮都存储一个命令对象,那么当按钮被按下的时候,就可以请命令对象做相关的工作。遥控器并不需要知道工作内容是什么,只要有一个命令对象能和正确的对象沟通,把事情做好就可以了。这样,遥控器和电灯对象就解藕了。

1
2
3
4
public interface Command {
public void execute();
public void undo();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public class LightOnCommand implements Command {
Light light;

public LightOnCommand(Light light) {
this.light = light;
}

public void execute() {
light.on();
}

public void undo() {
light.off();
}
}

public class LightOffCommand implements Command {
Light light;

public LightOffCommand(Light light) {
this.light = light;
}

public void execute() {
light.off();
}

public void undo() {
light.on();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
public class RemoteControl {
Command[] onCommands;
Command[] offCommands;
Command undoCommand;

public RemoteControl() {
onCommands = new Command[7];
offCommands = new Command[7];
Command noCommand = new NoCommand();
for (int i = 0; i < 7; i++) {
onCommands[i] = noCommand;
offCommands[i] = noCommand;
}
undoCommand = noCommand;
}

public void setCommand(int slot, Command onCommand, Command offCommand) {
onCommands[slot] = onCommand;
offCommands[slot] = offCommand;
}
public void onButtonWasPushed(int slot) {
onCommands[slot].execute();
undoCommand = onCommands[slot];
}
public void offButtonWasPushed(int slot) {
offCommands[slot].execute();
undoCommand = onCommands[slot];
}

public void undoButtonWasPushed() {
undoCommand.undo();
}

public String toString() {
StringBuffer stringBuff = new StringBuffer();
stringBuff.append("\n------ Remote Control -------\n");
for (int i = 0; i < onCommands.length; i++) {
stringBuff.append("[slot " + i + "] " + onCommands[i].getClass().getName()
+ " " + offCommands[i].getClass().getName() + "\n");
}
return stringBuff.toString();
}
}

方案的更进一步

利用宏命令,实现一个按钮同时控制多个灯光、音箱、电视、DVD、热水器的功能。

1
2
3
4
5
6
7
8
9
10
11
12
13
public class MacroCommand implements Command {
Command[] commands;

public MacroCommand(Command[] commands) {
this.commands = commands;
}

public void execute() {
for (int i = 0; i < commands.length; i++) {
commands[i].execute();
}
}
}