这几天从图书馆借了借来了《设计模式》,花了几个小时大致浏览了一下。当然了,在此之前我已经拜读过王垠的文章 。我的观点和他基本相同,不过既然是要谈谈还是详细点说吧。
创建型模式,简单点解释,就是根据输入创建不同的子类。唯一值得一提的是单例模式(Singleton pattern)。
结构型模式,说的就是接口的设计。其实道理很简单,尽量少暴露类型内部的实现,让接口保持功能简洁实用,包括一些行为模式也是这个道理。
行为模式倒是有几个值得稍微细说。
职责链模式(Chain of responsibility):每个子类都有一个后继,如果当前子类的功能无法处理得到的输入,则将输入传给其后继,直到链的尾部。说白了就是一个链表而已,当然了1994年数据结构并不如今天这么普及。如果换成其他数据结构例如函数数组效率会更高。
迭代器模式(Iterator):如今的各种语言标准库里的类型如链表,哈希表,红黑树,数组,字符串都是体现。如C++/STL里的Iterator,C#里的IEnumerable/IEnumerator。
观察者模式(Observer):将消息订阅者(Subscriber)统一放入一个列表中,事件发生后将消息传递给列表中的所有函数,处理该消息。C#中的委托链就是这么个东西。
策略模式(Strategy):就是把一个函数当作参数传入另一个函数,书中的做法是将一个对象传入函数,再调用对象中的函数。不过如今各种语言里头lambda到处都是,书中的做法已经过时。关于这点,可以多说几句,创建型模式中的很多做法以及命令模式(Command)其实都是输入消息=>函数处理的映射。然而很多时候我们可以直接传入需要处理的函数,甚至都省去了传入消息和映射的步骤。
解释器模式(Interpreter):就是写一个解释器,往简单了说不就是树的遍历?当然了不同解释器写起来难度千差万别,哪里是一个pattern就能说的了的?
访问者模式(Visitor):全书中唯一有技术含量的部分。因为面向对象语言中没有模式匹配,所以被迫用visitor pattern来达到multiple dispatch的目的。
以下是《C++思想》中multiple dispatch的实现:
using namespace std;
class Paper;
class Scissors;
class Rock;
enum Outcome { win, lose, draw };
ostream& operator<<(ostream& os, constOutcome out) {
switch(out) {
casewin: return os << "win";
caselose: return os << "lose";
casedraw: return os << "draw";
}
}
class Item {
public:
virtual Outcome compete(const Item*) = 0;
virtual Outcome eval(const Paper*) const = 0;
virtual Outcome eval(const Scissors*) const=0;
virtual Outcome eval(const Rock*) const = 0;
virtual ostream& print(ostream& os)const = 0;
virtual ~Item() {}
friend ostream& operator<<(ostream& os, const Item*it) {
return it->print(os);
}
};
class Paper : public Item {
public:
Outcome compete(const Item* it) {
return it->eval(this);
}
Outcome eval(const Paper*) const {
return draw;
}
Outcome eval(const Scissors*) const {
return win;
}
Outcome eval(const Rock*) const {
return lose;
}
ostream& print(ostream& os) const {
return os << "Paper ";
}
};
class Scissors : public Item {
public:
Outcome compete(const Item* it) {
return it->eval(this);
}
Outcome eval(const Paper*) const {
return lose;
}
Outcome eval(const Scissors*) const {
return draw;
}
Outcome eval(const Rock*) const {
return win;
}
ostream& print(ostream& os) const {
return os << "Scissors";
}
};
class Rock : public Item {
public:
Outcome compete(const Item* it) {
return it->eval(this);
}
Outcome eval(const Paper*) const {
return win;
}
Outcome eval(const Scissors*) const {
return lose;
}
Outcome eval(const Rock*) const {
return draw;
}
ostream& print(ostream& os) const {
return os << "Rock ";
}
};
struct Compete {
Outcome operator()(Item* a, Item* b) {
cout<< a << "\t" << b << "\t";
return a->compete(b);
}
};
如果使用visitor pattern代码如下:
using namespace std;
class Paper;
class Scissors;
class Rock;
enum Outcome { win, lose, draw };
ostream&
operator<<(ostream& os, constOutcome out) {
switch(out) {
default:
casewin: return os << "win";
caselose: return os << "lose";
casedraw: return os << "draw";
}
}
class Item {
public:
virtual Outcome compete(const Item*) = 0;
virtual ostream& print(ostream& os)const = 0;
virtual ~Item() {}
friend ostream&
operator<<(ostream& os, const Item*it) {
return it->print(os);
}
};
class Visitor{
public:
virtualOutcome evalPaper () const = 0;
virtual Outcome evalScissors () const= 0;
virtual Outcome evalRock() const = 0;
virtual~Visitor () {
};
class Paper : public Item, public Visitor {
public:
Outcome compete(const Visitor* it) {
return it->evalPaper();
}
Outcome evalPaper() const {
return draw;
}
Outcome evalScissors() const {
return win;
}
Outcome evalRock() const {
return lose;
}
ostream& print(ostream& os) const {
return os << "Paper ";
}
};
class Scissors : public Item , public Visitor {
public:
Outcome compete(const Visitor* it) {
return it->evalScissors();
}
Outcome evalPaper () const {
return lose;
}
Outcome evalScissors() const {
return draw;
}
Outcome evalRock() const {
return win;
}
ostream& print(ostream& os) const {
return os << "Scissors";
}
};
class Rock : public Item , public Visitor {
public:
Outcome compete(const Visitor* it) {
return it->evalRock();
}
Outcome evalPaper () const {
return win;
}
Outcome evalScissors() const {
return lose;
}
Outcome evalRock() const {
return draw;
}
ostream& print(ostream& os) const {
return os << "Rock ";
}
};
struct Compete {
Outcome operator()(Item* a, Item* b) {
cout << a << "\t"<< b << "\t";
return a->compete(b);
}
};
本例中特别之处在于同一类型互为访问者和接受者,通常访问者模式被用于解释器的使用中,接受者即为抽象语法树,访问者则提供解释方法,这样同一个语法树可以接受不同的解释方法。
更多阅读:
1.知乎上陈硕的回答:https://www.zhihu.com/question/23757906
2.[1]中的回答中有提到使用运行时类型识别(RTTI)或者反射(Reflection),实际编程中应当尽量避免使用这一功能,这也是设计模式存在的重要意义之一
3.visitor pattern与模式匹配(Vczh的回答):https://www.zhihu.com/question/28268207
4.Singleton pattern :http://www.klayge.org/?p=3280
5.Dan Friedman的书籍《A Little Java, A Few Patterns》(我已集齐Dan Friedman全套小人书系列),以及王垠和陈硕都有提到的Peter Norvig的演讲。