装饰器
9.1 场景
9.2 Dynamic Decorator
9.3 Static Decorator
9.4 Functional Decorator
假设你正在使用某个由你的同事所开发的类, 你想扩展它的某个功能. 如何在不修改代码的前提下实现这一点? 一个方法是继承.
但是, 不是所有的情况下都可以使用继承. 比如, 你无法从std::vector来派生, 因为它没有虚拟析构函数; 你同样无法从int来派生. 更重要的是, 你需要的是某些增强, 而你希望将这些增强独立出来, 因为单一职责原则
9.1 场景考虑一个称为”multiple enchancement”的例子. 你有一个称为Shape的类, 有两个派生类: ColoredShape和TransparentShape, 但是你还要考虑第三种情况: ColoredTransparentShape.
两个特性的组合, 你需要3个类; 如果是三个特性的组合, 就有7个类! 这是不可接受的.
不要忘了, 我们真正需要的是不同的形状类( Square, Circle, Regtangle等等). 因此, 为了这些特 ...
组合模式
在现实中, 对象通常都包含其他的对象. 但是, 一个对象并没有什么途径来宣称自己由其他的对象组成: 字段, 就其本身来说, 除非你定义虚getter和setter函数, 否则并不能代表是接口; 同样, 虽然你可以通过实现成员函数begin()/end()来说明对象是集合, 但是应当记住, 它实际上并没有什么真的约束: 毕竟, 你可以在begin()/end()中做任何事情.
另一种说明自己的对象是容器的做法是从另一个容器中派生, 这种方法大多数情况下是可行的–即便STL容器没有定义虚拟析构函数, 但是只要你并不打算在自己的析构函数中做什么事情, 这就不是一个问题.
那么, 组合模式的目的究竟是什么呢? 本质上, 我们的目的是为了给单一对象和一组对象提供相同的接口. 为它们定义相同的接口是一件很容易的事情, 当然我们也可以试试Duck typing机制, 例如, 定义begin()/end(). Duck typing, 在C++的世界中, 通常被认为是一种很危险的想法. 因为它依赖于某些秘密的知识而不是明确定义的接口.
在Python中, Duck typing则是一种受到鼓励 ...
结构模式
当决定对象的结构时, 我们可以采取几种方法:
继承Inheritance
组合Composition: 子对象不能脱离父对象存在. 例如, 考虑若一个对象有一个owner<T>的成员, 当对象被删除时, 成员也被销毁
聚合Aggregation: 对象可以包含其他的对象, 但是这些被包含的对象也可以独立存在. 比如一个对象的类型为T*或shared_ptr<T>的成员
现在, 一般将Composition和Aggregation同样对待.
桥接模式
7.1 the Pimpl Idiom
7.2 Bridge
7.3 总结
7.1 the Pimpl Idiom从一个简单的例子看起. 假设要设计一个Person类, 存储一些个人的信息. 除了常见的设计方式之外, 你可能还见过这样的定义:
1234567891011struct Person{ std::string name; void greet(); Person(); ~Person(); class PersonImpl; PersonImpl* impl;}
为什么要这么设计?
类PersonImpl并不是在头文件中定义的, 而是在.cpp中定义的.
12345678910struct Person::PersonImpl{ void greet(Person* p);}Person::Person() : impl( new PersonImpl){}Person::~Person() { delete impl; } ...
适配器模式
小区里面加了好几个群,找到了团购的渠道。晚上出去分东西了
6.1 场景
6.2 Adapter
6.3 Adapter Trmporaries
6.4 总结
6.1 场景考虑一个例子, 假设你已经有了一个在屏幕上画点的库, 它工作的很好. 现在, 你需要做几何图形的绘制( 线, 矩形等), 你仍然想继续使用原先的画点的库. 这样, 你就需要将几何形状对象适配(Adapt)到像素上.
首先, 定义领域对象:
12345678struct Point{ int x,y;}struct Line{ Point start, end;}
接下来对几何形状做抽象
12345struct VectorObject{ virtual std::vector<Line>::iterator begin() = 0; virtual std::vector<Line>::iterator end() = 0;}
然后可以定义具体的形状–将它们定义为向量的集合:
1234 ...
单件模式
5.1 Singleton as Global Object
5.2 经典实现
5.3 线程安全
5.4 The Trouble with Singleton
5.5 单件和控制反转
5.6 Monostate 单态
5.7 总结
Singleton设计模式是设计模式历史上最招人恨的模式之一 (?)
5.1 Singleton as Global Object最naive的方法是答应不创建一个以上的对象实例:). 例如:
12345struct Database{ //! @brief 不要创建多个实例! Database(){...}}
当然, 风险也很大.
最容易想到的做法是提供唯一的, 静态的全局变量. 例如:
1static Database database{};
静态全局变量的问题在于其初始化顺序在不同的编译单元中是不确定的. 这会带来一些很麻烦的事情. 例如一个全局对象引用了另一个全局对象, 而后者还没有初始化.另一个问题是发现行的问题: 使用者如何才能知道有这个全局变量存在? ...
原型模式
家里快没菜了,怎么办
4.1 构造对象
4.2 Ordinary Duplicate
4.3 Duplication via Copy Construction
4.4 Serialization的问题
4.5 Prototype Factory 原型工厂
4.6 总结
4.1 构造对象大部分的对象是使用构造函数来创建的. 但是, 如果你已经有了一个已经配置好了的对象, 为什么不利用它来简单的拷贝和修改来创建新的对象而非要一步步从头创建呢? 尤其是在你必须使用Builder模式来分段构造对象的时候.
考虑下面这段存在重复性的代码:
{.line-numbers}12Contact john{ "John Doe", Address{"123 East Dr", "London", 10 } };Contact jane{ "Jane Doe", Address{"123 East Dr&quo ...
Factory模式
3.1 Scenario
3.2 Factory Method 工厂方法
3.3 Factory
3.4 Inner Factory
3.5 抽象工厂Abstract Factory
3.6 Functional Factory
3.7 总结
> I had a problem and tried to use Java, now I have a ProblemFactory.
> - Old Java joke
3.1 Scenario加入要保存一个Point的信息, 你会先实现:
12345678struct Point{ Point(const float x, const float y) : x{x}, y{y} {} float x, y;};
到目前为止, 一切都好. 但是, 假如, 你又需要实现极坐标下的坐标表示. 你可能需要另一个构造函数:
12 ...
Builder模式
2.1 Scenario
2.2 Simiple Builder
2.3 Fluent Builder
2.4 Communicating Intent
2.5 Groovy-Style Builder
2.6 Composite Builder
2.7 Summary
Builder模式关注于创建复杂对象(creation of complicated object).
2.1 Scenario考虑下面一个简单的例子. 我们要生成一段HTML的代码:
12345678910string words[] = {"hello", "world"};ostringstream oss;oss << "<ul>";for( auto w : words){ oss << " <li>" << w << "</li>";}oss << & ...
前言
下楼做了核酸,人心惶惶的
1.1 重要的概念
1.1.1 Curiously Recurring Template Pattern
1.1.2 Mixin Inheritance
1.1.3 Properties
1.2 SOLID Design Principles
1.1 重要的概念1.1.1 Curiously Recurring Template Pattern下面是C++世界中的一种实质上的模式: 一个派生类将自己作为模板参数传递给基类.
{.line-numbers}1234struct Foo : SomeBase<Foo>{ ...}
你可能会奇怪为什么要这么做. 其中的一个原因是需要能够在基类中访问派生类的类型指针.
例如, 假定SomeBase的每个派生类都实现了一对自己的begin()/end()迭代函数. 那么如何迭代访问SomeBase的派生类呢? 直觉是不要这么做, 因为SomeBase自身并不会提供begin()/end()接口. 下面是实现:
123456789 ...