25 Dec 2019 | notes
class Product {
virtual void fun();
};
class Product1 : public Product { ... };
class Product2 : public Product { ... };
class Factory {
Product* createProduct(auto xx) {
switch (xx) { ... }
}
};
把简单工厂的内部逻辑判断移到了客户端代码来进行
工厂模式包括简单工厂、工厂方法、抽象工厂这3种细分模式。其中,简单工厂和工厂方法比较常用,抽象工厂的应用场景比较特殊,所以很少用到,不是我们学习的重点。
工厂模式用来创建不同但是相关类型的对象(继承同一父类或者接口的一组子类),由给定的参数来决定创建哪种类型的对象。实际上,如果创建对象的逻辑并不复杂,那我们直接通过new来创建对象就可以了,不需要使用工厂模式。当创建逻辑比较复杂,是一个“大工程”的时候,我们就考虑使用工厂模式,封装对象的创建过程,将对象的创建和使用相分离。
当每个对象的创建逻辑都比较简单的时候,我推荐使用简单工厂模式,将多个对象的创建逻辑放到一个工厂类中。当每个对象的创建逻辑都比较复杂的时候,为了避免设计一个过于庞大的工厂类,我们推荐使用工厂方法模式,将创建逻辑拆分得更细,每个对象的创建逻辑独立到各自的工厂类中。
详细点说,工厂模式的作用有下面4个,这也是判断要不要使用工厂模式最本质的参考标准。
除此之外,我们还讲了工厂模式一个非常经典的应用场景:依赖注入框架,比如Spring IOC、Google Guice,它用来集中创建、组装、管理对象,跟具体业务代码解耦,让程序员聚焦在业务代码的开发上。DI框架已经成为了我们平时开发的必备框架,在专栏中,我还带你实现了一个简单的DI框架,你可以再回过头去看看。
class Product {
virtual void fun();
};
class Product1 : public Product { ... };
class Product2 : public Product { ... };
class Factory {
virtual Product* createProduct() = 0;
};
class Factory1 : public Factory {
Product* createProduct() { return new Product1(); }
};
class Factory2 : public Factory { ... };
Factory* f = new Factory1();
Product* product1 = f->createProduct();
工厂模式有了多种 Product。
class ProductA {
virtual void fun();
};
class ProductA1 : public ProductA { ... };
class ProductB {
virtual void fun();
};
class ProductB1 : public ProductB { ... };
class Factory {
virtual ProductA* createProductA() = 0;
virtual ProductB* createProductB() = 0;
};
class Factory1 : public Factory {
Product* createProductA() { return new ProductA1(); }
Product* createProductB() { return new ProductB1(); }
};
class Factory2 : public Factory { ... };
定义一系列的算法,把它们一个个封装起来,并且使它们可相互替换。
普通的多态
class Strategy {
virtual void algorithmInterface() = 0;
};
class Strategy1 : public Strategy {
void algorithmInterface() { ... }
};
class Strategy2 : public Strategy { ... };
class Context {
Strategy* s;
void contextInterface() { s->algorithmInterface(); }
};
动态地给一个对象添加一些额外的职责。将核心职责和装饰功能分开。
装饰器模式主要解决继承关系过于复杂的问题,通过组合来替代继承,给原始类添加增强功能。这也是判断是否该用装饰器模式的一个重要的依据。除此之外,装饰器模式还有一个特点,那就是可以对原始类嵌套使用多个装饰器。为了满足这样的需求,在设计的时候,装饰器类需要跟原始类继承相同的抽象类或者接口。
class Component {
virtual void operation();
};
class ConcreteComponent { ... };
class Decorator : public Component {
Component c;
void operation() { c.operation(); };
};
ConcreteDecorator1 : public Decorator {
void operation() {
...
Decorator::operation(); // 我觉得c.operation();也行,因为构建子类时把父类赋予子类的c了
...
};
};
ConcreteDecorator2 : public Decorator { ... };
ConcreteComponent a;
ConcreteDecorator1 b(a);
ConcreteDecorator2 c(b);
c.operation();
代理模式在不改变原始类接口的条件下,为原始类定义一个代理类,主要目的是控制访问,而非加强功能,这是它跟装饰器模式最大的不同。一般情况下,我们让代理类和原始类实现同样的接口。但是,如果原始类并没有定义接口,并且原始类代码并不是我们开发维护的。在这种情况下,我们可以通过让代理类继承原始类的方法来实现代理模式。
静态代理需要针对每个类都创建一个代理类,并且每个代理类中的代码都有点像模板式的“重复”代码,增加了维护成本和开发成本。对于静态代理存在的问题,我们可以通过动态代理来解决。我们不事先为每个原始类编写代理类,而是在运行的时候动态地创建原始类对应的代理类,然后在系统中用代理类替换掉原始类。
代理模式常用在业务系统中开发一些非功能性需求,比如:监控、统计、鉴权、限流、事务、幂等、日志。我们将这些附加功能与业务功能解耦,放到代理类统一处理,让程序员只需要关注业务方面的开发。除此之外,代理模式还可以用在RPC、缓存等应用场景中。
class Subject {
virtual void request() = 0;
};
class RealSubject : public Subject {
void request() { ... }
};
class Proxy : public Subject {
Subject s;
void request() {
s.request();
}
};
用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
就是实现拷贝构造函数和赋值运算符而已。
如果对象的创建成本比较大,而同一个类的不同对象之间差别不大(大部分字段都相同),在这种情况下,我们可以利用对已有对象(原型)进行复制(或者叫拷贝)的方式,来创建新对象,以达到节省创建时间的目的。这种基于原型来创建对象的方式就叫作原型模式。
原型模式有两种实现方法,深拷贝和浅拷贝。浅拷贝只会复制对象中基本数据类型数据和引用对象的内存地址,不会递归地复制引用对象,以及引用对象的引用对象……而深拷贝得到的是一份完完全全独立的对象。所以,深拷贝比起浅拷贝来说,更加耗时,更加耗内存空间。
如果要拷贝的对象是不可变对象,浅拷贝共享不可变对象是没问题的,但对于可变对象来说,浅拷贝得到的对象和原始对象会共享部分数据,就有可能出现数据被修改的风险,也就变得复杂多了。操作非常耗时的情况下,我们比较推荐使用浅拷贝,否则,没有充分的理由,不要为了一点点的性能提升而使用浅拷贝。
class Prototype {
virtual Prototype* clone() = 0;
};
class ConcretePrototype : public Prototype { ... };
目的是把处理从数据结构分离出来。适用于有比较稳定的数据结构、易于变化的算法。
结构稳定,算法多变,所以转变思路,从 一个结构对应多个算法 变成 一个算法对应多个结构。
class MutableBase { // Visitor, 易于变化的算法
virtual void visitImmutable1(Immutable1*) = 0;
virtual void visitImmutable2(Immutable2*) = 0;
};
class Mutable1 : public MutableBase {
void visitImmutable1(Immutable1*);
void visitImmutable2(Immutable2*);
};
class Mutable2 : public MutableBase { ... };
class ImmutableBase { // Element, 比较稳定的数据结构
virtual void Accept(MutableBase*) = 0;
};
class Immutable1 : public ImmutableBase {
void Accept(MutableBase* p) {
p->visitImmutable1(this);
}
};
class Immutable2 : public ImmutableBase { ... };
vector<ImmutableBase> v{...}; // 用法
v[xx].Accept(xx);
https://zhuanlan.zhihu.com/p/112003741
组合模式使得用户对单个对象和组合对象的使用具有一致性。
感觉是很普通的多态
class Component
{
virtual void fun();
};
class Leaf : public Component { ... }; // 重载处理
class Composite : public Component {
vector<Component> v;
... // 重载处理
};
把不变行为搬移到基类,去除子类的重复代码。
在基类写好逻辑框架,其中变化的部分用虚函数替代。子类重载虚函数。
class Base {
void codeTemplate() { // 基类写好逻辑框架
...
fun1();
fun2();
...
}
virtual void fun1(); // 变化的部分
virtual void fun1();
};
class Derive1 : public Base {
virtual void fun1(); // 子类重载虚函数
virtual void fun2();
};
class Derive2 : public Base { ... };
目的是消除庞大的条件分支语句。通过定义新的子类可以很容易地增加新的状态和转换
定义一个Context类和多个State的子类。Context类里有State变量和设置State的函数。State子类进行判断及更改Context类状态。
class Context {
State state;
void setState(State) { ... }
void request() { state.handle(this); }
};
class State {
virtual void handle(Context*) = 0;
};
class State1 : public State {
void handle(Context* c) {
... // 对Context的某一变量进行判断,如true则执行相应动作
c->setState(State2); // 如false,则切换状态再进行判断,以此类推
c->request();
}
};
class State2 : public State { ... };
定义了一种一对多的依赖关系,让多个观测者同时监听某一个主题对象。这个主体对象在状态改变时,会通知所有观察者。
个人观点,在A类与B类互相需要对方的数据时使用,一对多的通知不是重点。把A类与B类都抽象出基类。
class Subject {
list<Observer> l;
virtual void notify() = 0;
};
class Subject1 : public Subject {
auto data; // Observer类想知道的数据
void notify() { for_each(l.begin(), l.end(), [&](Observer& o){ o.update(); }); }
};
class Observer {
virtual void update() = 0;
};
class observer : public Observer {
Subject* s;
void update() { ... } // update时读取s.data获得数据
};
C++实现委托 TODO
在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到原先保存的状态。
把要保存的细节封装在Memento类里,感觉Caretaker不必要。
class Originator {
... // 需要保存的数据
Memento save() { ... } // 数据在返回的Memento中
void recovery(Memento) { ... }
};
class Memento { ... }; // 需要保存的数据
class Caretaker {
Memento m;
... // Memento相应的set、get函数
};
用一个中介对象来封装一系列的对象交互。中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。
class Mediator {
virtual void send() = 0;
};
class Mediator1 : public Mediator {
void send(Colleague* c) { ... } // 判断c的类别,进行不同处理
};
class Colleague {
Mediator* m;
};
class Colleague1 : public Colleague {
void send() { m->send(this); }
};
class Colleague2 : public Colleague { ... };
提供一种方法顺序访问一个聚合对象中各个元素,而又不需要暴露该对象的内部表示。
class Iterator {
virtual auto first() = 0;
... // next()、isDone()、...
};
class ConcreteIterator : public Iterator {
Aggregate* a;
ConcreteIterator(Aggregate*);
auto first() { return a[0]; }
... // next()、isDone()、...
};
class Aggregate {
virtual Iterator createIterator() = 0;
};
class ConcreteAggregate : public Aggregate {
Iterator createIterator() { return ConcreteIterator(this); }
};
感觉就是普通的多态
将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可撤销的操作。
个人看法是为了把注册和执行分开,感觉并不是很实用。
class Receiver {
void action() { ... }
};
class Command {
Receiver* r;
virtual void execute() = 0;
};
class Command1 : public Command {
void execute() { r->action(); }
};
class Command1 : public Command { ... };
class Invoker {
auto container; // auto可以是Command*、vector<Command>...
void invoke() { container.execute(); };
};
Receiver r; ... Command1 c1; ... Invoker i; ... // 注册
...
i.invoke(); // 执行
从时序的角度来看,在一般编程任务中,如果想执行一个动作,先将某个对象、该对象的某个成员函数、该函数的引数组装成一个调用:window.Resize(0,0,200,100);
。“启动这样一个调用”的时刻和“收集这个调用所需元素”的时刻,在概念上是无法区分的。
使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,直到有一个对象处理它为止
感觉和状态模式差不多,都是把 switch 语句给分解成多个类。不同点在于状态模式的分支跳跃由作者写在子类,职责链的分支跳跃由使用者指定。
class Handler {
Handler successor;
void setSuccessor(Handle*) { ... }
virtual void handleRequest() = 0;
};
class Handler1 : public Handler {
void handleRequest(auto r) {
... // 进行判断,true则处理请求
successor.handleRequest(r); // false则进行下一个判断
};
};
class Handler2 : public Handler { ... };
Handler1 h1; Handler2 h2;
h1.setSuccessor(&h2); // 分支跳跃由使用者指定
h1.handleRequest(xx);
运用共享技术有效地支持大量细粒度的对象。
目的是减少存储开销,用较少的共享对象取代很多组对象。
class FlyWeight { ... };
class ConcreteFlyWeight : public FlyWeight { ... };
class FlyWeightFactory {
auto container; // 可以用哈希表或vector实现,目的是找出已经存在的对象再利用
FlyWeight* getFlyWeight() { ... }; // 如果container存在对象则返回,否则实例化再返回
};
将子系统中的一组接口提供一个一致的界面,外观模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。
就是用一个类把很多其他的类给包装起来
class SubSystem1 { void fun1(); };
class SubSystem2 { void fun2(); };
class Facade {
SubSystem1 s1; SubSystem2 s2;
void fun() {
s1.fun1();
s2.fun2();
}
};
感觉只是 组合关系(has-a)+多态
桥接模式的代码实现非常简单,但是理解起来稍微有点难度,并且应用场景也比较局限,所以,相对来说,桥接模式在实际的项目中并没有那么常用,你只需要简单了解,见到能认识就可以了,并不是我们学习的重点。
桥接模式有两种理解方式。第一种理解方式是“将抽象和实现解耦,让它们能独立开发”。这种理解方式比较特别,应用场景也不多。另一种理解方式更加简单,等同于“组合优于继承”设计原则,这种理解方式更加通用,应用场景比较多。不管是哪种理解方式,它们的代码结构都是相同的,都是一种类之间的组合关系。
对于第一种理解方式,弄懂定义中“抽象”和“实现”两个概念,是理解它的关键。定义中的“抽象”,指的并非“抽象类”或“接口”,而是被抽象出来的一套“类库”,它只包含骨架代码,真正的业务逻辑需要委派给定义中的“实现”来完成。而定义中的“实现”,也并非“接口的实现类”,而是的一套独立的“类库”。“抽象”和“实现”独立开发,通过对象之间的组合关系组装在一起。
class Implementor {
virtual void operation() = 0;
};
class Implementor1 : public Implementor {
void operation() { ... }
};
class Abstraction {
Implementor* i;
virtual void operation() = 0;
};
class Abstraction1 : public Abstraction {
void operation() { i->operation(); }
};
单例模式用来创建全局唯一的对象。一个类只允许创建一个对象(或者叫实例),那这个类就是一个单例类,这种设计模式就叫作单例模式。单例有几种经典的实现方式,它们分别是:饿汉式、懒汉式、双重检测、静态内部类、枚举。
尽管单例是一个很常用的设计模式,在实际的开发中,我们也确实经常用到它,但是,有些人认为单例是一种反模式(anti-pattern),并不推荐使用,主要的理由有以下几点:
那有什么替代单例的解决方案呢?如果要完全解决这些问题,我们可能要从根上寻找其他方式来实现全局唯一类。比如,通过工厂模式、IOC容器来保证全局唯一性。
有人把单例当作反模式,主张杜绝在项目中使用。我个人觉得这有点极端。模式本身没有对错,关键看你怎么用。如果单例类并没有后续扩展的需求,并且不依赖外部系统,那设计成单例类就没有太大问题。对于一些全局类,我们在其他地方new的话,还要在类之间传来传去,不如直接做成单例类,使用起来简洁方便。
除此之外,我们还讲到了进程唯一单例、线程唯一单例、集群唯一单例、多例等扩展知识点,这一部分在实际的开发中并不会被用到,但是可以扩展你的思路、锻炼你的逻辑思维。这里我就不带你回顾了,你可以自己回忆一下。
class Singleton {
static Singleton* s = nullptr;
Singleton() = delete;
static Singleton* getInstance() { ... } // 如果s为nullptr则创建
};
和外观模式差不多,都是有一个类把复杂的东西进行封装。
建造者模式用来创建复杂对象,可以通过设置不同的可选参数,“定制化”地创建不同的对象。建造者模式的原理和实现比较简单,重点是掌握应用场景,避免过度使用。
如果一个类中有很多属性,为了避免构造函数的参数列表过长,影响代码的可读性和易用性,我们可以通过构造函数配合set()方法来解决。但是,如果存在下面情况中的任意一种,我们就要考虑使用建造者模式了。
class Builder {
virtual void fun1() {}
virtual void fun2() {}
...
};
class ConcreteBuilder : public Builder {
... // 重载虚函数
};
class Director {
Builder b;
void create() { b.fun1(); b.fun2(); }
};
感觉就是中间层加一个类进行封装
代理模式、装饰器模式提供的都是跟原始类相同的接口,而适配器提供跟原始类不同的接口。适配器模式是用来做适配的,它将不兼容的接口转换为可兼容的接口,让原本由于接口不兼容而不能一起工作的类可以一起工作。适配器模式有两种实现方式:类适配器和对象适配器。其中,类适配器使用继承关系来实现,对象适配器使用组合关系来实现。
适配器模式是一种事后的补救策略,用来补救设计上的缺陷。应用这种模式算是“无奈之举”。如果在设计初期,我们就能规避接口不兼容的问题,那这种模式就无用武之地了。在实际的开发中,什么情况下才会出现接口不兼容呢?我总结下了下面这5种场景:
class Adaptee {
void specificRequest() { ... }
};
class Target {
virtual void request();
};
class Adapter : public Target {
Adaptee a;
void request() {
a.specificRequest();
}
};
TODO
https://blog.csdn.net/u011173875/article/details/17490807
template <class CPU, class DISPLAY, class BATTERY,
template<class C> class NUMCorePolicy = QuadCorePolicy,
template<class D> class DisplayPolicy = LCDDisplayPolicy,
template<class B> class BatteryPolicy = ReplacableBatteryPolicy >
class SmartPhone : public NUMCorePolicy<CPU>
, public DisplayPolicy<DISPLAY>
, public BatteryPolicy<BATTERY>
{ ... };
template <typename OutputPolicy, typename LanguagePolicy>
class HelloWorld : private OutputPolicy, private LanguagePolicy
{
using OutputPolicy::print;
using LanguagePolicy::message;
public:
void run() const {
print(message());
}
};
class OutputPolicyWriteToCout
{
protected:
template<typename MessageType>
void print(MessageType const &message) const {
std::cout << message << std::endl;
}
};
class LanguagePolicyEnglish
{
protected:
std::string message() const {
return "Hello, World!";
}
};
int main() {
typedef HelloWorld<OutputPolicyWriteToCout, LanguagePolicyEnglish> HelloWorldEnglish;
HelloWorldEnglish hello_world;
hello_world.run();
}
https://xr1s.me/2018/05/10/brief-introduction-to-crtp/、https://zhuanlan.zhihu.com/p/54945314
所谓的 CRTP ,就是基类作为模板类,派生类在继承基类的时候,传入自己作为模板参数。CRTP 真正的用武之地,是在模板类需要访问派生的类的成员(变量或函数)的时候,它可以引用它的派生类,也就可以访问派生类的成员。
template <typename Derive>
struct Quack {
void quack() {
std::cout << static_cast<Derive*>(this)->name << "quack!\n";
}
};
template <typename Derive>
struct MuteQuack {
void quack() {
std::cout << static_cast<Derive*>(this)->name << " did not say any thing!\n";
}
};
struct MallardDuck: Quack<MallardDuck> {
const char *name;
MallardDuck(const char *name): name{name} {
}
};
鸭子叫的多态例子中,编译期就可以确定下来,运行时不需要再修改,不需要在运行时构造函数中绑定行为,那么 CRTP 就可以派上用场。
子类也可以是模版,如下:
template <typename Derive>
struct Fly {
void fly() {
std::cout << static_cast<Derive *>(this)->name << " flies!";
}
};
// A same class Quack implementation as above.
template <template <typename> class QuackBehavior, template <typename> class FlyBehavior>
struct Duck
: QuackBehavior<Duck<QuackBehavior, FlyBehavior>>
, FlyBehavior<Duck<QuackBehavior, FlyBehavior>> {
const char *name;
Duck(const char *name): name{name} {
}
};
int main() {
Duck<Quack, Fly> xris{"Xris"};
xris.quack();
xris.fly();
}
固定的几个模板参数就显得很笨拙,使用可变模板参数:
template <template <typename ...> class ...Impl>
struct Duck: public Impl<Duck<Impl...>>... {
const char *name;
Duck(const char *name)
: name{name}, Impl<Duck<Impl...>>{*this}... {
}
};