zoukankan      html  css  js  c++  java
  • 实用的设计模式【二】——类的组织

    "四人帮"的《design pattern》的确博大精深,但个人觉得毕竟还是偏学院派了,似乎不用非常理性的去理解它们,所以在实际应用中,甚至一些人主张不使用模式。就像说话写作文一样,我们不一定要有模版,但是一些常用的总分、对比等手法还是可以借鉴的。如果能如独孤求败的剑术一样,到达精通剑、一切皆剑、无剑的境界,当然最好,但实际用好其中的一部分也已经足够你好好的耍耍了。

    凡是有一个过程,最近读了《c++ API设计》,里面从API设计角度,也提到了若干有用的模式,回想起来,《effective c++》中也经常提到如何设计类的大量条款等,遂在这里记录分享之。

    1 Pimpl惯用法

    这是保持API接口和实现分离的重要机制,但并不是严格的设计模式(可以看做桥接模式的一种特例)。
    将不需要客户知道的部分用一个实现类的指针代替。

    用途

    可以把实现细节从公有的头文件分离出来,需要提供API接口对应头文件,为了提供极简的接口,不需要引入自己的实现导致复杂化

    举例

    // timer.h
    class AutoTimer
    {
    public:
    	explicit AutoTimer();
    	~AutoTimer();
    
    private:
    	// 除非AutoTimer必须访问Impl的成员,才声明为public
    	// 在Impl一般只放置私有成员变量和方法,甚至可以包含指向公有类的指针
    	class Impl;
    	Impl *_Impl;
    
    	//禁止被复制,对于含有成员指针都要注意限制之
    	AutoTimer(const AutoTimer&);
    	const AutoTimer& operator=(const AutoTimer &);
        }
    

    2 单例模式

    通常我们将构造、复制、复制构造、析构声明为私有,而通过如下方式访问对象方法:
    Singleton& Singleton::GetInstance()
    {
    static Singleton instance;
    return instance;
    }

    不同编译单元中,非局部静态的初始化顺序是未定义的。多线程初始中,所以甚至需要使用DCLP双重检查锁定模式来调用,但可能会影响性能。

    用途

    更优雅的维护全局状态的方式,但应确定清楚是否需要全局状态

    举例

    实现一个健壮的单例是非常困难的,比如线程安全等,而应该尽可能避免使用单例,比如使用依赖注入、单一状态、会话状态,使用单一行为代替单一实例,实质是没有必要限制只有一个类的实例,而只是需要将那些不变的东西通过参数传入、静态化存储、临时一个对象汇总存储起来即可。

    3 工厂模式

    个人认为只是最有用的方法之一了。在类似CreateNew的方法中根据传入对象类型标识来new一个新的对象即可实现简单的工厂方法。

    //--------------------------------------------------------------------------------------------------
    //renderfactory.h
    //--------------------------------------------------------------------------------------------------
    #include "render.h"
    #include "user_render.h"
    #include 
    class RenderFactory
    {
    		public:
    				static IRender* CreateRender(const string& type) {
    						if (type == "user") {
    								return new UserRender();
    						}
    				}
    }
    

    这个简单工厂含有一个创建类的方法(具体create那种产品用switch处理一般)。
    当不仅create一种产品时,就是一个CreateProduct不够用,将其virtual化,派生出多个工厂来,在每个工厂类再具体生产其对应产品,这就是工厂方法
    当每一种产品本身需要再分各种类型时,需要将CreateProduct再细分为CreateProductA和CreateProductB,这样每个派生的工厂类就还有A、B两种具体产品类型。
    当然原来的一种Product也就需要改为继承模式了,由原Product派生两个ProductA 和 ProductB来。
    个人觉得最多用到工厂方法就可以了,
    比较常用的一种更优雅的方法是对象工厂(支持注册过程,用一个容器vector记录的类型标识与之对应的create方法函数,那个switch的创建流程着实吓人),见下文举例。

    用途

    提供更强大的类构造语义,并隐藏了子类的细节。

    举例

    //----------------------------------------------------------------------------------------
    // render.h
    //----------------------------------------------------------------------------------------
    #include <string>
    class IRender
    {
    	public:
    		virtual ~IRender() {}
    
    		// 可以在cpp中提供一个默认的实现,在派生类中IReand::LoadSrc(myname) 显式重写以重用代码
    		virtual bool LoadSrc(const string& filename);
    };
    
        //----------------------------------------------------------------------------------------
    //user_render.h
    //----------------------------------------------------------------------------------------
    class UserRender : public IRender
    {
    	public:
    		~UserRender() {};
    
    		bool LoadSrc(const string& myname) {
    			return true;
    		}
    
    		static IRender::Create() {
    			return new UserRender();
    		}
    };
    
        //----------------------------------------------------------------------------------------
    //renderfactory.h
    //----------------------------------------------------------------------------------------
    #include "render.h"
    #include <string>
    #include <map>
    
    class RenderFactory
    {
    	public:
    		typedef IRender *(*CreateCallback());
    		static void RegisterRender(const string& type, CreateCallback cb);
    		static void UnRegisterRender(const string& type);
    		static IRender* CreateRender(const string& type);
    
    	private:
    		typedef map<string, CreateCallback> CallbackMap;
    		static CallbackMap _Renders;
    }
    
    //----------------------------------------------------------------------------------------
    //renderfactory.cpp
    //----------------------------------------------------------------------------------------
    #include "renderfactory.h"
    RenderFactory::CallbackMap RenderFactory::_Renders;
    
    void RenderFactory::RegisterRender(const string& type, CreateCallback cb) 
    {
    	_Renders[type] = cb;
    }
    
    void IRender RenderFactory::UnRegisterRender(const string& type) 
    {
    	_Renders.erase(type);
    }
    
    void RenderFactory::CreateRender(const string& type) 
    {
    	CallbackMap::Iterator it = _Renders.find(type);
    	if (it == _Renders.end()) {
    		return NULL;
    	}
    	return it->second();
    }
    
    
    
    //----------------------------------------------------------------------------------------
    //client的使用
    //----------------------------------------------------------------------------------------
    int main() 
    {
    	//开始注册对象工厂车间
    	RenderFactory::RegisterRender("user", UserRender::Create);
    
    	//创建一个车间的实例
    	IRender *r = RenderFactory::CreateRender("user");
    	r->Render();
    
    	r->LoadSrc("/tmp/test.txt");
    
    	//对实例的清理动作
    	delete r;
    
    	return 0;
    }
    

    4 观察者模式

    抽象主题,抽象订阅者,然后绑定主题的多个订阅者关系;在主题变化时,执行notify,在notify中依次调用各个订阅者的update接口。

    用途

    避免双向依赖,降低耦合;一呼百应

    举例

    //--------------------------------------------------------------------------------------------------
    //i_subject.h
    //--------------------------------------------------------------------------------------------------
    #include <map>
    #include <vector>
    class ISubject
    {
    	public:
    		virtual ~ISubject() {}
    
    		virtual void Sub(int msg, IObserve* ob) {}
    		virtual void UnSub(int msg, IObserve* ob) {}
    		//在这里依次调用订阅者的update接口
                        virtual void Notify(int msg) {}
    
    	private:
    		typedef vector<IObserver*> 		ObVector;
    		//一个msg被多个订阅
    		typedef map<int, ObVector> 	ObMap;
    
    		ObMap _Obs;
    }
    
    //--------------------------------------------------------------------------------------------------
    //mysub.h
    //--------------------------------------------------------------------------------------------------
    class MySubject : public ISubject
    {
    	public:
    	enum Msg {ADD, DEL};
    }
    
    
    //--------------------------------------------------------------------------------------------------
    //i_observer.h
    //--------------------------------------------------------------------------------------------------
    class IObserve
    {
    	public:
    		virtual ~IObserve() {}
    		//声明为纯虚函数,但也可以有实现供子类调用IObserve::update(1)
    		virtual void update(int msg) = 0;
    }
    
    //--------------------------------------------------------------------------------------------------
    //my_observer.h
    //--------------------------------------------------------------------------------------------------
    class MyObserve : public IObserve
    {
    	public:
    		//含有入参,则最好explicit
    		explicit MyObserve(const string& str) : _name(str) {}
    		~MyObserve() {}
    
    		void update() {
    			prinf("change");
    		}
    	private:
    		string _name;
    }
    
    //--------------------------------------------------------------------------------------------------
    //client
    //--------------------------------------------------------------------------------------------------
    int main()
    {
    	//初始化一些对象
    	MySubject Sub;
    	MyObserve Ob1("ob1");
    	MyObserve Ob2("ob2");
    	//完成订阅绑定
    	sub.Sub(MySubject::ADD, &Ob1);
    	sub.Sub(MySubject::DEL, &Ob2);
    	//通知更改
    	sub.Notify(MySubject::ADD);
    	sub.Notify(MySubject::DEL);
    	
    	return 0;
    }
    

    5 Commad模式

    这个模式比较灵活,可以看成是高级点的callback机制。

    用途

    比如,在某个逻辑阶段,需要一个回调,回调需要参数,将参数之前设定好就OK,看起来像这样:

    举例

    	// 事务准备阶段
    	int flag = 1;
    	//...
    
    	// 事务回调阶段
    	Done(callback, flag)
    

    应用command模式后,将变成这样:

    	// 事务准备阶段
    	//Concreate 对应的flag = 1
    	Command pCmd = new ConcreateCommad();
    
    	// 事务回调阶段, 这里不会再理会flag了
    	// 所以客户端这里不需要关注command的接口等
    	Done(pCmd);
    

    6 state模式

    状态不同行为不同; state模式有些弊端,比如增加了类的个数,如果设计不好,则将代码混乱化了
    所以应用的时机需要仔细考虑。

    用途

    引用网络上的分析:

    1. 一个对象的行为取决于它的状态, 并且它必须在运行时刻根据状态改变它的行为。
    2. 代码中包含大量与对象状态有关的条件语句:一个操作中含有庞大的多分支的条件(if else(或switch case)语句,且这些分支依赖于该对象的状态。这个状态通常用一个或多个枚举常量表示。通常 , 有多个操作包含这一相同的条件结构。 State模式将每一个条件分支放入一个独立的类中。这使得你可以根据对象自身的情况将对象的状态作为一个对象,这一对象可以不依赖于其他对象而独立变化。

    举例

    
    class TransBase
    {
    	public:
    		TransBase(CDB* pdb) : _pdb(pdb) {}
    		~TransBase() {}
    		virtual void DBUpdate();
    
    	protected:
    	CDB* _pdb;
    }
    
    class TransInit : public TransHandle
    {
    	public:
    		TransInit(CDB* pdb) : TransHandle(pdb) {
    		}
    
    		void DBUpdate(Task* task) {
    			_db.(task, INIT)
    		}
    }
    
    class TransCommit : public TransHandle
    {
    	public:
    		TransInit(CDB* pdb) : TransHandle(pdb) {
    		}
    		void DBUpdate(Task* task) {
    			_db.(task, COMMIT)
    		}
    	CDB _db;
    }
    
    
    int main() 
    {
    	TransBase *pTrans = new TransCommit(new CDB());
    	pTrans->DBUpdate(new Task());
    	//...
    }
    

    更近一步,可以写个上下文将状态集成, 将状态的生成与转换写在context里面

    class TransContext
    {
    	enum STATE {INIT, CMMIT}
    
    	//考虑提供一个状态设置的方法,最好是在初始化时就全部new出来了
    	//网上有很多例子这里声明为static,则在客户端也可以使用这个去设置,但static要注意多线程下问题
    	//static TransInit	*pTransInit;
    	//static TransCommit	*pTransCommit;
            //不需要暴露,直接写SetCommitState(),这内部实现状态转移,这样不需要定义为static暴露出去,显得更加优越一些
            void SetCommitState(Task* task) {
                SetState(pTransCommit);
                pTrans->DBUpdate(task);
            }
            
    	TransInit	*pTransInit;
    	TransCommit	*pTransCommit;
    	TransContext(CDB& cdb) {
    		pTransInit		= new TransInit(cdb);
    		pTransCommand	= new TransCommand(cdb);
    	}
    
     private:
    	//这里也可以弄个标志传入,在函数内部switch,但暴露SetCommitState被暴露enum的state状态还要好些
    	void SetState(TransBase *pTrans) {
    		_pTrans = pTrans;
    	}
    
    	private:
    	TransBase *_pTrans;
    }
    
    int main()
    {
    	TransContext tc(new CDB());
    	Task commit_task;
            tc.SetCommitState(&commit_task);
    	
            //...
    }
    
    
  • 相关阅读:
    号称简明实用的django上手教程
    转先验概率、最大似然估计、贝叶斯估计、最大后验概率
    转基于概率的矩阵分解原理详解(PMF)
    转浅谈矩阵分解在推荐系统中的应用
    转推荐算法——基于矩阵分解的推荐算法
    代码生成器的需求
    兼容性的设计要求
    API设计的需求
    有关表单的需求梳理
    element-ui table 点击分页table滚到顶部
  • 原文地址:https://www.cnblogs.com/leby/p/5008036.html
Copyright © 2011-2022 走看看