网站建设合同 英文范文,wordpress 没有分类目录,捡个将军做男友啥网站能看,wto最新新闻本章内容
本章涵盖了一些与CAPI设计相关的设计模式和惯用法。 “设计模式(Design Pattern)”表示软件设计问题的一些通用解决方案。该术语来源于《设计模式#xff1a;可复用面向对象软件的基础》#xff08;Design Patterns: Elements of Reusable Object-Oriented Softwar…本章内容
本章涵盖了一些与CAPI设计相关的设计模式和惯用法。 “设计模式(Design Pattern)”表示软件设计问题的一些通用解决方案。该术语来源于《设计模式可复用面向对象软件的基础》Design Patterns: Elements of Reusable Object-Oriented Software 本书不会涵盖所有模式只讨论一些和API设计有关的。 还会涉及一些C的惯用法它们并非是真正的通用设计模式但却是CAPI设计的重要技巧。 本章讨论的技巧如下
1. Pimpl
Pimpl 意思是 Pointer to Implementation指向实现的指针。 Pimpl 不是严格意义上的“设计模式”而是受制于C语法特定限制的变通方案可以看作是 桥接 设计模式的一种特例
它主要解决的是C语法中的一个问题类的private成员其实只在内部使用但是在定义时还需要写在.h文件中公开。因此Pimpl 的做法是只在.h中定义一个指向实现的指针那就可以将一些private成员放在.cpp中的实现中这样就在.h文件中隐藏了private成员。 举例
使用Pimpl的方法
举例代码 .h文件中
class AutoTimer
{
public:explicit AutoTimer();~AutoTimer();
private:class Impl;Impl* mImpl;
}随后在.cpp中可以定义Impl类并实现具体的逻辑。
一个值得考虑的设计问题是Impl类中放置多少逻辑有以下选择
仅私有变量私有变量私有方法公有类的所有方法。而共有类的方法只是对Impl类中方法的简单包装
每种选择都适应于不同情况。一般情况下推荐 2 。
另外使用 Pimpl 时需要注意
virtual 函数不能放在Impl类中否则公有类的子类无法继承。Impl类可能还需要一个公有类的指针以方便其调用公有类的方法。
使用Pimpl的类的复制
使用 Pimpl 的类无法进行默认的复制因为复制出的对象会和原对象指向同一个Impl类变量。 这个问题有两种方法解决
禁止复制显示定义复制语义
Pimpl与智能指针
使用 Pimpl 时容易犯错的一点是构造时忘记分配它析构时忘记销毁它。 为此可以采用 智能指针 或者 作用域指针。
Pimpl的优点
信息隐藏。降低耦合加速编译更好的二进制兼容性。就算Impl类实现发生变化公用类的对象也不会改变二进制数据惰性分配可以选择只在需要时分配
Pimpl的缺点
额外的分配与销毁Impl类对象会增加性能开销。给开发者带来了不便很多函数调用时需要加上mImpl-如果Impl类需要调用公有类的方法也需要通过指针。编译器不能再检查constImpl类的成员更改公有类的const无法检查出。
2. 单例
“单例”设计模式确保一个类仅存在一个实例并提供唯一的全局访问点。
它可以看作是一种更优雅的全局变量但是相比全局变量有一些优点
确保这个类只能创建一个实例。控制对象的分配与销毁。可以支持线程安全。避免污染全局命名空间。
其基本实现很简单而本篇重点讨论的是
如何更健壮地实现。它也有些缺点但很多人都有滥用单例的趋向。为此这里也提供一些替代方法。
在C中实现单例
其基本实现很简单
class Singleton
{
public:static Singleton GetInstance();
};Singleton Singleton::GetInstance()
{static Singleton instance;return instance;
}有一些做法可以增加健壮性
声明私有默认构造函数防止用户创建新的实例。声明私有复制构造函数和赋值操作防止用户复制。声明私有析构函数防止用户删除。GetInstance()返回引用而非指针防止用户删除。
单例的线程安全
上面的 GetInstance() 并非线程安全的。
常规的处理方式是加互斥锁。但这样会增加开销。 要优化此类激进的加锁行为可以采用DCLPDouble Check Locking Pattern即加互斥锁前先判断instance是否存在。 但是DCLP不能保证任何编译器和处理器下都能正常工作。
所以也许你不应该尝试保证GetInstance()是线程安全的毕竟对使用C这样对并发缺乏内在支持的语言来说实现线程安全总会遇到这些困难。如果你真的需要它线程安全并且性能最高可以考虑避免惰性实例化模型即不要在需要他时再实例化比如
静态初始化在cpp文件中main函数调用之前调用GetInstance()。显式API初始化在一开始就调用GetInstance()调用时可以加互斥锁。而GetInstance()的内部就不用加互斥锁了。
替代方案依赖注入
初始化时传入需要的实例的指针而不是内部再使用GetInstance()获得实例。
替代方案单一状态
假设状态的初始化不需要控制或者不需要使用单例对象存储那么就可以使用“单一状态”即 类本身不保持是单例但是其所有成员(或者说“状态”)都是static。
替代方案会话上下文
《设计模式》作者指出单例有可能导致拙劣的设计。使用时候需要思考“单例”是否真的是正确的模式
需求是会变的未来有些对象可能会需要支持多个实例。
因此需要尽早考虑引入 “会话(session)” 或 “上下文(context)” 的概念。这是在强调使用单一的实例维护所有相关的状态而非使用多个单例。
3. 工厂模式
工厂模式是关于创建的设计模式本质上是构造函数的泛化可以回避C构造函数的限制
没有返回值。这样无法返回错误等其他信号。命名限制。这样相同参数的构造函数只能有一个。静态绑定创建。这样无法在运行时动态决定类型。不允许虚构造函数。限制同上。
从使用层面上看工厂方法仅是一个普通的方法调用时返回对应类的实例
class RendererFactory
{
public:IRenderer* CreateRenderer(string type)
}但这里的 IRenderer 是一个抽象基类(Abstract Base Class简称ABC)。抽象基类是包含纯虚函数的类不能被实例化。另外要注意抽象基类的析构函数需要声明为虚的。
这样用户可以使用参数动态决定要创建的类型。
另外作为扩展可以让工厂类提供注册函数这样用户可以自己添加新的类型
static void RegisterRender(string type, CreateCallback cb);4. API包装器
基于另一组API来包装接口是一项常见的API设计任务。比如你在维护一个遗留的代码库相比重构代码你更愿意封装一套新的更简洁的API以隐藏所有的底层遗留代码。
下面按照包装器层和原始接口的差异程度递增地划分
4.1 代理Proxy
一对一地将函数调用转发到具有相同形式的另一个接口。
案例
实现原始对象的惰性实例。实现对原始对象的访问控制。支持 “调试” 模式或者 “演习(DryRun)” 模式。保证原始对象的线程安全可以让多个代理对象共享相同的原始对象。应对原始对象将来被修改的情况。
4.2 适配器
一对一地将接口转换为一个兼容但是不完全相同的另一个接口。
优点
可以转换数据类型。强制API始终保持一致性。包装API的依赖库
4.3 外观模式
外观模式能够为一组类提供简化的接口。它实际上定义了一个更高层次的接口使得底层类更易于使用且对用户隐藏。 一个例子用一个“酒店助手类”简化了“预定房间”、“预定晚餐”、“预定出租车”等事务。
用途
隐藏遗留代码。创建更便捷的API。支持简化功能或替代功能的API。
5. 观察者模式
观察者模式为了解决这样的问题 实现复杂的任务通常需要多个对象一起合作完成。为了让A可以调用B较为简单的方法就是A.cpp包含B.h但是这样产生了编译时依赖迫使想要复用A时也必须引入B。
观察者模式就是 “发布/订阅” 范式的一个具体实例。
实现观察者模式的典型做法是引入两个概念
Subject主体也就是发布者。Observer观察者也就是订阅者。
代码上Subject仅知道Observer接口类即IObserver他将维护IObserver列表
class IObserver
{virtual void Update() 0;
}class ISubject
{
std::vectorIObserver* ObserverList
}随后观察者通过继承IObserver来实现观察者对象。
然后在使用时Subject就可以订阅(Subscribe)若干Observer并在需要的时候通知(Notify)它们。
这样Subject和Observer就没有编译时依赖关系它们的关系是运行时动态创建的。