直觉地用计算机的方式去思考是初学者常见的问题

活字印刷

  • 可维护:只更改需要更改的字
  • 可复用:在后来的印刷中重复使用
  • 可扩展:另外加字
  • 高灵活:排版方式不同

使用封装、继承、多态,降低程序耦合。

业务封装

将业务和界面分离:Web、Windows、Linux平台下的计算机均可使用Operation类。只需重写界面即可。

松耦合

构建一个基类Operation,其他运算继承此基类,并重写其虚方法。如此可更容易地添加新运算。

简单工厂模式

用一个单独的类来进行创造实例的活动

1
2
3
4
oper = OperationFactory.create_pperate('+')
oper.num1 = 1
oper.num2 = 1
result = oper.get_result()

如果需要修改加法,只需修正class OperationAdd,增加其他运算只需添加对应的子类并修改工厂。

UML简介

  • 接口实现:棒棒糖表示法
  • 关联:一个类"知晓"另一个类,可用实线箭头表示
  • 聚合:一种"弱拥有",A可包含B对象,但B对象不是A对象的一部分(如雁群与大雁),用空菱形(集体)箭头(个体)
  • 合成:强拥有关系,严格的部分与整体(如鸟与翅膀)
  • 依赖:虚线箭头表示,如动物与氧气

Design Pattern Guru

工厂模式的核心在于,"工厂"不单单只是一个制造"产品"的类,而是包含了大量业务逻辑的类。对工厂而言,产品总是实现相同的接口,并且业务逻辑相似。比如对于卡车和船两类产品而言,它们的"用法"都是相同的,都需要载人、启动、运输、停止、下人这四个步骤。因此只需要在产品内实现这些步骤的具体细节就可以了。如果想创造一个新交通工具,只需要重写这四个方法,就能无缝运行。

换言之,业务逻辑在工厂,而产品实现每个步骤的具体细节。

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
#include <functional>
#include <iostream>
#include <stdexcept>

class Dialog {
public:
Dialog() : btnOk(createButton()) {}
virtual ~Dialog() {delete btnOk;}

// createButton是一个工厂方法
// const表示该方法不修改类属性,保证“常量化”
// =0表示这是一个纯虚方法,子类必须重写该方法
virtual Button* createButton() const = 0;
virtual void render() {
btnOk -> onClick([](){ std::cout << "Button clicked!" << std::endl; });
btnOk -> render();
}
private:
Button* btnOk;
};

class WindowsDialog: public Dialog {
public:
Button* createButton() const override { // override表示重写了基类虚函数
return new WindowsButton();
}
};

class WebDialog: public Dialog {
public:
Button* createButton() const override {
return new WebButton();
}
};

class Button { // 两个纯虚函数允许多态行为
public:
virtual void onClick(std::function<void()> callback) const = 0;
virtual void render() const = 0;
};

class WindowsButton: public Button {
public:
void onClick(std::function<void()> callback) const {
// do some windows thing
}
void render() const {
// do some windows thing
}
};

class WebButton: public Button {
public:
void onClick(std::function<void()> callback) const {
// do some web thing
}
void render() const {
// do some web thing
}
};

class Application {
public:
Application() dialog(nullptr) { // 在这里初始化dialog为空指针
Config config = readConfigFile();
if (config.OS == "Win") dialog = new WindowsDialog();
else if (config.OS == "Web") dialog = new WebDialog();
else throw std::runtime_error("invalid config.OS");
}
~Application() {delete dialog;}

void run() {
if (dialog) dialog -> render();
}
private:
Dialog* dialog;
};

C++这种静态类型语言的多态与Python这种动态类型有重要差异:对C++而言,多态是依靠某一个共同的基类实现的,例如基类Base同时被A和B类继承。某一个函数若想同时处理AB两类,就需要把函数声明为ReturnType func(Base& abClass)。但对于Python而言,可以直接写成def func(ab_class)。Python的这种行为更加隐式、自然。所谓_Duck type_,是指如果一个对象实现了所需的方法或属性,那么它就可以被用在任何期望这些方法或属性的地方,而不管它的实际类型是什么。

此外虚函数是C++多态的重要实现部分。由于多态必须通过基类实现,因此对于重写的虚函数,即使使用基类指针或引用,调用的也是重写版本。非虚方法也可以重写,但是无法体现多态。使用基类指针或引用调用方法时,指向的是重写前的缺省方法。这可能导致非预期的结果。