C++设计模式 实例(updating)

一、预热

  什么是设计模式:设计模式(Design Pattern)是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结,使用设计模式是为了可重用代码、让代码更容易被他人理解并且保证代码可靠性。
  设计模式目标:可复用
  思维模式比代码技巧更重要,设计原则比模式更重要!

二、设计原则

  • 依赖倒置原则(DIP)
     稳定应该依赖于稳定,不能有对变化的依赖。对变化应该隔离开。
  • 开放封闭原则(OCP)
     对扩展开放,对更改封闭。类模块应该是可扩展的,但是不可修改。
  • 单一职责原则(SRP)
     一个类应该仅有一个引起它变化的原因,变化的方向隐含着类的责任。
  • 里氏代换原则 Liskov(LSP)
     子类必须能替换它们的基类(IS-A),继承表达类型抽象。
  • 接口隔离原则(ISP)
     接口应该小而完备。
  • 优先使用对象组合,而不是类继承
     继承在某种程度上破坏了封装性,子类父类耦合度高。但也不是说不能用继承,某些情况下可能继承更适合。
  • 封装变化点
     将稳定和变化的部分隔离开,出现变化时,稳定部分不需要改动。
  • 针对接口编程,而不是针对实现编程
     不将变量类型声明为某个特定的具体类,而是声明为某个接口。
     客户程序无需知道对象的具体类型,只需要知道对象所具有的接口。
     减少系统中各部分的依赖关系,从而实现“高内聚、松耦合”的类型设计方案。

三、GOF-23模式分类、重构

3.1 模式分类

  • 从目的来看:
  1. 创建型( Creational)模式: 将对象的部分创建工作延迟到类或者其他对象,从而应对需求变化为对象创建时具体类型现引来的冲击。
    包括:单例模式,简单工厂模式,抽象工厂模式,工厂方法模式,原型模式,建造者模式
  2. 结构型( Structural)模式: 通过类继承或者对象组合获得更活的结构,从而应对需求变化为对象的结构带来的冲击。
    包括:适配器模式,桥接模式,组合模式,装饰模式,外观模式,享元模式,代理模式
  3. 行为型( Behavioral)模式: 通过类继承或者对象组合来划类与对象间的职责,从而应对需求变化为多个交互的对象带的冲击。
    包括:职责链模式,命令模式,解释器模式,迭代器模式,中介者模式,备忘录模式,观察者模式,状态模式,策略模式,模板方法模式,访问者模式
  • 从范围来看:
  1. 类模式处理类与子类的静态关系。
  2. 对象模式处理对象间的动态关系。

3.2 重构关键技法

  • 静态 —> 动态
  • 早绑定 —> 晚绑定
  • 继承 —> 组合
  • 编译时依赖 —> 运行时依赖
  • 紧耦合 —> 松耦合

四、TemplateMethod Mode 模板方法模式

  定义一个操作中的算法的骨架 (稳定),而将一些步骤延迟(晚绑定)到子类中。 Template ethod使得子类可以不改变(复用)一个算法的结构即可重定义(override 重写)该算法的某些特定步骤

在这里插入图片描述
  模板方法实现了一个算法的框架,有些步骤可能是固定不变的,不需要暴露给用户,而有些步骤是允许用户重写的,这些步骤会被封装成一个或多个方法,以虚函数的形式提供给用户,允许用户自己实现。
  在下面的代码中,抽象基类的TemplateMethod方法中可能还有其它的步骤,不止这两个。用户只需要根据实际情况重写这些接口即可,不用关心整体的工作流程。(稳定的框架为非虚方法,可变化的步骤为虚方法)

#include <iostream>

using namespace::std;

class AbstractClass
{
public:
	void TemplateMethod() {
		CustomOperation1();
		CustomOperation2();
	}
protected:
	//抽象基类定义的默认Operation
	virtual void CustomOperation1() {
		cout << "Default Operation1." << endl;
	}
	virtual void CustomOperation2() {
		cout << "Default Operation2." << endl;
	}
};

class ConcreteClassA : public AbstractClass
{
public:
	//具体类重写的Operation
	virtual void CustomOperation1() {
		cout << "ConcreteA Operation1." << endl;
	}
	virtual void CustomOperation2() {
		cout << "ConcreteA Operation2." << endl;
	}
};

class ConcreteClassB : public AbstractClass
{
public:
	//具体类重写的Operation
	virtual void CustomOperation1() {
		cout << "ConcreteB Operation1." << endl;
	}
	virtual void CustomOperation2() {
		cout << "ConcreteB Operation2." << endl;
	}
};


int main(void)
{
	AbstractClass * pAbstract = new AbstractClass();
	pAbstract->TemplateMethod();

	AbstractClass * pConcreteA = new ConcreteClassA();
	pConcreteA->TemplateMethod();

	AbstractClass * pConcreteB = new ConcreteClassB();
	pConcreteB->TemplateMethod();

	return 0;
}

五、Strategy Mode 策略模式

  开放封闭原则

  定义一系列的算法,把它们一个个封装起来,并且使它们可相互替换。该模式使得算法可独立于使用它的客户而变化。

在这里插入图片描述
  策略模式其实是对算法的封装,当处理一个问题可能有多种算法(方法)时就可以使用该模式。Strategy类就是对该算法的封装,具体类则是对该算法的不同实现。
  当程序中出现if…else…或者switch…case…,而且很有可能会增加分支时,一般就要策略模式了。

#include <iostream>
#include <string>

using namespace std;

class Strategy
{
public:
	virtual ~Strategy() { }
	virtual void AlgorithmInterface() = 0;
};

//ConcreteStrategyA、ConcreteStrategyB对同一个算法的接口做了不同的实现
class ConcreteStrategyA : public Strategy
{
public:
	virtual void AlgorithmInterface()
	{
		cout << "AlgorithmInterface Implemented by ConcreteStrategyA." << endl;
	}
};

class ConcreteStrategyB : public Strategy
{
public:
	virtual void AlgorithmInterface()
	{
		cout << "AlgorithmInterface Implemented by ConcreteStrategyB." << endl;
	}
};

//Context代表上下文
class Context
{
public:
	~Context()
	{
		delete m_Strategy;
	}

	Context(Strategy * pStrategy) : m_Strategy(pStrategy) { }
	void ContextInterface()
	{
		if (NULL != m_Strategy)
		{
			//上下文根据需要调用算法的某个实现
			m_Strategy->AlgorithmInterface();
		}
	}

private:
	Strategy * m_Strategy;
};

int main(void)
{
	//情况1: 调用该算法时,我需要使用ConcreteStrategyA实现的方法
	Strategy * pConcreteStrategyA = new ConcreteStrategyA();
	Context * pContext = new Context(pConcreteStrategyA);
	//...
	pContext->ContextInterface();
	//...
	
	//情况2: 调用该算法时,使用ConcreteStrategyB实现的方法更为合适
	Strategy * pConcreteStrategyB = new ConcreteStrategyB();
	Context * pContext2 = new Context(pConcreteStrategyB);
	//...
	pContext2->ContextInterface();
	//...

	return 0;
}

  模板方法和策略模式对比:两者都是通过继承基类对接口进行具体的实现。不同之处在于前者是对某个算法流程中的多个步骤,后者是对某一个算法在不同情况下的不同实现。同时策略模式的上下文还需要维护一个算法基类的指针。

六、Observer Mode 观察者模式

  定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。当一个对象发生了变化,关注它的对象就会得到通知;这种交互也称为发布-订阅(publish-subscribe)。目标是通知的发布者,它发出通知时并不需要知道谁是它的观察者。

在这里插入图片描述
  目标不需要知道具体的观察者有什么接口以及有多少个观察者,观察者只需要实现一个update接口供目标通知时使用即可。如以下示例代码所示,当目标状态发生变化时,目标调用Notify接口自动通知所有的观察者(其实是目标调用了观察者的update接口,主动更新了观察者的状态,这样观察者就不用做任何事情就可以得到更新)

#include <iostream>
#include <string>
#include <list>

using namespace std;

class Subject;

class Observer
{
public:
	~Observer() {}
	virtual void update(Subject *pSubject) = 0;
};

class Subject
{
public:
	~Subject()
	{
		m_observer_list.clear();
	}
	void Attach(Observer * pObserver)
	{
		cout << "Attach an Observer: " << hex << pObserver << endl;
		m_observer_list.push_back(pObserver);
	}
	void Detach(Observer * pObserver)
	{
		cout << "Detach an Observer: " << hex << pObserver << endl;
		m_observer_list.remove(pObserver);
	}
	virtual void Notify()
	{
		list<Observer *>::iterator it;
		for (it = m_observer_list.begin(); it != m_observer_list.end(); ++it)
		{
			cout << "Notify the Observers: " << hex << *it << endl;
			(*it)->update(this);
		}
	}
	virtual void SetStatus(int iStatus) = 0;
	virtual int GetStatus() = 0;

private:
	list<Observer *> m_observer_list;
};

class ConcreteSubject : public Subject
{
public:
	virtual void SetStatus(int iStatus)
	{
		m_status = iStatus;
	}
	virtual int GetStatus()
	{
		return m_status;
	}
	
private:
	int m_status;

};

class ConcreteObserver : public Observer
{
public:
	virtual void update(Subject *pSubject)
	{
		m_status = pSubject->GetStatus();
		cout << "ConcreteObserver "<< hex << this << " get notify, status = " << m_status << endl;
	}

private:
	int m_status;
};

int main(void)
{
	Subject * pConcreteSubject = new ConcreteSubject();
	Observer * ConcreteObserver1 = new ConcreteObserver();
	Observer * ConcreteObserver2 = new ConcreteObserver();

	pConcreteSubject->Attach(ConcreteObserver1);
	pConcreteSubject->Attach(ConcreteObserver2);

	pConcreteSubject->SetStatus(2);
	pConcreteSubject->Notify();

	return 0;
}

运行结果:
在这里插入图片描述

七、Decorator Mode 装饰模式

  动态地给一个对象添加一些额外的职责。就增加功能来说,Decorator模式相比生成子类更为灵活。

在这里插入图片描述
  装饰模式能动态地为某个类的对象添加职责,这种添加是通过外部(装饰类)、采用对象组合的方式实现的,原有的类无需改动,且对使用者来说接口是一致的。
  在示例代码中,ConcreteComponent是原本存在的类,现在需要对这个类的对象添加职责,于是定义一个Decorator基类和ConcreteDecorator子类。Decorator基类中需要维护一个Component指针,以及一个和ConcreteComponent类一致的接口(Operation);ConcreteDecorator子类也有一个和ConcreteComponent类一致的接口(Operation),以及添加职责的方法(AddOperation)。当调用具体修饰类的对象的Operation接口时,一方面会执行原有的职责(即ConcreteComponent类对象的Operation),另一方面也会执行新添加的职责,这样就为类对象添加了一个新的职责。

#include <iostream>

using namespace std;

class Component
{
public:
	virtual void Operation() = 0;
};

//ConcreteComponent是需要添加职责的对象所属的类
class ConcreteComponent : public Component
{
public:
	void Operation()
	{
		cout << "This is original operation." << endl;
	}
};

class Decorator : public Component
{
public:
	Decorator(Component *pComponent) : m_pComponent(pComponent) {}
	void Operation()				//提供一个和Component一致的接口
	{
		m_pComponent->Operation();	//执行原有职责
	}
private:
	Component *m_pComponent;		//维护一个Component指针,用来执行原有职责
};

//ConcreteDecorator:为ConcreteComponent类的对象添加职责(ConcreteDecorator装饰ConcreteComponent)
class ConcreteDecorator : public Decorator
{
public:
	ConcreteDecorator(Component *pComponent) : Decorator(pComponent) {}
	void Operation()
	{
		Decorator::Operation();		//执行原有职责
		AddOperation();				//添加职责
	}
	void AddOperation()
	{
		cout << "This is Added Operation." << endl;
	}
};

int main(void)
{
	Component *pConcreteComponent = new ConcreteComponent();
	Decorator *pConcreteDecorator = new ConcreteDecorator(pConcreteComponent);
	pConcreteDecorator->Operation();

	return 0;
}

执行结果:
在这里插入图片描述
扩展:
  装饰模式是为类的对象添加职责,而不是对类。使用装饰模式能灵活地组合所需要的功能,例如可以在示例代码的基础上再定义一个ConcreteDecorator类的对象,这样就新增了两个功能:

#include <iostream>

using namespace std;


class Component
{
public:
	virtual void Operation() = 0;
};

//ConcreteComponent是需要添加职责的对象所属的类
class ConcreteComponent : public Component
{
public:
	void Operation()
	{
		cout << "This is original operation." << endl;
	}
};

class Decorator : public Component
{
public:
	Decorator(Component *pComponent) : m_pComponent(pComponent) {}
	void Operation()				//提供一个和Component一致的接口
	{
		m_pComponent->Operation();	//执行原有职责
	}
private:
	Component *m_pComponent;		//维护一个Component指针,用来执行原有职责
};

//ConcreteDecorator:为ConcreteComponent类的对象添加职责(ConcreteDecorator装饰ConcreteComponent)
class ConcreteDecoratorA : public Decorator
{
public:
	ConcreteDecoratorA(Component *pComponent) : Decorator(pComponent) {}
	void Operation()
	{
		Decorator::Operation();		//执行原有职责
		AddOperation();				//添加职责
	}
	void AddOperation()
	{
		cout << "This is Added OperationA." << endl;
	}
};

class ConcreteDecoratorB : public Decorator
{
public:
	ConcreteDecoratorB(Component *pComponent) : Decorator(pComponent) {}
	void Operation()
	{
		Decorator::Operation();		//执行原有职责
		AddOperation();				//添加职责
	}
	void AddOperation()
	{
		cout << "This is Added OperationB." << endl;
	}
};

int main(void)
{
	Component *pConcreteComponent = new ConcreteComponent();

	Decorator *pConcreteDecoratorA = new ConcreteDecoratorA(pConcreteComponent);
	pConcreteDecoratorA->Operation();

	cout << "============================" << endl;

	Decorator *pConcreteDecoratorB = new ConcreteDecoratorB(pConcreteComponent);
	pConcreteDecoratorB->Operation();

	return 0;
}

运行结果:
在这里插入图片描述

八、Bridge Mode 桥接模式

依赖倒置原则、开放封闭原则

  将抽象部分与它的具体实现解耦,使得二者可以独立的变化。桥接模式将两个独立变化的部分(即抽象接口与具体实现)解耦开,在抽象层建立两者的关联关系。

桥接模式类图

  • Abstraction(抽象类):定义抽象类的接口,维护一个指向Implementor接口的指针。
  • RefineAbstraction(扩充抽象类):实现抽象类接口,可以直接调用Implementor接口的TurnOn方法;
  • Implementor(实现类接口):定义实现类的接口。只定义接口,实现细节由具体实现类去完成,需要不同的实现时只需要修改具体实现类即可,接口不用修改(开放封闭原则)。
  • ConcreteImplementor(具体实现类):具体实现Implementor接口。不同的具体实现类拥有不同的操作。

  类图右边部分,具体实现类是对接口的实现,而对外只提供调用的接口,将实现细节隐藏起来。
  类图左边部分,抽象类维护了一个接口指针,因此抽象类和接口类是单向关联的关系。在抽象中引用实现部分,只需调用实现类接口,而不用考虑具体的实现细节,这样就将抽象部分和它的实现部分解耦开了。
  简言之,在Abstraction类中维护一个Implementor类指针,需要采用不同的实现方式的时候只需要传入不同的Implementor派生类就可以了。

实例代码:

#include <iostream>

using namespace std;

//实现类接口,定义了一个接口
class Implementor
{
public:
	virtual void TurnOn() = 0;
};
//具体实现类A、B:具体实现Implementor接口
class ConcreteImplementor_A : public Implementor
{
	void TurnOn()
	{
		cout << "Turn On the living room lights"<< endl;
	}
};

class ConcreteImplementor_B : public Implementor
{
	void TurnOn()
	{
		cout << "Turn On the bedroom lights"<< endl;
	}
};

//抽象类:定义了一个接口
class Abstraction
{
public:
	Abstraction(Implementor *pImpl) : m_Implementor(pImpl) {}
	virtual void TurnOnTheLights() = 0;
protected:
	Implementor *m_Implementor;
};

//扩充抽象类:实现了抽象类的接口
class RefinedAbstraction : public Abstraction
{
public:
	RefinedAbstraction(Implementor *pImpl) : Abstraction(pImpl) {}
	virtual void TurnOnTheLights()
	{
		m_Implementor->TurnOn();
	}
};

int main(int argc, char **argv)
{
	Implementor * pImpl_A = new ConcreteImplementor_A();
	Abstraction * pRefAbstr_A = new RefinedAbstraction(pImpl_A);
	pRefAbstr_A->TurnOnTheLights();	//打开客厅的灯

	Implementor * pImpl_B = new ConcreteImplementor_B();
	Abstraction * pRefAbstr_B = new RefinedAbstraction(pImpl_B);
	pRefAbstr_B->TurnOnTheLights();	//打开卧室的灯

}

运行结果:
桥接模式运行结果

九、单例模式

  确保一个类只有一个实例,并提供一个全局访问点来访问这个唯一实例。
单例模式UML

  单例模式结构很简单,这里以打印机为例,假设只有一台打印机,打印机只能同时打印一份文档。
  首先,为确保只有一个实例,防止创建多个对象,必须将构造函数设为私有的,只能通过类的静态方法实例化一个对象。其次,为了提供一个全局访问点来访问这个唯一实例,Printer类提供了公有的、静态的GetInstance方法,调用该方法即返回该实例。

9.1 简单的单例模式:

实例代码:

#include <iostream>

using namespace std;

class Printer
{
public:
	static Printer *GetInstance()
	{
		if (NULL == m_instance)
		{
			cout << "Create a printer." << endl;
			m_instance = new Printer();
		}
		return m_instance;
	}

	void work()
	{
		cout << "The printer is printing a document" << endl;
	}

private:
	Printer() {}
	static Printer *m_instance;
};

Printer* Printer::m_instance = NULL;

int main(int argc, char **argv)
{
	Printer *MyPrinter = Printer::GetInstance();
	MyPrinter->work();

	Printer *MyPrinter_c = Printer::GetInstance();
	MyPrinter_c->work();

}

运行结果:
单例模式运行结果
  可以看到,多次调用GetInstance方法也只有在第一次调用时实例化对象,以后调用直接返回该实例的指针。

9.2 线程安全的单例模式

  上述代码是单例模式最简单的使用,但不是线程安全的。如果有多个线程企图调用GetInstance方法访问该实例,则存在创建多个实例的风险。如何做到线程安全?有很多种方法,最简单的就是在GetInstance中加锁。

实例代码:

#include <iostream>
#include <mutex>

using namespace std;

class Printer
{
public:
	static Printer *GetInstance()
	{
		if (NULL == m_instance)
		{
			m_mutex.lock();
			if (NULL == m_instance)
			{
				cout << "Create a printer." << endl;
				m_instance = new Printer();
			}
			m_mutex.unlock();
		}
		return m_instance;
	}

	void work()
	{
		cout << "The printer is printing a document" << endl;
	}

private:
	Printer() {}
	static Printer *m_instance;
	static mutex m_mutex;
};

Printer* Printer::m_instance = NULL;
mutex Printer::m_mutex;

int main(int argc, char **argv)
{
	Printer *MyPrinter = Printer::GetInstance();
	MyPrinter->work();

	Printer *MyPrinter_c = Printer::GetInstance();
	MyPrinter_c->work();

}

  此处进行了两次m_Instance == NULL的判断,是借鉴了Java的单例模式实现时,使用的所谓的“双检锁”机制。因为进行一次加锁和解锁是需要付出对应的代价的,而进行两次判断,就可以避免多次加锁与解锁操作,同时也保证了线程安全。

9.3 饿汉实现

单例有两种实现方法:懒汉与饿汉。

  • 懒汉:故名思义,不到万不得已就不会去实例化类,也就是说在第一次用到类实例的时候才会去实例化,所以上面的方法属于懒汉实现;
  • 饿汉:饿了肯定要饥不择食。所以在单例类定义的时候就进行实例化。

特点与选择:

  • 由于要进行线程同步,所以在访问量比较大,或者可能访问的线程比较多时,采用饿汉实现,可以实现更好的性能。这是以空间换时间。
  • 在访问量较小时,采用懒汉实现。这是以时间换空间。

实例代码:

#include <iostream>
#include <mutex>

using namespace std;

class Printer
{
public:
	static Printer *GetInstance()
	{
		return m_instance;
	}

	void work()
	{
		cout << "The printer is printing a document" << endl;
	}

private:
	Printer() {}
	static Printer *m_instance;
};

Printer* Printer::m_instance = new Printer;

int main(int argc, char **argv)
{
	Printer *MyPrinter = Printer::GetInstance();
	MyPrinter->work();
}

  由于m_instance是静态成员变量,所以在程序运行时就会实例化一个对象出来,当调用GetInstance方法时,直接返回该实例即可。饿汉是线程安全的,因此不用加锁。

  

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值