✊✊✊大家好!本篇文章主要记录自己在进行音视频学习中,整理的包括单例模式、工厂模式、策略模式、观察者模式等6种相关的设计模式和4种准则的内容重点。

音视频学习笔记——设计模式


本专栏知识点是通过<零声教育>的音视频流媒体高级开发课程进行系统学习,按照学习重点梳理总结后写下文章,对音视频相关内容感兴趣的读者,可以点击观看课程网址:零声教育


导航小助手

  • 音视频学习笔记——设计模式
    • 1.单例模式
    • 2.简单工厂模式
    • 3.策略模式
    • 4.观察者模式
    • 5.组合模式
    • 6.责任链模式
    • 7. 四个准则
      • 单一职责原则
      • 开放――封闭原则
      • 里氏代换原则
      • 依赖倒转原则
    • 8.总结

1.单例模式

保证一个类仅有一个实例,并提供一个访问它的全局访问点

让类自身负责保护它的唯一实例,这个类可以保证没有其他实例可以被创建,并且他可以提供一个访问该实例的方法。

Singelton类,定义GetSingel操作,允许客户访问它的唯一实例。
GetSingel为静态方法,主要负责创建自己的唯一实例。

  • 优点:Singleton类封装它的唯一实例,这样可以严格控制客户怎样访问它以及何时访问它。简单来说,对唯一实例的受控访问。

示例代码:

#include #include #include using namespace std;class Singelton{private:Singelton(){} //构造方法令其私有,堵死外界利用new创建此类实例的可能static Singelton* singel; public:static Singelton* GetSingel(){//此方法是获得本类实例的唯一全局访问点if(singel == NULL){//若实例不存在,new一个实例,否则返回已有实例singel = new Singelton();}return singel;}};Singelton* Singelton::singel = NULL;//注意静态变量类外初始化

客户端:

int main(){Singelton* s1=Singelton::GetSingel();Singelton* s2=Singelton::GetSingel();if(s1 == s2)//两次实例化后对象结果是,实力相同,返回okcout<<"ok"<<endl;elsecout<<"no"<<endl;return 0;}

2.简单工厂模式

主要用于创建对象。新添加类时,不影响以前的系统代码。
核心思想:用一个工厂来根据输入的条件产生不同的类,然后根据不同类的virtual函数得到不同的结果。

注意:面向对象的编程,并不是类越多越好,类的划分是为了封装,但分类的基础是抽象,具有相同属性和功能的对象的抽象集合才是类。

  • 优点: 适应于不同情况创建不同的类时,容易维护、扩展、复用
  • 不足:客户端必须要知道基类和工厂类,耦合性差

活字印刷:

  1. 可维护:要改,只需改要改之字
  2. 可复用:用完之后,可以在之后的印刷中重复使用
  3. 可扩展:若要加字,只需另刻字
  4. 灵活性好:将活字移动就可满足各种排列需求

示例代码:

//基类class COperation{public:int m_nFirst;int m_nSecond;virtual double GetResult(){double dResult=0;return dResult;}};//加法类,继承基类class AddOperation : public COperation{public:virtual double GetResult(){return m_nFirst+m_nSecond;}};//减法类,继承基类class SubOperation : public COperation{public:virtual double GetResult(){return m_nFirst-m_nSecond;}};//工厂类class CCalculatorFactory{public:static COperation* Create(char cOperator);};COperation* CCalculatorFactory::Create(char cOperator){COperation *oper = NULL;//在 C# 中可以用反射来取消判断时用的 switch ,在 C++ 中用什么呢? RTTIswitch (cOperator){case '+':oper=new AddOperation();break;case '-':oper=new SubOperation();break;default:oper=new AddOperation();break;}return oper;}

客户端:

int main(){int a,b;cin>>a>>b;COperation * op=CCalculatorFactory::Create('-');op->m_nFirst=a;op->m_nSecond=b;cout<<op->GetResult()<<endl;return 0;}

3.策略模式

定义算法家族,分别封装起来,让它们之间可以互相替换,让算法变化,不会影响到用户
例如商城收银时如何促销,用打折还是返利,其实都是一些算法,用工厂来生成算法对象,并没有错,但算法本身只是一种策略,最重要的时这些算法是随时都可能互相替代的,这就是变化点,封装变化点就是策略模式的重点。

  • 优点:适合类中的成员以方法为主,算法经常变动;简化了单元测试(因为每个算法都有自己的类,可以通过自己的接口单独测试。
    • 策略模式和简单工厂基本相同,但简单工厂模式只能解决对象创建问题,对于经常变动的算法应使用策略模式。
  • 不足:客户端要做出判断

简单工厂模式只是解决对象的创建问题,每次维护或者扩展都需要改动工厂,以至于代码需要重新编译部署,面对算法的市场变动,简单工厂模式不是最好的办法,这时就需要采用策略模式。

示例代码:

//策略基类class COperation{public:int m_nFirst;int m_nSecond;virtual double GetResult(){double dResult=0;return dResult;}};//AddOperation,封装了具体的算法或者行为,继承于COperation//策略具体类—加法类class AddOperation : public COperation{public:AddOperation(int a,int b){m_nFirst=a;m_nSecond=b;}virtual double GetResult(){return m_n First+m_nSecond;}};//策略具体类—减法类class SubOperation : public COperation{public:AddOperation(int a,int b){m_nFirst=a;m_nSecond=b;}virtual double GetResult(){return m_n First-m_nSecond;}};//Context,用Operation来进行配置,维护对temp对象的引用。class Context{private:COperation* op;//public:Context(COperation* temp){op=temp;}double GetResult(){return op->GetResult();}};

客户端:

int main(){int a,b;char c;cin>>a>>b;cout<< 请输入运算符:cin>>c;switch(c){case '+':Context * context =new Context(new AddOperation(a,b));cout<< context->GetResult()<<endl;break;case '-':Context * context =new Context(new SubOperation(a,b));cout<< context->GetResult()<<endl;break;default:break;}return 0;}

策略和工厂结合
优点:客户端只需访问Context类,而不用知道其他任何类信息,实现了低耦合。
将上例的Context类和main函数进行如下修改:
示例代码:

class Context{private:COperation* op = NULL;//声明一个op对象public:Context(char cType){ //参数一个字符,表示具体算法switch (cType){case '+':op=new AddOperation(3,8);break;case '-':op=new SubOperation(5,2);break;default:op=new AddOperation();break;}}//将实例化具体策略的过程由客户端转移到Context中,简单工厂的应用double GetResult(){return op->GetResult();}};int main(){char c;cin>>c;Context *test=new Context(c);//将输入字符'c'传给Context的对象中cout<<test->GetResult()<<endl;return 0;}

简单工厂和策略工厂客户端代码对比(重点)
简单工厂模式需要让客户端认识多个类;
策略模式与简单工厂模式结合,客户端只需要认识一个Context类就可,耦合降低。

4.观察者模式

观察者模式又叫做发布-订阅(Publish/Sub)模式
定义了一种一对多的关系,让多个观察对象(公司员工)同时监听一个主题对象(秘书),主题对象状态发生变化时,会通知所有的观察者,使它们能够更新自己。

示例代码:

#include #include #include using namespace std;class Secretary;//看股票的同事类(观察对象,观察者)class StockObserver{private:string name;Secretary* sub;public:StockObserver(string strname,Secretary* strsub){name=strname;sub=strsub;}void Update();};//秘书类(主题对象,通知者)class Secretary{private://同事列表vector<StockObserver> observers;public:string action;//请前台帮忙的同事,都需要添加进集合void Add(StockObserver ob){observers.push_back(ob);}//通知void Notify(){vector<StockObserver>::iterator p = observers.begin();while (p!=observers.end()){(*p).Update();p++;};void StockObserver::Update(){cout<<name<<":"<<sub->action<< "不要玩股票了,要开始工作了"<<end;}}

客户端:

int main(){Secretary *p=new Secretary(); // 创建通知者//观察者StockObserver *s1= new StockObserver("小李",p);StockObserver *s2 = new StockObserver("小赵",p);//加入通知队列p->Add(*s1);p->Add(*s2);//事件p->action="老板来了";//通知p->Notify();return 0;}

CObserverBase类,抽象观察者,为所有具体观察者定义了一个接口,在得到主题的通知时更新自己。这个接口叫做更新接口。
抽象观察者一般用一个抽象类或者一个接口实现,更新接口通常包含一个Update()方法,这个方法叫做更新方法。

#include #include #include using namespace std;class SecretaryBase;//抽象观察者class CObserverBase{protected:string name;SecretaryBase* sub;public:CObserverBase(string strname,SecretaryBase* strsub){name=strname;sub=strsub;}virtual void Update()=0;};

StockObserver类,具体观察者,实现抽象观察者角色所要求的更新接口,以便使本身的状态与主题的状态相协调。具体观察者角色可以保存一个指向具体主题对象的引用。具体观察者角色通常用一个具体子类实现。

//具体的观察者,看股票的class StockObserver : public CObserverBase{public:StockObserver(string strname,SecretaryBase* strsub):CObserverBase(strname,strsub){}virtual void Update();};//具体观察者,看 NBA 的class NBAObserver : public CObserverBase{public:NBAObserver(string strname,SecretaryBase* strsub):CObserverBase(strname,strsub){}virtual void Update();};

SecretaryBase类,可翻译为主题或抽象通知者,一般用一个抽象类或一个接口实现。它把所有观察者对象的引用保存在一个集合里,每个主题都能有任意数量的观察者。抽象主题提供一个接口,可以增加和删除观察者对象。

//抽象通知者class SecretaryBase{public:string action;vector<CObserverBase*> observers;public:virtual void Attach(CObserverBase* observer)=0;virtual void Notify()=0;};

Secretary类,具体主题或者具体通知者,在具体内部状态改变时,给所有登记过的观察者发出通知。具体主题角色通常用一个具体子类实现。

//具体通知者class Secretary:public SecretaryBase{public:void Attach(CObserverBase* ob){observers.push_back(ob);}void Notify(){vector<CObserverBase*>::iterator p = observers.begin();while (p!=observers.end()){(*p)->Update();p++;}}};void StockObserver::Update(){cout<<name<<":"<<sub->"不要玩股票了,要开始工作了"<<endl;}void NBAObserver::Update(){cout<<name<<":"<<sub->"不要看 NBA 了,老板来了"<<endl;}

客户端:

int main(){SecretaryBase *p=new Secretary(); // 创建观察者//被观察的对象CObserverBase *s1= new NBAObserver("小李",p);CObserverBase *s2 = new StockObserver("小赵",p);//加入观察队列p->Attach(s1);p->Attach(s2);//事件p->action="老板来了";//通知p->Notify();return 0;}

观察者模式特点:

  • 目的:将一个系统分割成一系列相互协作的类,需要维护相关对象间的一致性。观察者模式可以解除各类耦合,便于维护、扩展和重用。
  • 使用时机:当一个对象改变需要同时改变其他对象,且它不知道具体有多少对象需要改变时。观察者模式可以将两者封装在独立的对象中使它们各自独立地改变和复用。
  • 总结:观察者模式所做的工作其实就是在解除耦合,让耦合的双方都依赖于抽象,而不是依赖与具体。从而使得各自的变化都不会影响另一边的变化。

5.组合模式

将对象组合成树形结构以表示’部分-整体’的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性
整体和部分可以被一致对待(如 WORD 中复制一个文字、一段文字、一篇文章都是一样的操作)

  • 优点:用户不用关系到底是处理一个叶节点还是处理一个组合组件,也就用不着定义组合而写一些选择判断语句了。简单来说,组合模式让客户可以一致地使用组合结构和单个对象。

示例代码:
Component类,为组合中的对象声明接口,适当情况下,实现所有类共有接口的默认行为。声明一个接口用于访问和管理Component的子部件。

#include #include #include using namespace std;class Component{public:string m_strName;Component(string strName){m_strName = strName;}virtual void Add(Component* com)=0;virtual void Display(int nDepth)=0;};

Leaf在组合中表示叶节点对象,叶节点没有子节点。

class Leaf : public Component{public:Leaf(string strNam e): Component(strName){}//叶子没有增加分支和树叶,所以Add方法实现没有意义。//但是这样可以消除叶节点和枝节点对象在抽象层次的区别,具有完全一致的接口virtual void Add(Component* com){cout<<"leaf can't add"<<endl;}//叶节点具体方法,显示名称和级别virtual void Display(int nDepth){string strtemp;for(int i = 0; i < nDepth; i++){strtemp += "-";}strtemp += m_strName;cout<<strtemp<<endl}};

Component类,定义枝节点行为,用来存储子部件,在Component接口中实现与子部件有关的操作,比如增加Add或者删除。

class Composite : public Component{private:vector<Component*> m_component;//一个子对象集合存储其下属的枝节点和叶结点public:Composite(string strName) : Component(strName){}virtual void Add(Component* com){m_component.push_back(com);}virtual void Display(int nDepth){//显示枝节点名称,并对其下级进行遍历string strtemp;for(int i=0; i < nDepth; i++){strtemp+="-"}strtemp += m_strName;cout<<strtemp<<endl;vector<Component*>::iterator p=m_component.begin();while (p!=m_component.end()){(*p)->Display(nDepth+2);p++;}}};

客户端:

#include "Model.h"int main(){//树根pComposite* p=new Composite("小王");p->Add(new Leaf("小李"));p->Add(new Leaf("小赵"));//跟上长出分支p1Composite* p1 = new Composite("小小五");p1->Add(new Leaf("大三"));p->Add(p1);p->Display(1);//显示大叔的样子return 0;}

结果:

-小王---小李---小赵---小小王-----大三

组合模式特点:

  • 使用时机:需求中是体现部分与整体层次的结构时,以及希望用户可以忽略组合对象与单个对象的不同,统一地使用组合结构中的所有对象时,应该考虑组合模式。

6.责任链模式

使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这个对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理为止

  • 优点:随时地增加或修改处理一个请求的结构,增强了给对象指派职责的灵活性。
  • 不足:一个请求极有可能到了链的末端都得不到处理,或者因为没有有正确配置而得不到处理。

接收者和发送者都没有对方的明确信息,且链中对象自己也不知道链的机构。结果是职责链可简化对象的相互连接,仅需保持一个指向其后继者的引用,而不需要保持它所有的候选接受者的引用,大大降低了耦合度。

示例:

示例代码:

#include #include #include using namespace std;//请求class Request{public:string m_strContent;int m_nNumber;};//管理者class Manager{protected:Manager* manager;//管理者的上级string name;public:Manager(string temp){name = temp;}//设置管理者的上级void SetSuccessor(Manager* temp){manager = temp;}//申请请求virtual void GetRequest(Request* request) = 0;};

经理类和总监类可以继承“管理者”类,只需重写“申请请求”的方法。

//经理class CommonManager : public Manager{public:CommonManager(string strTemp) : Manager(strTemp){}virtual void GetRequest(Request* request){if ( request->m_nNumber>=0 && request->m_nNumber<10{cout<<name<<" 处理了 "<<request->m_nNumber<<"个请求"<<endl;}else{manager->GetRequest(request); //其余的申请转到上级}}};//总监class MajorDomo : public Manager{public:MajorDomo(string strTemp) : Manager(strTemp){}virtual void GetRequest(Request* request){if ( request->m_nNumber>=10{cout<<name<<" 处理了 "<<request->m_nNumber<<"个请求"<<endl;}else{manager->GetRequest(request); //其余的申请转到上级}}};

客户端:

int main(){Manager * common = new CommonManager("张经理");Manager * major = new MajorDomo("李总监");common->SetSuccessor(major);//设置上级Request* req = new Request();req->m_nNumber = 33;common->GetRequest(req);req->m_nNumber = 3;common->GetRequest(req);return 0;}

7. 四个准则

单一职责原则

  • 就一个类而言,应该仅有一个引起它变化的原因。

  • 如果一个类承担的职责过多,就等于把这些职责耦合在一起,一个职责的变化可能会削弱或者抑制这个类完成其它职责能力。这种耦合会导制脆弱的设计,当变化发生时,设计会遭受到意想不到的破坏。

  • 如果你能够想到多于一个的动机去改变一个类,那么这个类就具有多于一个的职责。

开放――封闭原则

  • 软件实体可以扩展,但是不可修改。即对于扩展是开放的,对于修改是封闭的。面对需求,对程序的改动是通过增加代码来完成的,而不是改动现有的代码。
  • 当变化发生时,我们就创建抽象来隔离以后发生同类的变化。
  • 开放――封闭原则是面向对象的核心所在。开发人员应该对程序中呈现出频繁变化的那部分做出抽象,拒绝对任何部分都刻意抽象及不成熟的抽象。

里氏代换原则

  • 一个软件实体如果使用的是一个父类的话,那么一定适用其子类。而且它察觉不出父类对象和子类对象的区别。也就是说:在软件里面,把父类替换成子类,程序的行为没有变化。
  • 子类型必须能够替换掉它们的父类型。

依赖倒转原则

  • 抽象不应该依赖细节,细节应该依赖抽象。即针对接口编程,不要对实现编程。 高层模块不能依赖低层模块,两者都应依赖抽象。
  • 依赖倒转原则是面向对象的标志,用哪种语言编写程序不重要,如果编写时考虑的是如何针对抽象编程而不是针对细节编程,即程序的所有依赖关系都终止于抽象类或接口。那就是面向对象设计,反之那就是过程化设计。

8.总结

介绍了音视频学习中需重点把握的6个设计模式,简单介绍相关的4个准则。
这6种设计模式都是必须要掌握的,下表列出了各个模式的优点:

模式优点
单例模式严格控制客户怎样访问它以及何时访问它。简单来说,对唯一实例的受控访问。
工厂模式适应于不同情况创建不同的类时,容易维护、扩展、复用
策略模式适合类中的成员以方法为主,算法经常变动;简化了单元测试(因为每个算法都有自己的类,可以通过自己的接口单独测试。
观察者模式解除各类耦合,便于维护、扩展和重用
组合模式让客户可以一致地使用组合结构和单个对象
责任链模式随时地增加或修改处理一个请求的结构,增强了给对象指派职责的灵活性。