guojh's Blog.

设计模式笔记(上)

字数统计: 4.1k阅读时长: 17 min
2019/06/23

设计模式笔记(上)

代码设计很复杂,但正是因为有了设计模式,才能更简单地理解、描述和解决一个复杂的问题。个人学习设计模式笔记。主要参考《Head First设计模式》和Wikipedia,youtube上有个系列视频也非常值得一看

chap 1 设计模式入门—策略模式

定义

策略模式 Strategy Pattern 定义了算法簇,分别封装起来,让它们之间可以互相替换,此模式让算法的变化独立于使用算法的客户。

分析

  • 环境(Context):持有一个Strategy的引用
  • 抽象策略(Strategy):这是一个抽象角色,通常由一个接口或抽象类实现。此角色给出所有的具体策略类所需的接口
  • 具体策略(ConcreteStrategy):包装了相关的算法或行为

利用继承设计子类行为,是在编译时静态决定,而且继承的行为相同;而利用组合可扩展对象的行为,就可在运行时动态地进行扩展。

策略模式=组合composition+委托delegation,实现运行时具有继承行为

设计原则

  • 找出应用中可能需要变化之处,把它们独立出来,不要和那些不需要变化的代码混在一起
  • 针对接口编程,而不是针对实现编程
  • 多用组合,少用继承

模式是针对设计问题的通用解决方案,大多数模式都允许局部改变独立于其他部分。

UML

https://en.wikipedia.org/wiki/Strategy_pattern

代码

下面代码中Duck也可以设计为基类,为了简便这里简化了。

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
#include <iostream>
#include <memory>

// strategy
class FlyBehavior {
public:
typedef std::shared_ptr<FlyBehavior> Ptr;
virtual void fly()=0;
virtual ~FlyBehavior(){};
};

// concrete strategy
class FlyWithWings:public FlyBehavior{
public:
void fly() override {
std::cout<<"I am flying\n";
}
};

class FlyNoWay:public FlyBehavior{
public:
void fly() override {
std::cout<<"I can't fly\n";
}
};

// context
class Duck{
public:
Duck(const FlyBehavior::Ptr &flyBehavior) : flyBehavior_(flyBehavior) {}
void performFly(){flyBehavior_->fly();}
private:
FlyBehavior::Ptr flyBehavior_;
};

// test
int main(){
FlyBehavior::Ptr flyBehavior(new FlyNoWay);
Duck duck(flyBehavior);
duck.performFly();

return 0;
}

chap2 让你的对象知悉现况—观察者模式

定义

观察者模式 Observer Pattern 定义了对象之间的一对多依赖,这样一来,当一个对象改变状态时,它的所有依赖者都会收到通知并自动更新。

分析

  • 主题(Subject):一般为接口或类,存储所有观察者的引用。使用一个共同的接口来更新观察者。叫talker,publisher或许更贴切;
  • 观察者(Observer):设置为接口,每个实体观察者需要重写接口函数。叫listener,subscriber或许更贴切;
  • 具体观察者(Concrete Observer):实现观察者接口。

观察者和主题之间是松耦合:主题不知道观察者的细节,只知道观察者实现了观察者接口。

从publisher获取数据时,可以通过推push或拉pull的方式获取数据,推似乎更常见。

有多个subscriber时,不可以依赖特定的通知顺序。

许多GUI框架经常使用观察者模式,如Android中控件的监听事件函数。这里本质是使用回调机制实现的:publisher触发某种事件后,调用回调函数;subscriber在回调函数中实现相应自定义的功能。在调用回调函数时,可以将参数传递,也可以参数为空。

设计原则

  • 为了交互对象之间的松耦合设计而努力

小技巧:可以通过setChanged()方法标记状态已经改变,然后再通知观察者,这样更有弹性,可以控制通知的时机,比如可以用来避免频繁通知观察者。

UML

https://en.wikipedia.org/wiki/Observer_pattern

代码

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
#include <iostream>
#include <memory>
#include <vector>

// observer
class Observer {
public:
typedef std::shared_ptr<Observer> Ptr;
virtual void update(std::string event)=0;
virtual ~Observer(){};
};

// concrete observer
class ObserverOne:public Observer{
public:
void update(std::string event) override {
std::cout<<"Observer One received response: "+event<<std::endl;
}
};

class ObserverTwo:public Observer{
public:
void update(std::string event) override {
std::cout<<"Observer Two received response: "+event<<std::endl;
}
};

// subject
class EventSource {
public:
void addObserver(const Observer::Ptr& observer){observers_.push_back(observer);}
void scanInput(){
std::string input;
while (std::getline(std::cin,input)){
notifyObserver(input);
}
}
private:
void notifyObserver(std::string event){
for(auto item:observers_)
item->update(event);
}

std::vector<Observer::Ptr> observers_;
};

// test
int main(){
Observer::Ptr observer1(new ObserverOne);
Observer::Ptr observer2(new ObserverTwo);

EventSource eventSource;
eventSource.addObserver(observer1);
eventSource.addObserver(observer2);
eventSource.scanInput();

return 0;
}

chap3 装饰对象—装饰者模式

定义

装饰者模式Decorator Pattern 动态地将责任附加到对象上,若要有扩展功能,装饰者提供了比继承更有弹性的替代方案。

分析

  • 装饰者(Decorator):继承自组件类,包含组件引用以达到包装wrap目的,通过该引用可以调用装饰之前组件对象的方法(即转发forward),并重写一些需要修改需求的方法,以达到装饰的目的
  • 组件(Component):为抽象类,是具体构件和抽象装饰类的共同父类

因为装饰者必须能够取代被装饰者(组件),因此继承在这里的作用是达到"类型匹配",而不是利用继承去获得"行为"。行为来自装饰者和基础组件,或与其他装饰者之间的组合关系。

许多Java I/O相关类使用的就是装饰者模式,如FilterInputStream等。可以用许多装饰者包装一个组件,但这样也会造成出现许多小对象,使程序结构变得复杂。

设计原则

  • 对扩展开放,对修改关闭

转发forward委托delegation二者比较相近,但有区别:

  • 转发

下面是一个Java中例子,来自Wikipedia。Printer包含一个print方法,但是该方法自己没有具体实现,而是把责任转发给了RealPrinter的一个对象。在外界看来,好像是Printer输出了字符串,但实际是RealPrinter做了实际的工作。

划重点:转发即把锅甩给别人

应用:Chain-of-responsibility pattern、Decorator pattern、Proxy pattern等

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class RealPrinter {	// the "receiver" (wrappee)
void print() {
System.out.println("Hello world!");
}
}

class Printer { // the "sender" (wrapper)
RealPrinter p = new RealPrinter(); // create the receiver
void print() {
p.print(); // calls the receiver
}
}

public class Main {
public static void main(String[] arguments) {
// to the outside world it looks like Printer actually prints.
Printer printer = new Printer();
printer.print();
}
}
  • 委托

委托和转发比较像,但有一些区别。委托中,self指代sender,而转发中,self指代receiver。或者,按照SO上的一个回答,委托算是一种特殊的转发,只不过转发给了接口本身,也就是策略模式中的做法。

UML

https://en.wikipedia.org/wiki/Decorator_pattern

代码

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
#include <iostream>
#include <string>

// abstract component (decorator)
class Beverage{
public:
virtual double cost()=0;
virtual ~Beverage() {}
};

// concrete component
class Espresso:public Beverage{
public:
double cost() override {
return 1.99;
}
};

// concrete decorator
class Mocha:public Beverage{
public:
Mocha(Beverage *beverage) : beverage_(beverage) {}

double cost() override {
return beverage_->cost()+.20;
}

private:
Beverage* beverage_;
};

int main() {
Espresso coffee;
std::cout<<"original coffee: "<<coffee.cost()<<std::endl;

Beverage* coffee_with_mocha=new Mocha(&coffee);
coffee_with_mocha=new Mocha(coffee_with_mocha);
std::cout<<"coffee with double mocha: "<<coffee_with_mocha->cost()<<std::endl;

return 0;
}

chap 4 烘烤松耦合OO设计—工厂模式

书中分为了三种,即简单工厂模式、工厂方法模式和抽象工厂模式。

本节UML请参考书上

简单工厂模式

简单工厂模式其实并不算是一种设计模式,更多的时候是一种编程习惯。

定义

定义一个工厂类,根据传入的参数不同返回不同的实例,被创建的实例具有共同的父类或接口。

分析

根据single responsibility原则,将对象创建工作单独分离出来一个类来做,这个类即工厂类,工厂类把全部的创建工作在一个地方处理完了。

代码

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
#include <iostream>
#include <memory>

// product
class Shape{
public:
typedef std::shared_ptr<Shape> Ptr;
virtual void draw()=0;
virtual ~Shape() {}
};

// concrete product
class Circle:public Shape{
public:
void draw() override {
std::cout<<"draw: circle\n";
}
};

class Rectangle:public Shape{
public:
void draw() override {
std::cout<<"draw: rectangle\n";
}
};

// concrete factory
class ShapeFactory{
public:
Shape::Ptr createShape(std::string type){
Shape::Ptr shape;
if (type=="circle")
shape=Shape::Ptr(new Circle);
else if (type=="rect")
shape=Shape::Ptr(new Rectangle);

return shape;
}
};

// test
int main(){
ShapeFactory shapeFactory;
Shape::Ptr circle=shapeFactory.createShape("circle");
circle->draw();
Shape::Ptr rect=shapeFactory.createShape("rect");
rect->draw();

return 0;
}

工厂方法模式

通常所说的工厂模式指的是这种模式,工厂方法模式是日常开发中使用频率最高的一种设计模式。

定义

定义一个用于创建对象的接口,让子类决定将哪一个类实例化。工厂方法模式让一个类的实例化延迟到其子类。

分析

工厂方法模式,又叫多态工厂模式。从这个名字,就可以看出其特点。简单工厂只有一个统一的工厂类,所有创建工作在一个地方都处理完了;而工厂方法是有许多工厂类,这些工厂类都实现了一个工厂基类。每一个工厂子类看起来就像简单工厂一样。

代码

下面代码中,每个工厂类对应了一个product创建,也可以对应多个product创建(这时可能需要在构造时传入参数等,像简单工厂模式那样)。注意将下面代码和简单工厂代码对比。

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
#include <iostream>
#include <memory>

// product
class Shape{
public:
typedef std::shared_ptr<Shape> Ptr;
virtual void draw()=0;
virtual ~Shape() {}
};

// concrete product
class Circle:public Shape{
public:
void draw() override {
std::cout<<"draw: circle\n";
}
};

class Rectangle:public Shape{
public:
void draw() override {
std::cout<<"draw: rectangle\n";
}
};

// factory
class Factory{
public:
typedef std::shared_ptr<Factory> Ptr;
virtual Shape::Ptr createShape()=0;
virtual ~Factory() {}
};

// concrete factory
class CircleFactory:public Factory{
public:
Shape::Ptr createShape() override {
return Shape::Ptr(new Circle);
}
};

class RectFactory:public Factory{
public:
Shape::Ptr createShape() override {
return Shape::Ptr(new Rectangle);
}
};

// test
int main(){
Factory::Ptr factory(new CircleFactory);
Shape::Ptr circle=factory->createShape();
circle->draw();

Factory::Ptr factory2(new RectFactory);
Shape::Ptr rect=factory2->createShape();
rect->draw();

return 0;
}

抽象工厂模式

定义

提供一个接口,用于创建相关或依赖对象的家族,而不需要明确指定具体类。( 在抽象工厂模式中,每一个具体工厂都提供了多个工厂方法用于产生多种不同类型的对象)

分析

作用:把搭配的一系列对象捆绑在一起生产,比如下面代码中的例子。

角色:

  • AbstractFactory(抽象工厂):声明了一组用于创建对象的方法,注意是一组对象,而且这一组对象是搭配match的;
  • ConcreteFactory(具体工厂):它实现了在抽象工厂中声明的创建对象的方法,生成一组具体对象;
  • AbstractProduct(抽象产品):它为每种对象声明接口,在其中声明了对象所具有的业务方法;
  • ConcreteProduct(具体产品):它定义具体工厂生产的具体对象。

抽象工厂的每个方法实际上看像是工厂方法:每个方法都被声明为抽象,而工厂子类的方法覆盖这些方法来创建某些对象。

工厂方法每个只能创建"一个产品",而抽象工厂可以创建"一个家族产品"。

但该模式不符合开闭原则,如果加入新的产品就必须改变接口。

代码

抽象工厂模式擅长把搭配的一系列对象捆绑在一起生产,比如下面的例子。如果在生产UI控件时,把Android风格的Text控件和IOS风格的Button控件混在一起了,那将十分糟糕。而使用抽象工厂模式,可以看到可以创建相关或依赖对象的家族,从而避免了把不搭的对象一起生产。youtube那个系列视频的这一节讲的很清楚,可以参考下。

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
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
#include <iostream>
#include <memory>

// product
// some ui widgets
class Text{
public:
typedef std::shared_ptr<Text> Ptr;
virtual void showText()=0;
virtual ~Text() {}
};

class Button{
public:
typedef std::shared_ptr<Button> Ptr;
virtual void showButton()=0;
virtual ~Button() {}
};

// concrete product
// different styled ui widgets
class TextAndroid:public Text{
public:
void showText() override {
std::cout<<"Text in Android\n";
}
};

class TextIOS:public Text{
public:
void showText() override {
std::cout<<"Text in IOS\n";
}
};

class ButtonAndroid:public Button{
public:
void showButton() override {
std::cout<<"Button in Android\n";
}
};

class ButtonIOS:public Button{
public:
void showButton() override {
std::cout<<"Button in IOS\n";
}
};

// factory
class UIFactory{
public:
typedef std::shared_ptr<UIFactory> Ptr;
virtual Text::Ptr createText()=0;
virtual Button::Ptr createButton()=0;
virtual ~UIFactory() {}
};

// concrete factory
// this are two versions of ui factory for two different platforms
class AndroidUIFactory:public UIFactory{
public:
Text::Ptr createText() override {
return Text::Ptr(new TextAndroid);
}

Button::Ptr createButton() override {
return Button::Ptr(new ButtonAndroid);
}
};

class IOSUIFactory:public UIFactory{
public:
Text::Ptr createText() override {
return Text::Ptr(new TextIOS);
}

Button::Ptr createButton() override {
return Button::Ptr(new ButtonIOS);
}
};

// test
// suppose I now want to create an Android app
int main(){
UIFactory::Ptr uiFactory(new AndroidUIFactory);
Text::Ptr text=uiFactory->createText();
Button::Ptr button=uiFactory->createButton();
text->showText();
button->showButton();

return 0;
}

chap5 独一无二的对象—单例模式

定义

单例模式Singleton Pattrn 确保一个类只有一个实例,并提供一个全局访问点。

分析

一个static变量,一个私有构造函数,一个static方法(getInstance())。

如果考虑多线程,必须进行一些改变,否则可能多个线程同时进入全局访问点中,从而创造多个对象。Java中,改进方式有三种:

  • 同步getInstance()方法。缺点是效率较低;
  • 恶汉模式;
  • double-checked。首先检查实例是否已创建,如果尚未创建,才进行同步。

UML

https://en.wikipedia.org/wiki/Singleton_pattern

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Singleton {
public:
static Singleton &getInstance() {
static Singleton singleton;
return singleton;
}

Singleton(const Singleton &) = delete;

Singleton &operator=(const Singleton &) = delete;

private:
Singleton() {}
};

chap6 封装调用—命令模式

定义

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

分析

  • 命令Command:具体命令类的抽象接口;
  • 具体命令Concrete Command:打包运算块(一个接收者和一组动作);
  • 接收者Receiver:负责调用命令对象执行请求;
  • 请求者Invoker:以委托的形式调用命令。

一个命令对象(ConcreteCommand)通过在特定接收者上绑定一组动作封装一个请求。即命令对象将一组动作和一个接收者打包进一个对象中,这个对象只暴露出一个execute()方法,此方法被调用时,接收者就会进行这些动作。这个打包后的对象就像一般的对象一样,可以被存储、传递和调用

调用者可接受命令对象当做参数,甚至在运行时动态地进行。

命令支持撤销,做法是实现一个undo()方法来回到execute()方法执行前的状态。

宏命令是命令的一种简单延伸,允许调用多个命令。也支持撤销。

命令模式可以用来实现线程池、工作队列和日志请求等。如Java中常见的开启新线程就是使用命令模式。其中Runnable接口即Command,new生成的匿名类即Concrete Command,Thread即Invoker,Receiver省略了,具体的逻辑直接放在了Concrete Command中。

1
2
3
4
5
6
new Thread(new Runnable(){
@Override
public void run(){
// code goes here
}
}).start();

UML

https://en.wikipedia.org/wiki/Command_pattern

代码

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
#include <iostream>
#include <memory>

// receiver
class Light{
public:
typedef std::shared_ptr<Light> Ptr;
void on(){std::cout<<"Light is on\n";}
void off(){std::cout<<"Light is off\n";}
};

// command
class Command{
public:
typedef std::shared_ptr<Command> Ptr;
virtual void execute()=0;
virtual ~Command() {}
};

// concrete command
class LightOnCommand:public Command{
public:
LightOnCommand(const Light::Ptr &light) : light_(light) {}

void execute() override {
light_->on();
}

private:
Light::Ptr light_;
};

// invoker
class Remote{
public:
Remote() {}
void setCommand(Command::Ptr command){command_=command;}
void onButtonWasPressed(){command_->execute();}

private:
Command::Ptr command_;
};

// test
int main(){
Light::Ptr light(new Light);
Command::Ptr lightOn(new LightOnCommand(light));

Remote remote;
remote.setCommand(lightOn);
remote.onButtonWasPressed();

return 0;
}

个人理解错误的地方还请不吝赐教,转载请标明出处

CATALOG
  1. 1. 设计模式笔记(上)
    1. 1.1. chap 1 设计模式入门—策略模式
      1. 1.1.1. 定义
      2. 1.1.2. 分析
      3. 1.1.3. UML
      4. 1.1.4. 代码
    2. 1.2. chap2 让你的对象知悉现况—观察者模式
      1. 1.2.1. 定义
      2. 1.2.2. 分析
      3. 1.2.3. UML
      4. 1.2.4. 代码
    3. 1.3. chap3 装饰对象—装饰者模式
      1. 1.3.1. 定义
      2. 1.3.2. 分析
      3. 1.3.3. UML
      4. 1.3.4. 代码
    4. 1.4. chap 4 烘烤松耦合OO设计—工厂模式
      1. 1.4.1. 简单工厂模式
        1. 1.4.1.1. 定义
        2. 1.4.1.2. 分析
        3. 1.4.1.3. 代码
      2. 1.4.2. 工厂方法模式
        1. 1.4.2.1. 定义
        2. 1.4.2.2. 分析
        3. 1.4.2.3. 代码
      3. 1.4.3. 抽象工厂模式
        1. 1.4.3.1. 定义
        2. 1.4.3.2. 分析
        3. 1.4.3.3. 代码
    5. 1.5. chap5 独一无二的对象—单例模式
      1. 1.5.1. 定义
      2. 1.5.2. 分析
      3. 1.5.3. UML
      4. 1.5.4. 代码
    6. 1.6. chap6 封装调用—命令模式
      1. 1.6.1. 定义
      2. 1.6.2. 分析
      3. 1.6.3. UML
      4. 1.6.4. 代码