-
设计原则:高内聚低耦合,开闭原则。 两大基础设计原则 在说面向对象设计的六大原则之前,我们先来说下程序设计的原则:模块内高内聚,模块间低耦合。我们在面向对象时只需把类看成模块,那么就容易理解封装等了。 说是七大原则,这里我先提出来一个:对扩展开放,对修改关闭。 为啥这么说,因为我们都知道软件是要改的。对扩展开放保证了可以增加功能,像泛型啦这些。对修改关闭保证了像前的兼容性,jdk7兼容jdk6这样。所以开闭原则围绕软件的整个生命周期。 从基础原则出发,产生六个具体的原则: 1.单一职责(一个方法或一个类只做一件事,为了模块内高内聚) 2.迪米特法则(也叫最少知道原则,为了模块间低耦合) 3.里氏替换(就是继承原则,子类可以无缝替代父类。很好的符合了开闭原则) 4.依赖倒置(类之间的依赖通过接口实现,低耦合的同时对扩展开放) 5.接口隔离(即把单个复杂接口拆分为多个独立接口,与上条共同实现面向接口编程) 6.合成复用原则(即尽量使用合成/聚合的方式,而不是使用继承。主要为了防止继承滥用而导致的类之间耦合严重。记住只有符合继承原则时才用继承) 设计模式 我觉得程序员最好的沟通方式是代码,所以每个设计模式都是一个例子。所有例子都很方便,可以复制直接运行。因为对java熟悉,所以下面设计模式例子都是用java语言来实现的。 创建型模式(IOC:控制反转,就是创建分离的集大成) 1.Singleton:单例模式(全局只要一个实例) 2.Prototype:原型模式(通过拷贝原对象创建新对象) 3.Factory Method:工厂方法模式(对象创建可控,隐藏具体类名等实现解耦) 4.Abstract Factory:抽象工厂模式(解决对象与其属性匹配的工厂模式) 5.Builder:建造者模式(封装降低耦合,生成的对象与构造顺序无关) 创建型模式的五种有各自的使用环境,单例和原型比较简单就不说了,工厂方法模式和建造者模式,都是封装和降低耦合有啥不同呢,其实工厂方法关注的是一个类有多个子类的对象创建(汽车类的各种品牌),而建造者模式关注的是属性较多的对象创建(能达到过程无关)。而抽象工厂模式关注的是对象和属性及属性与属性的匹配关系(如奥迪汽车与其发动机及空调的匹配)。 结构型模式(对象的组成以及对象之间的依赖关系) 1.Adapter:适配器模式(适配不同接口和类,一般解决历史遗留问题) 2.Decorator:装饰器模式(比继承更灵活,可用排列组合形成多种扩展类) 3.Proxy:代理模式(可以给类的每个方法增加逻辑,如身份验证) 4.Facade:外观模式(对模块或产品的封装,降低耦合) 5.Bridge:桥接模式(就是接口模式,抽象与实现分离) 6.Plyweight:享元模式(相同对象的重用) 7.Composite:组合模式(整体和部分相同时,如文件夹包含文件夹) 我们可以看到适配器模式、装饰器模式、代理模式都可以用包装对象来实现(把对象作为一个属性放在用的对象里),所以模式关注的并不是实现,而是解决的问题。模式更多体现的是类与类之间的逻辑关系,比如代理模式和装饰器模式很像。但从字面就知道,代理是访问不了实际工作对象的,这是他们的区别。 行为型模式(即方法及其调用关系) 1.Strategy:策略模式(提供功能不同实现方式,且实现可选) 2.Template Method:模板方法模式(相同流程用一个模板方法) 3.Observer:观察者模式(用订阅-发布实现的被观察者变化时回调) 4.Iterator:迭代器模式(一种内部实现无关的集合遍历模式) 5.Chain of Responsibility:责任链模式(事件处理的分层结构产生的责任链条) 6.Command:命令模式(将命令者与被命令者分离) 7.Memento:备忘录模式(需要撤销与恢复操作时使用) 8.State:状态模式 (当对象两种状态差别很大时使用) 9.Visitor:访问者模式 (当对同一对象有多种不同操作时使用) 10.Mediator:中介者模式(以中介为中心,将网状关系变成星型关系) 11.Interpreter:解释器模式(常用于纯文本的表达式执行) 写完设计模式之后感觉设计模式更多的一种逻辑关系,如果代码中有这种逻辑关系就可以用了。记得需要时候再用,不能为了设计模式而设计模式。没有什么就是好的,最主要用起来舒服吧。 ———————————————— 原文链接:https://blog.csdn.net/wanyouzhi/article/details/77248710
-
什么是设计模式? 设计模式是指在软件开发中,经过验证的,用于解决在特定环境下,重复出现的,特定问题的解决方案。面向对象设计模式通常以类或者对象来描述其中的关系和相互作用,但不涉及用来完成应用程序的特定类别或对象。设计模式能使不稳定依赖于相对稳定、具体依赖于相对抽象,避免会引起麻烦的紧耦合,以增强软件设计面对并适应变化的能力。 设计模式的基本原则 1. 依赖倒置原则 高层模块不应该依赖低层模块,二者都应该依赖抽象; 抽象不应该依赖具体实现,具体实现应该依赖于抽象; 举个例子,自动驾驶系统公司是高层,汽⻋生产厂商为低层,它们不应该互相依赖,一方变动另一方也会跟着变动;而应该抽象一个自动驾驶行业标准,高层和低层都依赖它;这样以来就解耦了两方的变动;自动驾驶系统、汽⻋生产厂商都是具体实现,它们应该都依赖自动驾驶行业标准(抽象)。 2. 开放封闭原则 一个类应该对扩展开放,对修改关闭。也就是对一个类尽量做扩展而不是修改,扩展一般是用继承或者组合的方式。 3. 面向接口编程 不将变量类型声明为某个特定的具体类,而是声明为某个接口。比如某个类成员是另一个类的具体对象,那么应该改成指向这个类的指针,因为根据多态原理,这个类指针实际上成为了一个接口。 客户程序无需获知对象的具体类型,只需要知道对象所具有的接口。 减少系统中各部分的依赖关系,从而实现“高内聚、松耦合”的类型设计方案。 4. 封装变化点 将稳定点和变化点分离,扩展修改变化点;让稳定点与变化点的实现层次分离。 5. 单一职责原则 一个类应该仅有一个引起它变化的原因。 6. 里式替换原则 子类型必须能够替换掉它的父类型;主要出现在子类覆盖父类实现,原来使用父类型的程序可 能出现错误;覆盖了父类方法却没实现父类方法的职责。也就是子类如果声明了父类存在的函数 就要把功能实现完全,父类有的子类也一定要有。 7. 接口隔离原则 不应该强迫客户依赖于他们不用的方法。 一般用于处理一个类拥有比较多的接口,而这些接口涉及到很多职责。 所以要正确使用public,protected,private这三种权限。 8. 对象组合优于类继承 继承耦合度高,组合耦合度低(继承不能换爹,组合可以换爹)。 如何找到设计模式 从重构中获得: 1. 静态转变为动态 2. 早绑定转为晚绑定 3. 继承转为组合 4. 编译时依赖转变为运行时依赖 5. 紧耦合转变为松耦合 掌握设计模式的原则比具体的设计模式更重要,只有当你对业务非常熟悉的时候,才能信手拈来的写出适合的设计模式。 常见的设计模式和使用场景 1. 模板模式 定义一个操作中的算法的⻣架 ,而将一些步骤延迟到子类中。 Template Method使得子类可以不 改变一个算法的结构即可重定义该算法的某些特定步骤。 下面来看一个例子: class IGame { public: Play() { Process1(); Process2(); Process3(); Process4(); } protected: virtual void Process1(){} virtual void Process2(){} virtual void Process3(){} virtual void Process4(){} }; class Game1:public IGame { protected: virtual void Process2(){} virtual void Process4(){} }; class Game2:public IGame { protected: virtual void Process1(){} virtual void Process3(){} }; 某个游戏IGame类有固定的流程,然后Game1和Game2分别继承自IGame,但是在具体某个步骤上各有不同,因此将Process1~4写成虚函数,由各个子类自己去实现。 这样写的前提当然是主框架流程是固定的,Play函数对外开放给用户使用,但是具体流程的函数是protected,提供子类去修改。这体现了接口隔离原则。 流程的接口在基类都抽象成了虚函数,由各个子类具体实现,这体现了依赖倒置原则。 2. 观察者模式 观察者模式在分布式架构中应用广泛,比如actor框架,skynet,redis,zoomkeeper,订阅和发布等。 观察者模式定义对象间的一种一对多(变化)的依赖关系,以便当一个对象(Subject)的状态发生改变时,所有 依赖于它的对象都得到通知并自动更新。 // 终端基类 class ITerminal { public: virtual void show(int data){} }; class DataCenter { public: void Attach(ITerminal* ob){} // 注册绑定 void Detach(ITerminal* ob){} // 解绑 void Notify() { for(auto it = obs.begin(); it != obs.end(); ++it) { it->show(); } } protected: virtual int GetData(){return 0;} // 获取数据 private: std::vector<ITerminal*> obs; }; class Terminal1:public ITerminal { public: void show(int data){} } class Terminal2:public ITerminal { public: void show(int data){} } 上面是一个发布和订阅的简单例子,假设有个数据中心提供数据给不同的终端展示,数据中心是一个稳定点,不同的终端设备是变化点,因此将终端封装成基类,提供一个展示的接口,不同的设备继承去实现不同的展示方式。 数据中心用一个vector或者set把注册过来的终端记录起来,需要展示的时候通过notify接口逐个通知展示。这样数据中心不必关注具体有哪些设备订阅了数据,是否订阅的操作交给了各个终端设备上。 3. 策略模式 定义一系列算法,把它们一个个封装起来,并且使它们可互相替换。该模式使得算法可独立于使用 它的客户程序而变化。 策略模式提供了一系列可重用的算法,从而可以使得类型在运行时方便地根据需要在各个算法 之间进行切换; 策略模式消除了条件判断语句;就是在解耦合; 充分体现了开闭原则;单一职责; 其本质是分离算法,选择实现。 来看一个简单的例子,假设某电商网站一件商品在不同节假日的价格算法使不同的,可能会这么写: enum VacationEnum { chunjie, // 春节 wuyi; // 五一 qixi; // 七夕 guoqing; // 国庆 }; class Context { }; class Promotion { VacationEnum vac; public: double calc() { if(vac == chunjie){} else if(vac == wuyi){} else if(vac == qixi){} else if(vac == guoqing){} } }; 这样写并不符合设计模式的单一职责原则和开放封闭原则。所以可以改成这样: class Context {}; // 变化的 扩展的 class BaseVac { public: virtual calc(Context &ctx){} }; class Vacchunjie: public BaseVac { public: virtual calc(Context &ctx){} }; class Vacwuyi: public BaseVac { public: virtual calc(Context &ctx){} }; class Vacxiqi: public BaseVac { public: virtual calc(Context &ctx){} }; // 稳定的 单一职责的 class Promotion { public: Promotion(BaseVac *ss):s(ss){} double calc(Context &ctx) { s->calc(ctx); } private: BaseVac *s; }; int main() { BaseVac *v = new Vacxiqi(); Promotion *p = new Promotion(v); return 0; } 把变化点(不同节假日的不同算法)隔离出去,只保留单一的职责:获取价格。 这样做非常符合设计模式的原则,但是前提是对业务非常熟悉,因为别人来扩展维护的话,必须先了解之前已有的扩展有哪些。 4. 责任链模式 使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成 一条链,并沿着这条链传递请求,直到有一个对象处理它为止。 这个模式的一些要点: a.解耦请求方和处理方,请求方不知道请求是如何被处理,处理方的组成是由相互独立的子处理构成,子处理流程通过链表的方式连接,子处理请求可以按任意顺序组合; b.责任链请求强调请求最终由一个子处理流程处理;通过了各个子处理条件判断; c.责任链扩展就是功能链,功能链强调的是,一个请求依次经由功能链中的子处理流程处理; d.充分体现了单一职责原则;将职责以及职责顺序运行进行抽象,那么职责变化可以任意扩展, 同时职责顺序也可以任意扩展; 本质是分离职责,动态组合。 责任链模式是比较过时的,在c语言里用的比较多,c++里有更好的数据结构(list)可以代替这个模式。 来看一个具体例子,公司请假审批规则是小于3天只给人事审批,大于3天且小于5天给经理审批,5天以上的给老板审批。下面是第一个版本的代码: class Context { public: std::string name; int day; }; class LeaveRequest { public: void HandleRequest(const Context &cxt) { if(cxt.day < 3) { HandleByHR(cxt); } else if(cxt.day < 5); { HandleByMgr(cxt); } else { HandleByBoss(cxt); } } private: // 接口隔离原则 void HandleByHR(const Context &cxt){} void HandleByMgr(const Context &cxt){} void HandleByBoss(const Context &cxt){} }; 怎么重构这个代码,从封装变化点的原则来看,先把HandleRequest里的各种条件拆分出来,然后新增一个类成员指针指向上一级,子类只需要判断自己是否应该处理,不能处理的就通过指针转移到上级去处理。 class Context { public: std::string name; int day; }; class IHandler { public: virtual ~ IHandler(){} void SetNext(IHandler *n){bext = n;} virtual bool HandleRequest(const Context &cxt) { if(CanHandle(cxt)) { } else if(GetNext()) { GetNext()-> HandleRequest(cxt); } else { // error } } protected: virtual bool CanHandle(const Context &cxt){} IHandler * GetNext(){return next;} private: IHandler *next; }; class HandleByMainProgram : public IHandler { protected: virtual bool HandleRequest(const Context &ctx){ // } virtual bool CanHandle() { // } }; class HandleByProjMgr : public IHandler { protected: virtual bool HandleRequest(const Context &ctx){ // } virtual bool CanHandle() { // } }; class HandleByBoss : public IHandler { public: virtual bool HandleRequest(const Context &ctx){ // } protected: virtual bool CanHandle() { // } }; int main () { IHandler * h1 = new MainProgram(); IHandler * h2 = new HandleByProjMgr(); IHandler * h3 = new HandleByBoss(); h1->SetNextHandler(h2); h2->SetNextHandler(h3); Context ctx; h1->handle(ctx); return 0; } 把不同部门的职责拆出来作为子类继承,主流程封装进基类里,上面这个版本同时使用了模板模式和责任链模式。 子类的接口都是protected的,这体现了接口隔离原则和依赖倒置原则。 责任链模式可以变形成功能链模式,比如nginx阶段处理,不同之处在于责任链每次只有一个模块会处理,功能链是按顺序执行不同的模块。 5. 装饰器模式 动态地给一个对象增加一些额外的职责。就增加功能而言,装饰器模式比生成子类更为灵活。 这个模式看起来会和责任链模式很像。 通过采用组合而非继承的手法, 装饰器模式实现了在运行时动态扩展对象功能的能力,而且可以根据需要扩展多个功能。 避免了使用继承带来的“灵活性差”和“多子类衍生问题”。 不是解决“多子类衍生的多继承”问题,而是解决“父类在多个方向上的扩展功能”问题; 装饰器模式把一系列复杂的功能分散到每个装饰器当中,一般一个装饰器只实现一个功能,实现复用装饰器的功能; 装饰器模式本质是动态组合。 举个例子,普通员工有销售奖金,累计奖金,部⻔经理除此之外还有团队奖金;后面可能会添加环比增⻓奖 金,同时可能针对不同的职位产生不同的奖金组合。那么代码可以这样写: class Context { public: bool isMgr; // User user; // double groupsale; }; class Bonus { public: double CalcBonus(Context &ctx) { double bonus = 0.0; bonus += CalcMonthBonus(ctx); bonus += CalcSumBonus(ctx); if (ctx.isMgr) { bonus += CalcGroupBonus(ctx); } return bonus; } private: double CalcMonthBonus(Context &ctx) { double bonus/* = */; return bonus; } double CalcSumBonus(Context &ctx) { double bonus/* = */; return bonus; } double CalcGroupBonus(Context &ctx) { double bonus/* = */; return bonus; } }; int main() { Context ctx; // 设置 ctx Bonus *bonus = new Bonus; bonus->CalcBonus(ctx); } 重构成装饰器模式后的代码: class Context { public: bool isMgr; // User user; // double groupsale; }; // 试着从职责出发,将职责抽象出来 class CalcBonus { public: CalcBonus(CalcBonus * c = nullptr) {} virtual double Calc(Context &ctx) { return 0.0; // 基本工资 } virtual ~CalcBonus() {} protected: CalcBonus* cc; }; class CalcMonthBonus : public CalcBonus { public: CalcMonthBonus(CalcBonus * c) : cc(c) {} virtual double Calc(Context &ctx) { double mbonus /*= 计算流程忽略*/; return mbonus + cc->Calc(ctx); } }; class CalcSumBonus : public CalcBonus { public: CalcSumBonus(CalcBonus * c) : cc(c) {} virtual double Calc(Context &ctx) { double sbonus /*= 计算流程忽略*/; return sbonus + cc->Calc(ctx); } }; class CalcGroupBonus : public CalcBonus { public: CalcGroupBonus(CalcBonus * c) : cc(c) {} virtual double Calc(Context &ctx) { double gbnonus /*= 计算流程忽略*/; return gbnonus + cc->Calc(ctx); } }; class CalcCycleBonus : public CalcBonus { public: CalcGroupBonus(CalcBonus * c) : cc(c) {} virtual double Calc(Context &ctx) { double gbnonus /*= 计算流程忽略*/; return gbnonus + cc->Calc(ctx); } }; int main() { // 1. 普通员工 Context ctx1; CalcBonus *base = new CalcBonus(); CalcBonus *cb1 = new CalcMonthBonus(base); CalcBonus *cb2 = new CalcSumBonus(cb1); cb2->Calc(ctx1); // 2. 部门经理 Context ctx2; CalcBonus *cb3 = new CalcGroupBonus(cb2); cb3->Calc(ctx2); } 在计算奖金的形式上,有点像递归,因为构造函数里设置的基类成员是给用户自己设置的,可以 灵活组合。而不是第一个版本那样用ifelse去判断是否要添加奖金 ,这样也很容易扩展和修改。 打个比方,责任链的模式看起来就像一个链表的结构,而装饰器模式看起来像一个俄罗斯套娃。 6. 单例模式 保证一个类仅有一个实例,并提供一个该实例的全局访问点。 先了解一个知识点,由C/C++编译的程序占用的内存分为以下几个部分: 1、栈区(stack): 由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。其 操作方式类似于数据结构中的栈。 2、堆区(heap):一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。注意它与数据结构中的堆是两回事,分配方式倒是类似于链表。 3、全局区(静态区)(static):全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域, 未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。 程序结束后由系统释放。 4、文字常量区 :常量字符串就是放在这里的。 程序结束后由系统释放 5、程序代码区:存放函数体的二进制代码。 下面来看看几种实现单例模式的例子。 版本一: #include <mutex> class Singleton { // 懒汉模式 lazy load public: static Singleton * GetInstance() { //std::lock_guard<std::mutex> lock(_mutex); // 这里加锁粒度太大 切换线程开销大 if (_instance == nullptr) { std::lock_guard<std::mutex> lock(_mutex); // 在这里加锁 if (_instance == nullptr) // 双重判断 { _instance = new Singleton(); atexit(Destructor); } } return _instance; } private: static void Destructor() { if (nullptr != _instance) { delete _instance; _instance = nullptr; } } Singleton(){} //构造 Singleton(const Singleton &cpy){} //拷⻉构造 Singleton& operator=(const Singleton&) {} static Singleton * _instance; static std::mutex _mutex; }; Singleton* Singleton::_instance = nullptr;//静态成员需要初始化 std::mutex Singleton::_mutex; //互斥锁初始化 这个例子有几个需要注意的点,第一个是加锁的粒度,当单例指针是空的时候再去加锁,减少锁粒度可以减少线程切换的开销。第二个是双重判断,防止多线程重入导致多次分配的问题。第三这是个懒汉模式,用到的时候才决定分配初始化。 但是这个例子有个问题。 c++的new操作里分为了三个步骤:分配内存,调用构造函数,赋值操作。在多线程环境下,cpu会进行指令重排,前面三个步骤可能执行起来是132的顺序。所以可能会出现某个线程判断单例指针不为空,但是还没调用构造函数,就立即返回,最后导致程序崩溃。所以上面这个例子还是有问题的。(如果在判断前就加锁是没问题的,代价是增加了开销) 版本二: #include <mutex> #include <atomic> class Singleton { public: static Singleton * GetInstance() { Singleton* tmp = _instance.load(std::memory_order_relaxed); //取出原子对象 std::atomic_thread_fence(std::memory_order_acquire);//获取内存屏障 if (tmp == nullptr) { std::lock_guard<std::mutex> lock(_mutex); tmp = _instance.load(std::memory_order_relaxed); if (tmp == nullptr) { tmp = new Singleton; std::atomic_thread_fence(std::memory_order_release);//释放内存屏障 _instance.store(tmp, std::memory_order_relaxed);// 赋值给原子对象 atexit(Destructor); } } return tmp; } private: static void Destructor() { Singleton* tmp = _instance.load(std::memory_order_relaxed); if (nullptr != tmp) { delete tmp; } } Singleton(){} Singleton(const Singleton&) {} Singleton& operator=(const Singleton&) {} static std::atomic<Singleton*> _instance; static std::mutex _mutex; }; std::atomic<Singleton*> Singleton::_instance;//原子对象 静态成员需要初始化 std::mutex Singleton::_mutex; //互斥锁初始化 // g++ Singleton.cpp -o singleton -std=c++11 上面代码用c++11特性内存屏障来解决cpu指令重排的问题。 版本三: class Singleton { public: ~Singleton(){} static Singleton& GetInstance() { static Singleton instance; return instance; } private: Singleton(){} Singleton(const Singleton&) {} Singleton& operator=(const Singleton&) {} }; // g++ Singleton.cpp -o singleton -std=c++11 这个版本需要在c++11或者更高级版本的编译后才能用。 c++11 magic static 特性:如果当变量在初始化的时候,并发同时进入声明语句,并发线程将会阻塞等待初始化结束。这就解决了多线程场景带来的问题。 该版本的优点: a. 利用静态局部变量特性,延迟加载; b. 利用静态局部变量特性,系统自动回收内存,自动调用析构函数; c. 静态局部变量初始化时,没有 new 操作带来的cpu指令reorder操作; d. c++11 静态局部变量初始化时,具备线程安全; 版本四: template<typename T> class Singleton { public: static T& GetInstance() { static T instance; // 这里要初始化DesignPattern,需要调用DesignPattern 构造函数,同时会调用父类的构造函数。 return instance; } protected: virtual ~Singleton() {} Singleton() {} // protected修饰构造函数,才能让别人继承 Singleton(const Singleton&) {} Singleton& operator =(const Singleton&) {} }; class DesignPattern : public Singleton<DesignPattern> { friend class Singleton<DesignPattern>; // friend 能让 Singleton<T> 访 问到 DesignPattern构造函数 private: DesignPattern(){} DesignPattern(const DesignPattern&) {} DesignPattern& operator=(const DesignPattern&) {} } 这个版本将单例模式封装成了模板,需要注意两个地方: a.单例模板类的构造函数用protected修饰是为了它本身能被继承。 b. 单例模板里的GetInstance接口要获取类型T的构造函数,因此继承这个模板的类必须声明模板类为友元。 7. 工厂方法模式 定义一个用于创建对象的接口,让子类决定实例化哪一个类。Factory Method使得一个类的实例化 延迟到子类。 一般这些业务会使用工厂模式:解决创建过程比较复杂,希望对外隐藏这些细节;比如连接池,线程池; 隐藏对象真实类型; 对象创建会有很多参数来决定如何创建; 创建对象有复杂的依赖关系; 线程池的初始化需要由机器的内核数来决定线程数量,但是用户并不需要关心内核数,用户只需要获取到可用的线程就行。因此可以用工厂模式。 其本质是延迟到子类来选择实现; 举个例子,实现一个导出数据的接口,让客户选择数据的导出方式: #include <string> // 实现导出数据的接口, 导出数据的格式包含 xml,json,文本格式txt 后面可能扩展excel格式csv class IExport { public: virtual bool Export(const std::string &data) = 0; virtual ~IExport(){} }; class ExportXml : public IExport { public: virtual bool Export(const std::string &data) { return true; } }; class ExportJson : public IExport { public: virtual bool Export(const std::string &data) { return true; } }; class ExportTxt : public IExport { public: virtual bool Export(const std::string &data) { return true; } }; // =====1 int main() { std::string choose/* = */; if (choose == "txt") { IExport *e = new ExportTxt(); e->Export("hello world"); } else if (choose == "json") { IExport *e = new ExportJson(); e->Export("hello world"); } else if (choose == "xml") { IExport *e = new ExportXml(); e->Export("hello world"); } } 对于数据导出有多种不同格式的类,在业务代码层面上,第一个版本里需要使用者区分判断多种格式,我们希望把这种复杂的创建操作封装起来,因此创建一个工厂类,这个类绑定了导出数据的操作和指向具体数据格式的类成员,使用者只需要创建某个具体的工厂子类,调用导出的接口就完成任务了。 重构后的代码: #include <string> // 实现导出数据的接口, 导出数据的格式包含 xml,json,文本格式txt 后面可能扩展excel格式csv class IExport { public: virtual bool Export(const std::string &data) = 0; virtual ~IExport(){} }; class ExportXml : public IExport { public: virtual bool Export(const std::string &data) { return true; } }; class ExportJson : public IExport { public: virtual bool Export(const std::string &data) { return true; } }; class ExportTxt : public IExport { public: virtual bool Export(const std::string &data) { return true; } }; class ExportCSV : public IExport { public: virtual bool Export(const std::string &data) { return true; } }; class IExportFactory { public: IExportFactory() { _export = nullptr; } virtual ~IExportFactory() { if (_export) { delete _export; _export = nullptr; } } bool Export(const std::string &data) { if (_export == nullptr) { _export = NewExport(); } return _export->Export(data); } protected: virtual IExport * NewExport(/* ... */) = 0; private: IExport* _export; }; class ExportXmlFactory : public IExportFactory { protected: virtual IExport * NewExport(/* ... */) { // 可能有其它操作,或者许多参数 IExport * temp = new ExportXml(); // 可能之后有什么操作 return temp; } }; class ExportJsonFactory : public IExportFactory { protected: virtual IExport * NewExport(/* ... */) { // 可能有其它操作,或者许多参数 IExport * temp = new ExportJson; // 可能之后有什么操作 return temp; } }; class ExportTxtFactory : public IExportFactory { protected: IExport * NewExport(/* ... */) { // 可能有其它操作,或者许多参数 IExport * temp = new ExportTxt; // 可能之后有什么操作 return temp; } }; class ExportCSVFactory : public IExportFactory { protected: virtual IExport * NewExport(/* ... */) { // 可能有其它操作,或者许多参数 IExport * temp = new ExportCSV; // 可能之后有什么操作 return temp; } }; int main () { IExportFactory *factory = new ExportTxtFactory(); factory->Export("hello world"); return 0; } 8. 抽象工厂模式 提供一个接口,让该接口负责创建一系列“相关或者相互依赖的对象”,无需指定它们具体的类。 抽象工厂类是基于工厂类实现的,实际上是一回事,抽象工厂类拥有更多的更复杂的操作接口,但是对于使用者来说,仍然只需要创建时指定某个子工厂类,无需关心具体的操作内容。 举个例子,实现一个拥有导出导入数据的接口,让客户选择数据的导出导入方式: #include <string> // 实现导出数据的接口, 导出数据的格式包含 xml,json,文本格式txt 后面可能扩展excel格式csv class IExport { public: virtual bool Export(const std::string &data) = 0; virtual ~IExport(){} }; class ExportXml : public IExport { public: virtual bool Export(const std::string &data) { return true; } }; class ExportJson : public IExport { public: virtual bool Export(const std::string &data) { return true; } }; class ExportTxt : public IExport { public: virtual bool Export(const std::string &data) { return true; } }; class ExportCSV : public IExport { public: virtual bool Export(const std::string &data) { return true; } }; class IImport { public: virtual bool Import(const std::string &data) = 0; virtual ~IImport(){} }; class ImportXml : public IImport { public: virtual bool Import(const std::string &data) { return true; } }; class ImportJson : public IImport { public: virtual bool Import(const std::string &data) { return true; } }; class ImportTxt : public IImport { public: virtual bool Import(const std::string &data) { return true; } }; class ImportCSV : public IImport { public: virtual bool Import(const std::string &data) { // .... return true; } }; class IDataApiFactory { public: IDataApiFactory() { _export = nullptr; _import = nullptr; } virtual ~IDataApiFactory() { if (_export) { delete _export; _export = nullptr; } if (_import) { delete _import; _import = nullptr; } } bool Export(const std::string &data) { if (_export == nullptr) { _export = NewExport(); } return _export->Export(data); } bool Import(const std::string &data) { if (_import == nullptr) { _import = NewImport(); } return _import->Import(data); } protected: virtual IExport * NewExport(/* ... */) = 0; virtual IImport * NewImport(/* ... */) = 0; private: IExport *_export; IImport *_import; }; class XmlApiFactory : public IDataApiFactory { protected: virtual IExport * NewExport(/* ... */) { // 可能有其它操作,或者许多参数 IExport * temp = new ExportXml; // 可能之后有什么操作 return temp; } virtual IImport * NewImport(/* ... */) { // 可能有其它操作,或者许多参数 IImport * temp = new ImportXml; // 可能之后有什么操作 return temp; } }; class JsonApiFactory : public IDataApiFactory { protected: virtual IExport * NewExport(/* ... */) { // 可能有其它操作,或者许多参数 IExport * temp = new ExportJson; // 可能之后有什么操作 return temp; } virtual IImport * NewImport(/* ... */) { // 可能有其它操作,或者许多参数 IImport * temp = new ImportJson; // 可能之后有什么操作 return temp; } }; class TxtApiFactory : public IDataApiFactory { protected: virtual IExport * NewExport(/* ... */) { // 可能有其它操作,或者许多参数 IExport * temp = new ExportTxt; // 可能之后有什么操作 return temp; } virtual IImport * NewImport(/* ... */) { // 可能有其它操作,或者许多参数 IImport * temp = new ImportTxt; // 可能之后有什么操作 return temp; } }; class CSVApiFactory : public IDataApiFactory { protected: virtual IExport * NewExport(/* ... */) { // 可能有其它操作,或者许多参数 IExport * temp = new ExportCSV; // 可能之后有什么操作 return temp; } virtual IImport * NewImport(/* ... */) { // 可能有其它操作,或者许多参数 IImport * temp = new ImportCSV; // 可能之后有什么操作 return temp; } }; int main () { IDataApiFactory *factory = new CSVApiFactory(); factory->Import("hello world"); factory->Export("hello world"); return 0; } 9. 适配器模式 将一个类的接口转换成客户希望的另一个接口。Adapter模式使得原本由于接口不兼容而不能一起 工作的那些类可以一起工作。 原来的接口是稳定的,新的外来的需求是变化的,那么可以通过继承原来的接口,让原来的接 口继续保持稳定,在子类通过组合的方式来扩展功能。 其本质是转换匹配,复用功能。 举个日志系统的例子,原来是通过写磁盘的方式进行存储,后来因为查询不便,需要额外添加往数据库写日志 的功能(写文件和数据库并存): #include <string> #include <vector> using namespace std; class LogSys { public: LogSys() {} void WriteLog(const vector<string> &) { // ... 日志id 时间戳 服务器id 具体日志内容 roleid } vector<string>& ReadLog() { // ... vector<string> data /* = ...*/; return data; } }; class DB; // 面向接口编程 而不是具体类 强依赖 耦合性高 mysql mongo class LogSysEx : public LogSys { public: LogSysEx(DB *db) : _db(db) {} void AddLog(const vector<string> &data) { LogSys::WriteLog(data); /* 这里调用 _db 的方法将 data 数据存储到数据库 */ } void DelLog(const int logid) { vector<string>& data = LogSys::ReadLog(); // 从 vector<string> 中删除 logid的日志 LogSys::WriteLog(data); // 调用 _db 的方法将 logid的日志删除 } void UpdateLog(const int logid, const string &udt) { vector<string>& data = LogSys::ReadLog(); // 从 vector<string> 中更新 logid的日志 udt LogSys::WriteLog(data); // 调用 _db 的方法将 logid的日志更改 } string& LocateLog(const int logid) { vector<string>& data = LogSys::ReadLog(); string log1 /* = from log file*/; string log2 /* = from db */; string temp = log1 + ";" + log2; return temp; } private: DB* _db; }; 咱就是说在对一个类进行扩展修改的时候,尽量不要动原来的代码,而是通过继承组合的方式创建新的类,在这个新的类里实现新功能和包含旧功能。 10. 代理模式 代理模式是为其他对象提供一种代理以控制对这对象的访问。 比如远程代理(隐藏一个对象存在不同的地址空间的事实),虚代理(延迟加载lazyload),保护 代理(在代理前后做额外操作,权限管理,引用计数等); 在分布式系统中,actor模型(skynet)等会常用到代理模式。 其本质是控制对象访问。 举个例子,在有些系统中,为了某些对象的纯粹性,只进行了功能相关封装(稳定点),后期添加了其他功能 需要对该对象进行额外操作(变化点),为了隔离变化点(也就是不直接在稳定点进行修改,这样 会让稳定点也变得不稳定),可以抽象一层代理层: class ISubject { public: virtual void Handle() = 0; virtual ~ISubject() {} }; // 该类在当前进程,也可能在其他进程当中 class RealSubject : public ISubject { public: virtual void Handle() { // 只完成功能相关的操作,不做其他模块的判断 } }; // 在当前进程当中 只会在某个模块中使用 class Proxy1 : public ISubject { public: Proxy1(ISubject *subject) : _subject(subject) {} virtual void Handle() { // 在访问 RealSubject 之前做一些处理 //if (不满足条件) // return; _subject->Handle(); count++; // 在访问 RealSubject 之后做一些处理 } private: ISubject* _subject; static int count; }; int Proxy1::count = 0; // 在分布式系统当中 skynet actor class Proxy2 : public ISubject { public: virtual void Handle() { // 在访问 RealSubject 之前做一些处理 // 发送到数据到远端 网络处理 同步非阻塞 ntyco c协程 //IResult * val = rpc->call("RealSubject", "Handle"); // 在访问 RealSubject 之后做一些处理 } private: /*void callback(IResult * val) { // 在访问 RealSubject 之后做一些处理 }*/ }; 上面代码中proxy类可以继承ISubject类也可以不继承,写成继承的样式是为了让程序员明白这是该类的一个代理模块,如果不是继承的,看起来会和适配器模式很像。 proxy类中的基类指针可以改成一个容器,管理多个对象,这在游戏服务器的网关里经常看到。 ———————————————— 原文链接:https://blog.csdn.net/zhoujiajie0521/article/details/122194799
-
【问题来源】【必填】南网电网【问题简要】【必填】坐席调用签出接口后单轮训事件获取不到签出成功事件,轮训状态获取接口再次调用直接返回105-005报错,没有明确的签出事件获取的情况下该如何判断坐席已经正常签出【问题类别】【必填】CC-Gateway【AICC解决方案版本】【必填】AICC 24.200.0【期望解决时间】【选填】尽快【问题现象描述】【必填】坐席调用签出接口后单轮训事件获取不到签出成功事件,轮训状态获取接口再次调用直接返回105-005报错,没有明确的签出事件获取的情况下该如何判断坐席已经正常签出【日志或错误截图】【可选】【附件】【可选】无
-
【问题来源】【必填】南网电网【问题简要】【必填】示忙状态下调用请求休息接口报错100-009【问题类别】【必填】CC-Gateway【AICC解决方案版本】【必填】AICC 24.200.0【期望解决时间】【选填】尽快【问题现象描述】【必填】示忙状态下调用请求休息接口报错100-009,空闲态下可以直接状态变更。请求休息的前置条件都有什么具体的限制吗,只能在空闲态下进行变更吗,不存在之前说到的接口延迟执行的情况吗【日志或错误截图】【可选】无【附件】【可选】无
-
【问题来源】【必填】南网电网【问题简要】【必填】transfer转接方式devicetype选择5外呼号码,该号码可以是任意的手机号或者热线号码吗【问题类别】【必填】CC-Gateway【AICC解决方案版本】【必填】AICC 24.200.0【期望解决时间】【选填】尽快【问题现象描述】【必填】transfer转接方式devicetype选择5外呼号码,该号码可以是任意的手机号或者热线号码吗【日志或错误截图】【可选】无【附件】【可选】无
-
【问题来源】【必填】南网电网【问题简要】【必填】拥有不接来话权限的角色签入后是默认处于不接来话的状态还是要在签入成功后调用设置是否接听来话接口进行权限控制【问题类别】【必填】CC-Gateway【AICC解决方案版本】【必填】AICC 24.200.0【期望解决时间】【选填】尽快【问题现象描述】【必填】拥有不接来话权限的角色签入后是默认处于不接来话的状态还是要在签入成功后调用设置是否接听来话接口进行权限控制。【日志或错误截图】【可选】无【附件】【可选】无
-
【问题来源】【必填】南网电网【问题简要】【必填】通话转移后,生成录音是一通还是两通,以及callid会变更吗【问题类别】【必填】CC-Gateway【AICC解决方案版本】【必填】AICC 24.200.0【期望解决时间】【选填】尽快【问题现象描述】【必填】通话转移后,生成录音是一通还是两通,以及callid会变更吗。【日志或错误截图】【可选】无【附件】【可选】无
-
【问题来源】【必填】南网电网【问题简要】【必填】视频坐席如何进行集成,视频电话页面该如何打开,并且视频文件获取跟录音有何区别【问题类别】【必填】CC-Gateway【AICC解决方案版本】【必填】AICC 24.200.0【期望解决时间】【选填】尽快【问题现象描述】【必填】视频坐席如何进行实现,坐席签入时选择视频类型,那视频来话页面怎么加载,并且视频通话下按钮操作是否与语音情况下接口调用一致。视频来话情况下视频文件是否也跟录音一样通过录音事件标志开启,并且返回filename为视频文件路径【日志或错误截图】【可选】无【附件】【可选】无
-
【问题来源】 【南方电网】【问题简要】计算规则【问题类别】 【可选问题分类:CMS开发】【AICC解决方案版本】 【AICC可选择版本:AICC 24.200】【期望解决时间】2024年9月6日【问题描述】 查询表: 呼入技能队列维度指标5分钟粒度表【表代码:t_cms_callin_skill_5min】 区分数据条件: 统计的技能队列编号【current_skill_id】、媒体类型【media_type】区分数据, 数据结果: 以第6条和第8条数据为例 :技能队列编号相同、媒体类型相同、时间相同,指标不同,还有别的字段去区分这2条数据?还是脏数据查询SQL如下:SELECT lin5.log_date, log_quarter, log_half, log_hour, lin5.call_type, lin5.current_skill_id, lin5.fail_wait_ans_time, lin5.media_type, lin5.local_log_week FROM aicc_cms.t_cms_callin_skill_5min AS lin5 ORDER BY lin5.log_date DESC截图如下:
-
【问题来源】【必填】南网电网【问题简要】【必填】如何判断坐席拥有质检权限,该怎么设置权限。并且强制操作时状态如何变更【问题类别】【必填】CC-Gateway【AICC解决方案版本】【必填】AICC 24.200.0【期望解决时间】【选填】尽快【问题现象描述】【必填】1.坐席对另一个坐席通话进行监听拦截插入,对坐席状态进行强制示闲示忙,需要判断坐席有没有权限进行这些操作吗,该如何进行权限赋予。2.通话态时可以进行监听拦截插入操作,强制示闲示忙签出有状态限制吗,如坐席处于空闲状态可进行强制示忙签出操作,坐席处于示忙态可以进行强制示闲签出操作,如果坐席处于休息和工作态时,是否能直接进行强制示闲示忙签出操作,还需要先调用退出休息退出工作态接口吗,直接调用强制操作接口是不是就可以实现状态直接转换的操作【日志或错误截图】【可选】无【附件】【可选】无
-
【问题来源】【必填】南网电网【问题简要】【必填】示忙状态到空闲状态,工作态和休息态的结束等,如何进行各状态间的跳转【问题类别】【必填】CC-Gateway【AICC解决方案版本】【必填】AICC 24.200.0【期望解决时间】【选填】尽快【问题现象描述】【必填】状态间的切换如何实现,示忙态时能否通过示闲接口直接进入到示闲状态,工作态时能否同时调用空闲和示忙接口分别进入不同状态而不需要调用取消工作态接口。取消示忙、取消休息和退出工作态等接口使用场景是什么样的。比如调用取消休息接口成功后AgentState_cancelRest_Success状态返回,怎么判断取消休息后进入什么状态中,示忙还是空闲【日志或错误截图】【可选】无【附件】【可选】无
-
【问题来源】【必填】南网电网【问题简要】【必填】录音事件该如何触发【问题类别】【必填】CC-Gateway【AICC解决方案版本】【必填】AICC 24.200.0【期望解决时间】【选填】尽快【问题现象描述】【必填】录音开始 AgentMediaEvent_Record 事件在restful接口情况下是否会自动触发,需手动触发开始录音接口吗【日志或错误截图】【可选】无【附件】【可选】无
-
【问题来源】【必填】南网电网【问题简要】【必填】resetSkill坐席签入后签入技能队列时,autoFlag传true默认签入所有的技能队列时,是否需要配置skillid签入技能队列号,该签入技能队列号该如何取值【问题类别】【必填】CC-Gateway【AICC解决方案版本】【必填】AICC 24.200.0【期望解决时间】【选填】尽快【问题现象描述】【必填】resetSkill坐席签入后签入技能队列时,autoFlag传true默认签入所有的技能队列时,是否需要配置skillid签入技能队列号,该签入技能队列号该如何取值【日志或错误截图】【可选】无【附件】【可选】无
-
resetSkill坐席签入后签入技能队列时,autoFlag传true默认签入所有的技能队列时,是否需要配置skillid签入技能队列号,该签入技能队列号该如何取值
-
synchronized关键字是Java中用于控制多线程访问共享资源的一种机制,它确保同一时刻只有一个线程可以执行某个方法或代码块。其底层原理主要基于JVM(Java虚拟机)中的Monitor(监视器)机制来实现。以下是synchronized关键字的详细底层原理:1. Monitor机制Monitor(监视器):是Java虚拟机中的一种内置同步机制,每个Java对象都有一个与之关联的Monitor。Monitor通过内部的一些同步原语(如lock和unlock指令)来实现线程间的互斥访问和协调。锁的获取与释放:当一个线程尝试进入被synchronized修饰的代码块或方法时,它首先需要获取对象的Monitor。如果Monitor已被其他线程持有,则当前线程将被阻塞,直到Monitor变为可用状态。当线程完成执行后,它会释放Monitor,以便其他线程可以获取并继续执行。2. 锁的升级偏向锁(Biased Locking):Java 6及以后版本中引入的一种锁优化技术。如果某个对象锁在整个运行过程中只被一个线程持有,那么JVM会将该锁升级为偏向锁,以减少锁获取的开销。偏向锁通过CAS(Compare-And-Swap)操作将线程ID设置到对象的Mark Word中,以表示该对象锁已被当前线程占有。轻量级锁(Lightweight Locking):当存在多个线程竞争同一个锁时,偏向锁会升级为轻量级锁。轻量级锁通过CAS操作在JVM的栈帧中创建锁记录(Lock Record),并将锁对象的Mark Word指向该锁记录。如果锁记录中的Mark Word与锁对象的Mark Word一致,则当前线程获取锁成功;否则,表示存在锁竞争,可能需要升级到重量级锁。重量级锁(Heavyweight Locking):当轻量级锁无法满足需求时(如多个线程长时间竞争同一个锁),锁会升级为重量级锁。重量级锁依赖于操作系统层面的互斥量(Mutex)来实现,涉及到用户态和内核态的切换,成本较高。3. 锁的释放与等待/通知锁的释放:当线程完成synchronized代码块的执行后,它会通过unlock指令释放Monitor,并将Monitor返还给对象池,以便其他线程可以获取。等待/通知:在synchronized代码块中,线程可以通过调用对象的wait()方法进入等待状态,并释放Monitor。其他线程可以通过调用对象的notify()或notifyAll()方法唤醒等待的线程。这些操作都是通过操纵与对象关联的Monitor来实现的。4. 锁的获取流程线程尝试获取对象的Monitor。如果Monitor未被其他线程持有,则当前线程获取Monitor并继续执行。如果Monitor已被其他线程持有,则当前线程进入阻塞状态,等待Monitor变为可用。当Monitor变为可用时,当前线程重新尝试获取Monitor。5. 注意事项synchronized关键字是JVM层面的机制,其实现依赖于JVM的具体实现(如HotSpot)。锁的升级和降级是JVM自动进行的,无需开发者手动干预。在使用synchronized时,应尽量避免长时间持有锁,以减少线程阻塞和死锁的风险。综上所述,synchronized关键字的底层原理主要基于JVM中的Monitor机制,通过锁的获取、释放、升级和降级等机制来实现线程间的同步控制。
推荐直播
-
码道新技能,AI 新生产力——从自动视频生成到开源项目解析2026/04/08 周三 19:00-21:00
童得力-华为云开发者生态运营总监/何文强-无人机企业AI提效负责人
本次华为云码道 Skill 实战活动,聚焦两大 AI 开发场景:通过实战教学,带你打造 AI 编程自动生成视频 Skill,并实现对 GitHub 热门开源项目的智能知识抽取,手把手掌握 Skill 开发全流程,用 AI 提升研发效率与内容生产力。
回顾中 -
华为云码道:零代码股票智能决策平台全功能实战2026/04/18 周六 10:00-12:00
秦拳德-中软国际教育卓越研究院研究员、华为云金牌讲师、云原生技术专家
利用Tushare接口获取实时行情数据,采用Transformer算法进行时序预测与涨跌分析,并集成DeepSeek API提供智能解读。同时,项目深度结合华为云CodeArts(码道)的代码智能体能力,实现代码一键推送至云端代码仓库,建立起高效、可协作的团队开发新范式。开发者可快速上手,从零打造功能完整的个股筛选、智能分析与风险管控产品。
回顾中 -
华为云码道全新升级,多会话并行与多智能体协作2026/05/08 周五 19:00-21:00
王一男-华为云码道产品专家;张嘉冉-华为云码道工程师;胡琦-华为云HCDE;程诗杰-华为云HCDG
华为云码道4月份版本全新升级,此次直播深度解读4月份产品特性,通过“特性解读+实操演示+实战案例+设计创新”的组合,全方位展现码道在多会话并行与多智能体协作方面的能力,赋能开发者提升效率
正在直播
热门标签