Plusaber's Blog

  • Home

  • Tags

  • Categories

  • Archives

Command Pattern

Posted on 2015-01-02 | In Design Pattern | Comments:

命令模式

定义命令模式

命令模式将”请求”封装成命令对象,以便使用不同的请求、队列或者日志来参数化其他对象,作为命令执行实际的主体,也就是命令实际的接收者。命令模式还可支持可撤销的操作。

一个命令对象通过在特定接收者上绑定一组动作来封装一个请求。要做到这一点,命令对象将动作和接收者包装进对象。这个对象只暴露一个execute()方法,当此方法被调用时,接收者就会进行这些动作。从外面开来,调用对象并不知道究竟哪个接收者进行了哪些动作,只知道如果调用exectue()方法,请求的目的就能达到。

通过对基本命令模式进行扩展,我们可以轻易实现Meta command pattern。meta command pattern可以创建命令的宏,以便依次执行多个命令。

Design_pattern_command_1

Design_pattern_command_2

Design_pattern_command_3

Design_pattern_command_1

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
public interface Command {
public void execute();
}

public class LightOnCommand implements Command {
Light light;

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

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

public class LightOffCommand implements Command {
Light light;

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

public void execute() {
light.off();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
public class SimpleRemoteControl {
Command slot;

public SimpleRemoteControl() {}

public void setCommand(Command command) {
slot = command;
}

public void buttonWasPressed() {
slot.execute();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class RemoteControlTest {
public static void main(String[] args) {
SimpleRemoteControl remote = new SimpleRemoteControl();
Light light = new Light();
GarageDoor garageDoor = new GarageDoor();
LightOnCommand lightOn = new LightOnCommand(light);
GarageDoorOpenCommand garageOpen =
new GarageDoorOpenCommand(garageDoor);

remote.setCommand(lightOn);
remote.buttonWasPressed();
remote.setCommand(garageOpen);
remote.buttonWasPressed();
}
}

提供撤销操作

通过提供在Command提供undo方法,我们可以实现撤销操作。如果有多个命令对象,则需要记录前面一个操作的是哪个命令对象。

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 LightOffCommand implements Command {
Light light;

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

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

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

public class LightOnCommand implements Command {
Light light;

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

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

public void undo() {
light.off();
}
}
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 class RemoteControlWithUndo {
Command[] onCommands;
Command[] offCommands;
Command undoCommand;

public RemoteControlWithUndo() {
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 = offCommands[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");
}
stringBuff.append("[undo] " + undoCommand.getClass().getName() + "\n");
return stringBuff.toString();
}
}

使用状态实现撤销

如果需要实现更复杂的撤销操作,我们需要加入状态,并记录前面的状态。

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
public class CeilingFan {
public static final int HIGH = 3;
public static final int MEDIUM = 2;
public static final int LOW = 1;
public static final int OFF = 0;
String location;
int speed;

public CeilingFan(String location) {
this.location = location;
speed = OFF;
}

public void high() {
speed = HIGH;
System.out.println(location + " ceiling fan is on high");
}

public void medium() {
speed = MEDIUM;
System.out.println(location + " ceiling fan is on medium");
}

public void low() {
speed = LOW;
System.out.println(location + " ceiling fan is on low");
}

public void off() {
speed = OFF;
System.out.println(location + " ceiling fan is off");
}

public int getSpeed() {
return speed;
}
}
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
public class CeilingFanHighCommand implements Command {
CeilingFan ceilingFan;
int prevSpeed;

public CeilingFanHighCommand(CeilingFan ceilingFan) {
this.ceilingFan = ceilingFan;
}

public void execute() {
prevSpeed = ceilingFan.getSpeed();
ceilingFan.high();
}

public void undo() {
if (prevSpeed == CeilingFan.HIGH) {
ceilingFan.high();
} else if (prevSpeed == CeilingFan.MEDIUM) {
ceilingFan.medium();
} else if (prevSpeed == CeilingFan.LOW) {
ceilingFan.low();
} else if (prevSpeed == CeilingFan.OFF) {
ceilingFan.off();
}
}
}

使用宏命令

通过宏命令,我们可以一次执行多个命令。宏命令本身也是封装为一个命令对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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();
}
}

public void undo() {
for (int i = 0; i < commands.length; i++) {
commands[i].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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
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 = offCommands[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");
}
stringBuff.append("[undo] " + undoCommand.getClass().getName() + "\n");
return stringBuff.toString();
}
}
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 RemoteLoader {

public static void main(String[] args) {

RemoteControl remoteControl = new RemoteControl();

Light light = new Light("Living Room");
TV tv = new TV("Living Room");
Stereo stereo = new Stereo("Living Room");
Hottub hottub = new Hottub();

LightOnCommand lightOn = new LightOnCommand(light);
StereoOnCommand stereoOn = new StereoOnCommand(stereo);
TVOnCommand tvOn = new TVOnCommand(tv);
HottubOnCommand hottubOn = new HottubOnCommand(hottub);
LightOffCommand lightOff = new LightOffCommand(light);
StereoOffCommand stereoOff = new StereoOffCommand(stereo);
TVOffCommand tvOff = new TVOffCommand(tv);
HottubOffCommand hottubOff = new HottubOffCommand(hottub);

Command[] partyOn = { lightOn, stereoOn, tvOn, hottubOn};
Command[] partyOff = { lightOff, stereoOff, tvOff, hottubOff};

MacroCommand partyOnMacro = new MacroCommand(partyOn);
MacroCommand partyOffMacro = new MacroCommand(partyOff);

remoteControl.setCommand(0, partyOnMacro, partyOffMacro);

System.out.println(remoteControl);
System.out.println("--- Pushing Macro On---");
remoteControl.onButtonWasPushed(0);
System.out.println("--- Pushing Macro Off---");
remoteControl.offButtonWasPushed(0);
}
}

命令模式还可以用于日志请求。某些应用需要我们将所有的动作都记录在日志中,并能在系统死机之后,重新调用这些动作恢复到之前到状态。

也就是执行命令的时候,将历史记录存储在磁盘中。一旦系统死机,就可以将命令对象重新加载,并依次调用这些对象的execture()方法。

Walden

Posted on 2015-01-01 | In Life | Comments:

However mean your life is, meet it and live it; do not shun it and call it hard names. It is not so bad as you are. It looks poorest when you are richest. The fault-finder will find faults even in paradise. Love your life, poor as it is. You may perhaps have some pleasant, thrilling, glorious hours, even in a poor-house. The setting sun is reflected from the windows of the almshouse as brightly as from the rich man’s abode; the snow melts before its door as early in the spring. I do not see but a quiet mind may live as contentedly there, and have as cheering thoughts, as in a palace. The town’s poor seem to me often to live the most independent lives of any. Maybe they are simply goreat enough to receive without misgiving. Most think that they are above being supported by the town; but it oftener happens that they are not above supporting themselves by dishonest means, which should be more disreputable. Cultivate poverty like a garden herb, like sage. Do not trouble yourself much to get new things, whether clothes or friends. Turn the old; return to them. Things do not change; we change. Sell your clothes and keep your thoughts. God will see that you do not want society. If I were confined to a corner of a garret all my days, like a spider, the world would be just as large to me while I had my thoughts about me. The philosopher said: “From an army of three divisions one can take away its general, and put it in disorder; from the man the most abject and vulgar one cannot take away his thought.” Do not seek so anxiously to be developed, to subject yourself to many influences to be played on; it is all dissipation. Humility like darkness reveals the heavenly lights. The shadows of poverty and meanness gather around us, “and lo! creation widens to our view.” We are often reminded that if there were bestowed on us the wealth of Croesus, our aims must still be the same, and our means essentially the same. Moreover, if you are restricted in your range by poverty, if you cannot buy books and newspapers, for instance, you are but confined to the most significant and vital experiences; you are compelled to deal with the material which yields the most sugar and the most starch. It is life near the bone where it is sweetest. You are defended from being a trifler. No man loses ever on a lower level by magnanimity on a higher. Superfluous wealth can buy superfluities only. Money is not required to buy one necessary of the soul.

So thoroughly and sincerely are we compelled to live, reverencing our life, and denying the possibility of change. This is the only way, we say; but there are as many ways as there can be drawn radii from one center.
我们被迫生活得如此认真,敬畏自己的生命,拒绝任何可能的改变,我们说,这是唯一的方式;而事实上,经过圆心能画出多少直径,就有多少生活方式。

I wanted to live deep and suck out all the marrow of life, to live so sturdily and Spartan- like as to put to rout all that was not life, to cut a broad swath and shave close, to drive life into a corner, and reduce it to its lowest terms.
我愿意深深地扎入生活,吮尽生活的骨髓,过得扎实,简单,把一切不属于生活的内容剔除得干净利落,把生活逼到绝处,用最基本的形式,简单,简单,再简单。

时间决定你会在生命中遇见谁,你的心决定你想要谁出现在你的生命里,而你的行为决定最后谁能留下。

The finest qualities of our nature, like the bloom on fruits, can be preserved only by the most delicate handling. Yet we do not treat ourselves nor one another thus tenderly.
我们天性中最优秀的品质,就像果实上的霜粉,只有最为精心的对待,才能得以保存下来。然而,我们在对待自己和彼此相处时,却缺少这样的轻柔。

— by Henry David Thoreau

MySQL记录

Posted on 2014-12-23 | In Developing | Comments:

Mac设置mysql命令行

在系统环境加上mysql的路径,关于PATH设置方法可以参考Linux环境变量配置

PATH="$PATH":/usr/local/mysql/bin

修改root密码的几种方法:

  • 登录mysql以后,使用
    set password for 'root'@'localhost' = PASSWORD('***');

  • 用mysqladmin命令
    mysqladmin -u root password "***"

  • 直接update mysql数据库的user表
    update user set Password = PASSWORD('***') where user = 'root';

备份与导入

导出

  • 导出数据库
    mysqldump -u 用户名 -p 数据库名 > 导出的文件名

  • 导出具体表
    mysqldump -u 用户名 -p 数据库名 表名 > 导出的文件名

  • 导出一个数据库结构
    mysqldump -u 用户名 -p 数据库名 -d > 导出的文件名
    -d表示不导出数据

  • 导出具体表结构
    mysqldump -u 用户名 -p -d 数据库名 表名 > 导出的文件名

导入

使用source语句

1
2
3
use aol_log;
set names utf8; #设置字符
source /home/chen/Desktop/logbk.sql;

参考:
Linux 简单的mysql备份和导入,以及文件的备份和导入

Factory Pattern

Posted on 2014-12-12 | In Design Pattern | Comments:

在策略模式曾提到过,我们需要面向接口编程,而不是具体类。但是无法避免的是,我们需要使用new方法来创建对象,这时我们不得不使用具体类。例如:

1
2
3
4
5
6
7
if(picnic) {
duck = new MalldarDuck();
} else if(hunting) {
duck = new DecoyDuck();
} else {
duck = new RubberDuck();
}

这样一旦有变化或扩展,我们需要修改所有这些引用了具体类new对象的代码,造成系统难以维护和修改。代码无法对修改关闭。

同样的,我们应该分离变化的部分。在这里new对象部分是可能发生变化的部分,例如新增具体累,删除具体类,或者构造函数变化等。

Design_pattern_factory_1

简单工厂模式

最直接的方法就是将创建对象移到一个专职创建披萨的对象中,称这个对象为工厂。工厂(factory)处理创建对象的细节,这个如果客户需要使用产品,只需要通过工厂来获得产品直接使用,不需要自己使用具体类来进行创建。创建代码集中到一个工厂类中,也方便集中同一管理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class SimplePizzaFactory {

public Pizza createPizza(String type) {
Pizza pizza = null;

if (type.equals("cheese")) {
pizza = new CheesePizza();
} else if (type.equals("pepperoni")) {
pizza = new PepperoniPizza();
} else if (type.equals("clam")) {
pizza = new ClamPizza();
} else if (type.equals("veggie")) {
pizza = new VeggiePizza();
}
return pizza;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class PizzaStore {
SimplePizzaFactory factory;

public PizzaStore(SimplePizzaFactory factory) {
this.factory = factory;
}

public Pizza orderPizza(String type) {
Pizza pizza;

pizza = factory.createPizza(type);

pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();

return pizza;
}

}

定义简单工厂

上面用到的是简单工厂,其实并不是一个设计模式。因为其只是将问题搬到了另一个对象,当需要新增实现类时,仍然需要修改这个工厂类。但是由于其简单性,在变化有限的情况下仍是很好的选择。

Design_pattern_factory_2

工厂方法模式

在上面的简单工厂模式中,但需要新增产品类时,我们任然需要修改简单工厂类,对修改没有关闭。这里介绍的工厂方法模式可以帮助我们满足开闭原则。

对于工厂方法模式,所有工厂模式都用来封装对象的创建。工厂方法模式(Factory Method Pattern)让子类决定该创建的对象是什么,来达到将对象创建的过程封装的目的。

Design_pattern_factory_3

另一种理解方式是,平行的类层级。

Design_pattern_factory_5

定义工厂方法模式

工厂方法模式定义了一个创建对象的接口,但由其子类决定具体实例化的是哪一个产品。工厂方法让类把实例化推迟到子类。我们选择那个子类工厂,也就是选择了需要获得产品类。

这样当我们需要新增产品时,只需要实现这个产品类(也可是多个产品类,但是同属于一个基类,比如都是pizza),然后实现生产这个产品的工厂类,继承自工厂方法模式。就可以通过这个工厂获得这个新产品,无需修改抽象工厂类和任何其他类的代码。

Design_pattern_factory_6

定义工厂方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public abstract class PizzaStore {

abstract Pizza createPizza(String item);

public Pizza orderPizza(String type) {
Pizza pizza = createPizza(type);
System.out.println("--- Making a " + pizza.getName() + " ---");
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
return pizza;
}
}
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 abstract class Pizza {
String name;
String dough;
String sauce;
ArrayList toppings = new ArrayList();

void prepare() {
System.out.println("Preparing " + name);
System.out.println("Tossing dough...");
System.out.println("Adding sauce...");
System.out.println("Adding toppings: ");
for (int i = 0; i < toppings.size(); i++) {
System.out.println(" " + toppings.get(i));
}
}

void bake() {
System.out.println("Bake for 25 minutes at 350");
}

void cut() {
System.out.println("Cutting the pizza into diagonal slices");
}

void box() {
System.out.println("Place pizza in official PizzaStore box");
}

public String getName() {
return name;
}

public String toString() {
StringBuffer display = new StringBuffer();
display.append("---- " + name + " ----\n");
display.append(dough + "\n");
display.append(sauce + "\n");
for (int i = 0; i < toppings.size(); i++) {
display.append((String )toppings.get(i) + "\n");
}
return display.toString();
}
}
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
public class NYPizzaStore extends PizzaStore {

Pizza createPizza(String item) {
if (item.equals("cheese")) {
return new NYStyleCheesePizza();
} else if (item.equals("veggie")) {
return new NYStyleVeggiePizza();
} else if (item.equals("clam")) {
return new NYStyleClamPizza();
} else if (item.equals("pepperoni")) {
return new NYStylePepperoniPizza();
} else return null;
}
}

public class ChicagoPizzaStore extends PizzaStore {

Pizza createPizza(String item) {
if (item.equals("cheese")) {
return new ChicagoStyleCheesePizza();
} else if (item.equals("veggie")) {
return new ChicagoStyleVeggiePizza();
} else if (item.equals("clam")) {
return new ChicagoStyleClamPizza();
} else if (item.equals("pepperoni")) {
return new ChicagoStylePepperoniPizza();
} else return null;
}
}
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 class ChicagoStylePepperoniPizza extends Pizza {
public ChicagoStylePepperoniPizza() {
name = "Chicago Style Pepperoni Pizza";
dough = "Extra Thick Crust Dough";
sauce = "Plum Tomato Sauce";

toppings.add("Shredded Mozzarella Cheese");
toppings.add("Black Olives");
toppings.add("Spinach");
toppings.add("Eggplant");
toppings.add("Sliced Pepperoni");
}

void cut() {
System.out.println("Cutting the pizza into square slices");
}
}

public class ChicagoStyleCheesePizza extends Pizza {

public ChicagoStyleCheesePizza() {
name = "Chicago Style Deep Dish Cheese Pizza";
dough = "Extra Thick Crust Dough";
sauce = "Plum Tomato Sauce";

toppings.add("Shredded Mozzarella Cheese");
}

void cut() {
System.out.println("Cutting the pizza into square slices");
}
}

public class ChicagoStyleClamPizza extends Pizza {
public ChicagoStyleClamPizza() {
name = "Chicago Style Clam Pizza";
dough = "Extra Thick Crust Dough";
sauce = "Plum Tomato Sauce";

toppings.add("Shredded Mozzarella Cheese");
toppings.add("Frozen Clams from Chesapeake Bay");
}

void cut() {
System.out.println("Cutting the pizza into square slices");
}
}
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 NYStylePepperoniPizza extends Pizza {

public NYStylePepperoniPizza() {
name = "NY Style Pepperoni Pizza";
dough = "Thin Crust Dough";
sauce = "Marinara Sauce";

toppings.add("Grated Reggiano Cheese");
toppings.add("Sliced Pepperoni");
toppings.add("Garlic");
toppings.add("Onion");
toppings.add("Mushrooms");
toppings.add("Red Pepper");
}
}
public class NYStyleCheesePizza extends Pizza {

public NYStyleCheesePizza() {
name = "NY Style Sauce and Cheese Pizza";
dough = "Thin Crust Dough";
sauce = "Marinara Sauce";

toppings.add("Grated Reggiano Cheese");
}
}
public class NYStyleClamPizza extends Pizza {

public NYStyleClamPizza() {
name = "NY Style Clam Pizza";
dough = "Thin Crust Dough";
sauce = "Marinara Sauce";

toppings.add("Grated Reggiano Cheese");
toppings.add("Fresh Clams from Long Island Sound");
}
}

另外可以对比一下,不使用工厂方法的实现,尽管代码上可能没有上面的抽象复杂,但是其在系统变化是非常难以维护。

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 DependentPizzaStore {

public Pizza createPizza(String style, String type) {
Pizza pizza = null;
if (style.equals("NY")) {
if (type.equals("cheese")) {
pizza = new NYStyleCheesePizza();
} else if (type.equals("veggie")) {
pizza = new NYStyleVeggiePizza();
} else if (type.equals("clam")) {
pizza = new NYStyleClamPizza();
} else if (type.equals("pepperoni")) {
pizza = new NYStylePepperoniPizza();
}
} else if (style.equals("Chicago")) {
if (type.equals("cheese")) {
pizza = new ChicagoStyleCheesePizza();
} else if (type.equals("veggie")) {
pizza = new ChicagoStyleVeggiePizza();
} else if (type.equals("clam")) {
pizza = new ChicagoStyleClamPizza();
} else if (type.equals("pepperoni")) {
pizza = new ChicagoStylePepperoniPizza();
}
} else {
System.out.println("Error: invalid type of pizza");
return null;
}
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
return pizza;
}
}

审视对象依赖

当我们直接实例化一个类是,就是在依赖它的具体类。如果把上面的代码依赖画成一张图:

Design_pattern_factory_7

设计原则:
要依赖抽象,不要依赖具体类。

在应用工厂方法后,类图会变成如下。

Design_pattern_factory_8

抽象工厂模式

抽象工厂本质上就是一中工厂方法模式的加强版。在工厂方法中,一个工厂方法抽象了一种基类型产品的建立,并交给具体子类负责创建产品。也就是一个工厂方法负责一类基类型产品创建。在有的时候,会有一系列基类型产品同属于一个产品家族,并且最好由一个具体工厂来创建。抽象工厂模式就提供了这种解决方案。

抽象工厂模式¥提供了一个接口(设计模式中接口即可以指实际的接口,也可指抽象类),用于创建相关或依赖对象的家族,而不需要明确指定具体类。

也就是抽象工厂申明了几个工厂方法,各个工厂方法创建的类型属于同一个产品族。

Design_pattern_factory_9

Design_pattern_factory_10

定义抽象工厂

1
2
3
4
5
6
7
8
9
10
public interface PizzaIngredientFactory {

public Dough createDough();
public Sauce createSauce();
public Cheese createCheese();
public Veggies[] createVeggies();
public Pepperoni createPepperoni();
public Clams createClam();

}
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
public class NYPizzaIngredientFactory implements PizzaIngredientFactory {

public Dough createDough() {
return new ThinCrustDough();
}

public Sauce createSauce() {
return new MarinaraSauce();
}

public Cheese createCheese() {
return new ReggianoCheese();
}

public Veggies[] createVeggies() {
Veggies veggies[] = { new Garlic(), new Onion(), new Mushroom(), new RedPepper() };
return veggies;
}

public Pepperoni createPepperoni() {
return new SlicedPepperoni();
}

public Clams createClam() {
return new FreshClams();
}
}
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
public abstract class Pizza {
String name;

Dough dough;
Sauce sauce;
Veggies veggies[];
Cheese cheese;
Pepperoni pepperoni;
Clams clam;

abstract void prepare();

void bake() {
System.out.println("Bake for 25 minutes at 350");
}

void cut() {
System.out.println("Cutting the pizza into diagonal slices");
}

void box() {
System.out.println("Place pizza in official PizzaStore box");
}

void setName(String name) {
this.name = name;
}

String getName() {
return name;
}

public String toString() {
StringBuffer result = new StringBuffer();
result.append("---- " + name + " ----\n");
if (dough != null) {
result.append(dough);
result.append("\n");
}
if (sauce != null) {
result.append(sauce);
result.append("\n");
}
if (cheese != null) {
result.append(cheese);
result.append("\n");
}
if (veggies != null) {
for (int i = 0; i < veggies.length; i++) {
result.append(veggies[i]);
if (i < veggies.length-1) {
result.append(", ");
}
}
result.append("\n");
}
if (clam != null) {
result.append(clam);
result.append("\n");
}
if (pepperoni != null) {
result.append(pepperoni);
result.append("\n");
}
return result.toString();
}
}
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 CheesePizza extends Pizza {
PizzaIngredientFactory ingredientFactory;

public CheesePizza(PizzaIngredientFactory ingredientFactory) {
this.ingredientFactory = ingredientFactory;
}

void prepare() {
System.out.println("Preparing " + name);
dough = ingredientFactory.createDough();
sauce = ingredientFactory.createSauce();
cheese = ingredientFactory.createCheese();
}
}

public class ClamPizza extends Pizza {
PizzaIngredientFactory ingredientFactory;

public ClamPizza(PizzaIngredientFactory ingredientFactory) {
this.ingredientFactory = ingredientFactory;
}

void prepare() {
System.out.println("Preparing " + name);
dough = ingredientFactory.createDough();
sauce = ingredientFactory.createSauce();
cheese = ingredientFactory.createCheese();
clam = ingredientFactory.createClam();
}
}
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 NYPizzaStore extends PizzaStore {

protected Pizza createPizza(String item) {
Pizza pizza = null;
PizzaIngredientFactory ingredientFactory =
new NYPizzaIngredientFactory();

if (item.equals("cheese")) {

pizza = new CheesePizza(ingredientFactory);
pizza.setName("New York Style Cheese Pizza");

} else if (item.equals("veggie")) {

pizza = new VeggiePizza(ingredientFactory);
pizza.setName("New York Style Veggie Pizza");

} else if (item.equals("clam")) {

pizza = new ClamPizza(ingredientFactory);
pizza.setName("New York Style Clam Pizza");

} else if (item.equals("pepperoni")) {

pizza = new PepperoniPizza(ingredientFactory);
pizza.setName("New York Style Pepperoni Pizza");

}
return pizza;
}
}

比较工厂方法和抽象工厂

Design_pattern_factory_11

Design_pattern_factory_12

System design_Scalability

Posted on 2014-12-12 | In System design | Comments:

Scalability

转自Scalability for Dummies,非常好的System design介绍文章。

Just recently I was asked what it would take to make a web service massively scalable. My answer was lengthy and maybe it is also for other people interesting. So I share it with you here in my blog and split it into parts to make it easier to read. New parts are released on a regular basis. Have fun and your comments are always welcomed!

Part 1: Clones

Public servers of a scalable web service are hidden behind a load balancer. This load balancer evenly distributes load (requests from your users) onto your group/cluster of application servers. That means that if, for example, user Steve interacts with your service, he may be served at his first request by server 2, then with his second request by server 9 and then maybe again by server 2 on his third request.

Steve should always get the same results of his request back, independent what server he “landed on”. That leads to the first golden rule for scalability: every server contains exactly the same codebase and does not store any user-related data, like sessions or profile pictures, on local disc or memory.

Sessions need to be stored in a centralized data store which is accessible to all your application servers. It can be an external database or an external persistent cache, like Redis. An external persistent cache will have better performance than an external database. By external I mean that the data store does not reside on the application servers. Instead, it is somewhere in or near the data center of your application servers.

But what about deployment? How can you make sure that a code change is sent to all your servers without one server still serving old code? This tricky problem is fortunately already solved by the great tool Capistrano. It requires some learning, especially if you are not into Ruby on Rails, but it is definitely both the effort.

After “outsourcing” your sessions and serving the same codebase from all your servers, you can now create an image file from one of these servers (AWS calls this AMI - Amazon Machine Image.) Use this AMI as a “super-clone” that all your new instances are based upon. Whenever you start a new instance/clone, just do an initial deployment of your latest code and you are ready!

Part 2: Database

After following Part 1 of this series, your servers can now horizontally scale and you can already serve thousands of concurrent requests. But somewhere down the road your application gets slower and slower and finally breaks down. The reason: your database. It’s MySQL, isn’t it?

Now the required changes are more radical than just adding more cloned servers and may even require some boldness. In the end, you can choose from 2 paths:

Path #1 is to stick with MySQL and keep the “beast” running. Hire a database administrator (DBA,) tell him to do master-slave replication (read from slaves, write to master) and upgrade your master server by adding RAM, RAM and more RAM. In some months, your DBA will come up with words like “sharding”, “denormalization” and “SQL tuning” and will look worried about the necessary overtime during the next weeks. At that point every new action to keep your database running will be more expensive and time consuming than the previous one. You might have been better off if you had chosen Path #2 while your dataset was still small and easy to migrate.

**Path #2 means to denormalize right from the beginning and include no more Joins in any database query. You can stay with MySQL, and use it like a NoSQL database, or you can switch to a better and easier to scale NoSQL database like MongoDB or CouchDB. Joins will now need to be done in your application code. The sooner you do this step the less code you will have to change in the future. But even if you successfully switch to the latest and greatest NoSQL database and let your app do the dataset-joins, soon your database requests will again be slower and slower. You will need to introduce a cache.

But even if successfully switched the latest and greatest NoSQL database and let your app do the dataset-joins, soon your database requests will be again slower and slower. You will need to introduce a cache.

Part 3: Cache

After following Part 2 of this series, you now have a scalable database solution. You have no fear of storing terabytes anymore and the world is looking fine. But just for you. Your users still have to suffer slow page requests when a lot of data is fetched from the database. The solution is the implementation of a cache.

With “cache” I always mean in-memory caches like Memcached or Redis. Please never do file-based caching, it makes cloning and auto-scaling of your servers just a pain.

But back to in-memory caches. A cache is a simple key-value store and it should reside as a buffering layer between your application and your data storage. Whenever your application has to read data it should at first try to retrieve the data from your cache. Only if it’s not in the cache should it then try to get the data from the main data source. Why should you do that? Because a cache is lightning-fast. It holds every dataset in RAM and requests are handled as fast as technically possible. For example, Redis can do several hundreds of thousands of read operations per second when being hosted on a standard server. Also writes, especially increments, are very, very fast. Try that with a database!

There are 2 patterns of caching your data. An old one and a new one:

#1 - Cached Database Queries
That’s still the most commonly used caching pattern. Whenever you do a query to your database, you store the result dataset in cache. A hashed version of your query is the cache key. The next time you run the query, you first check if it is already in the cache. The next time you run the query, you check at first the cache if there is already a result. This pattern has several issues. The main issue is the expiration. It is hard to delete a cached result when you cache a complex query (who has not?). When one piece of data changes (for example a table cell) you need to delete all cached queries who may include that table cell. You get the point?

#2 - Cached Objects
That’s my strong recommendation and I always prefer this pattern. In general, see your data as an object like you already do in your code (classes, instances, etc.). Let your class assemble a dataset from your database and then store the complete instance of the class or the assembed dataset in the cache. Sounds theoretical, I know, but just look how you normally code. You have, for example, a class called “Product” which has a property called “data”. It is an array containing prices, texts, pictures, and customer reviews of your product. The property “data” is filled by several methods in the class doing several database requests which are hard to cache, since many things relate to each other. Now, do the following: when your class has finished the “assembling” of the data array, directly store the data array, or better yet the complete instance of the class, in the cache! This allows you to easily get rid of the object whenever something did change and makes the overall operation of your code faster and more logical.

And the best part: it makes asynchronous processing possible! Just imagine an army of worker servers who assemble your objects for you! The application just consumes the latest cached object and nearly never touches the databases anymore!

Some ideas of objects to cache:

user sessions (never use the database!)
fully rendered blog articles
activity streams
user<->friend relationships
As you maybe already realized, I am a huge fan of caching. It is easy to understand, very simple to implement and the result is always breathtaking. In general, I am more a friend of Redis than Memcached, because I love the extra database-features of Redis like persistence and the built-in data structures like lists and sets. With Redis and a clever key’ing there may be a chance that you even can get completly rid of a database. But if you just need to cache, take Memcached, because it scales like a charm.

Happy caching!

Part 4: Asynchronism

This 4th part of the series starts with a picture: please imagine that you want to buy bread at your favorite bakery. So you go into the bakery, ask for a loaf of bread, but there is no bread there! Instead, you are asked to come back in 2 hours when your ordered bread is ready. That’s annoying, isn’t it?

To avoid such a “please wait a while” - situation, asynchronism needs to be done. And what’s good for a bakery, is maybe also good for your web service or web app.
In general, there are two ways / paradigms asynchronism can be done.

Async #1
Let’s stay in the former bakery picture. The first way of async processing is the “bake the breads at night and sell them in the morning” way. No waiting time at the cash register and a happy customer. Referring to a web app this means doing the time-consuming work in advance and serving the finished work with a low request time.

Very often this paradigm is used to turn dynamic content into static content. Pages of a website, maybe built with a massive framework or CMS, are pre-rendered and locally stored as static HTML files on every change. Often these computing tasks are done on a regular basis, maybe by a script which is called every hour by a cronjob. This pre-computing of overall general data can extremely improve websites and web apps and makes them very scalable and performant. Just imagine the scalability of your website if the script would upload these pre-rendered HTML pages to AWS S3 or Cloudfront or another Content Delivery Network! Your website would be super responsive and could handle millions of visitors per hour!

Async #2
Back to the bakery. Unfortunately, sometimes customers has special requests like a birthday cake with “Happy Birthday, Steve!” on it. The bakery can not foresee these kind of customer wishes, so it must start the task when the customer is in the bakery and tell him to come back at the next day. Refering to a web service that means to handle tasks asynchronously.

Here is a typical workflow:

A user comes to your website and starts a very computing intensive task which would take several minutes to finish. So the frontend of your website sends a job onto a job queue and immediately signals back to the user: your job is in work, please continue to the browse the page. The job queue is constantly checked by a bunch of workers for new jobs. If there is a new job then the worker does the job and after some minutes sends a signal that the job was done. The frontend, which constantly checks for new “job is done” - signals, sees that the job was done and informs the user about it. I know, that was a very simplified example.

If you now want to dive more into the details and actual technical design, I recommend you take a look at the first 3 tutorials on the RabbitMQ website. RabbitMQ is one of many systems which help to implement async processing. You could also use ActiveMQ or a simple Redis list. The basic idea is to have a queue of tasks or jobs that a worker can process. Asynchronism seems complicated, but it is definitely worth your time to learn about it and implement it yourself. Backends become nearly infinitely scalable and frontends become snappy which is good for the overall user experience.

If you do something time-consuming, try to do it always asynchronously.

1…101112…17
Plusaber

Plusaber

Plusaber's Blog
82 posts
12 categories
22 tags
Links
  • LinkedIn
  • Indeed
  • Baito
  • Kaggle
© 2014 – 2019 Plusaber
Powered by Hexo v3.8.0
|
Theme – NexT.Mist v7.1.1