C++ 常见设计模式

面向对象设计原则

1、依赖倒置原则:上层模块和实现细节都应该依赖于抽象。eg)上层模块为获取国家的税率,实现细节会包括中国税率、美国税率等,不能直接在上层模块里利用if/else编码实现具体细节,而应该抽象出一个基类(内含税率计算函数),各实现细节模块继承这个抽象基类,根据虚函数动态绑定的特点利用基类指针或引用来解耦合。

2、开放封闭原则:类模块应该是可扩展的,但是不能修改。eg)如果还有日本的税率需要计算,不能直接在上层模块编码实现,而是重写一个子类继承抽象基类用来计算。

3、单一职责原则:每一个类都应该专注于做一件事,这样可以降低类的复杂度。

4、优先使用对象组合而不是类继承:继承在某种程度上破坏了类的封装性,可让另一个类成为本类的成员。类A中包含类B的指针而不是类B对象,比较灵活。

什么是设计模式以及好处优点

一·、定义·:设计模式其实上就是用来让代码写的更加规范,方便后期维护扩展,提高软件的复用性、灵活性、扩展性。

二、优点:

***设计模式的使用将提高软件系统的开发效率和软件质量,节省开发成本

***设计模式有助于初学者深入理解面向对象思想

***设计模式使设计方案更加灵活,方便后期维护修改

策略模式

一、定义:定义一系列算法(将算法定义为虚函数,子类重写它),将它们封装起来,并使他们可互相替换(变化)。

二、应用场景:在软件构建过程中,某些对象使用的算法可能多种多样,经常改变,如果将这些算法都编码到对象中,将会使对象变得异常复杂;而且有时候支持不使用的算法也是一个性能负担(例如if/else条件判断语句有很多判断语句不会被执行)。

三、解决什么问题:在运行时根据需要透明地更改对象的算法;将算法与对象本身解耦。

原始代码设计(伪代码)

enum TaxBase {
    CN_Tax,
    US_Tax,
    DE_Tax,
    FR_Tax       //更改
};

class SalesOrder{
    TaxBase tax;
public:
    double CalculateTax(){
        //...
        
        if (tax == CN_Tax){
            //CN***********
        }
        else if (tax == US_Tax){
            //US***********
        }
        else if (tax == DE_Tax){
            //DE***********
        }
        else if (tax == FR_Tax){  //更改
            //...
        }

        //....
     }
    
};

加入策略设计模式之后的代码(伪代码):

class TaxStrategy{   // 父类
public:
    virtual double Calculate(const Context& context)=0;  // 父类纯虚函数
    virtual ~TaxStrategy(){}
};


class CNTax : public TaxStrategy{
public:
    virtual double Calculate(const Context& context){  //子类重写父类虚函数
        //***********
    }
};

class USTax : public TaxStrategy{
public:
    virtual double Calculate(const Context& context){
        //***********
    }
};

class DETax : public TaxStrategy{
public:
    virtual double Calculate(const Context& context){
        //***********
    }
};

//扩展
//*********************************
class FRTax : public TaxStrategy{
public:
    virtual double Calculate(const Context& context){
        //.........
    }
};


class SalesOrder{
private:
    TaxStrategy* strategy; //父类指针指向子类对象 发生多态

public:
    SalesOrder(StrategyFactory* strategyFactory){
        this->strategy = strategyFactory->NewStrategy();
    }
    ~SalesOrder(){
        delete this->strategy;
    }

    public double CalculateTax(){
        //...
        CNTax context();
        
        double val = strategy->Calculate(context); //多态调用
        //...
    }
    
};

总结:使用继承,将算法通过纯虚函数向子类中进行传递,每个子类都可以单独进行实现,在使用算法时便可以准确调用对应的函数,不需要包含不需要使用到的算法。在使用对if/else条件判断语句进行替换的时候,策略模式可能是一个不错的选择。

观察者模式

一·、定义:定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的观察者对象都得到通知并自动更新。

二、主要构成:

抽象被观察者:将所有观察者对象保存到一个集合里,并提供增加、删除观察者对象的接口

具体被观察者:有关状态存入具体观察者对象,具体被观察者的内部状态发生改变时,给所有登记过的观察者发通知。负责实现抽象被观察者中的方法。

抽象观察者:为具体观察者提供一个更新接口,该接口声明了更新数据的方法。

具体观察者:实现抽象观察者定义的更新接口,以便更新自身状态

三、举例:我的微信公众是被观察者,微信用户是观察者,有多个微信用户关注了我的公众号,当我的公众号更新订阅时就会通知这些订阅了的微信用户。

四、具体应用场景:消息更新、广播机制、消息传递(发布-订阅模式)

单例模式

单例模式只允许类创建一个对象,共享该对象可以节省内存,并加快对象访问速度。如 Web 中的配置对象、数据库的连接池等。分为懒汉模式和饿汉模式。

一、多线程不安全版本

优点:实现了一个类只有一个类对象,当再次进入函数创建时由于age不为空,直接return了。

缺点:在多线程情况下会创建多个对象。如果线程a和线程b同时进入63行的函数,每个线程都会创建一个类对象,违反了单例模式原则。

二、多线程安全版本  加锁

 优点:对函数全局加锁。线程a进入函数后获得锁,线程b不能再进入函数只能等待a释放锁。当线程a创建好age后结束函数并释放锁,这时b取得锁,发现age不为空,直接return了。保证了一个类只有一个类对象。

缺点:加锁代价过高。如果后续的多线程全执行读操作,这会造成浪费,因为读不用加锁。后续多线程会等待锁释放,效率降低,尤其针对于高并发的网络服务器来说效率大大降低。

三、双检查锁版本

优点:解决了全执行读操作的多线程浪费且效率低下问题。只有age为空时才加锁。当线程a进入函数,会获得锁,给age赋值。后续执行读操作的多进程进入函数后发现age不为空,直接return了,方便了执行读操作的多线程。

PS:为啥判断两次?

        ***线程a线程b同时在65 66行进入,如果不加第二次判断,第二个线程等待第一个线程释放锁后还是会创建多个类对象。

缺点:68行代码 age=new Person()会由于内存读写reorder不安全导致双检查锁失效。理想情况下这行代码会分三步进行:1、分配内存;2、调用Person构造函数初始化内存;3、将new的内存地址返回后赋值给person。但是编译器有可能实际情况下会先分配内存然后直接将内存地址赋值给person,最后调用构造函数。这样的话person不为空了,第二个线程进入函数发现person不为空,直接return person了,但person是不能被使用的,因为内存没初始化。

四、volatile 或者 atomic库   解决双检查锁版本的reorder问题

1、volatile Person* age; //让编译器不reorder

2、atomic<Person*> Person::age; //让编译器不reorder

上述都是单例模式的懒汉模式:当外界第一次调用get_person()接口时才进行类的实例化。会出现多线程不安全问题

因此有饿汉模式:类加载时就对类实例化。不会出现多线程不安全问题,不用加锁。C++11 保证静态局部变量的初始化过程是线程安全的。这里的线程安全指的是:一个线程在初始化 m 的时候,其他线程执行到 m 的初始化这一行的时候,就会挂起阻塞。

工厂模式

简单工厂模式:多了工厂基类

class Splitter { //抽象基类
public:
	virtual void split() = 0;
};
class ImageSplitter : public Splitter{
	virtual void split() {}//具体子类 重写基类虚函数
};
class VideoSplitter : public Splitter {
	virtual void split() {}//具体子类 重写基类虚函数
};

class SplitterFactory {//工厂基类 充当第三方角色 不需要向工厂方法一样再创建具体工厂子类
public:
	Splitter* createSplitter(){
        if(...) return new ImageSplitter();//一般需要switch或if进行类型选择
        else if(...) return new VideoSplitter();//
    }
};

int main() {//常规做法
	Splitter * splitter = new ImageSplitter();//常规做法 利用new创建具体细节对象
	Splitter * splitter = new VideoSplitter();//上层模块还是依赖了具体细节 违背依赖倒置原则
}


int main() {//简单工厂模式 
	SplitterFactory*  factory;
	Splitter* splitter=factory->createSplitter();
    splitter.split();
}

工厂方法模式:绕开new,解决利用new创建对象时出现的紧耦合问题。多了工厂基类和具体工厂子类

class Splitter { //抽象基类
public:
	virtual void split() = 0;
};
class ImageSplitter : public Splitter{
	virtual void split() {}//具体子类 重写基类虚函数
};
class VideoSplitter : public Splitter {
	virtual void split() {}//具体子类 重写基类虚函数
};


class SplitterFactory {//工厂基类
public:
	virtual Splitter* createSplitter() = 0;//纯虚函数 动态绑定
};
class ImageSplitterFactory : public SplitterFactory {//具体工厂
public:
	virtual Splitter* createSplitter() {
		return new ImageSplitter();//将常规的new搬到了具体工厂子类中
	}
};
class VideoSplitterFactory : public SplitterFactory {//具体工厂
public:
	virtual Splitter* createSplitter() {
		return new VideoSplitter();//将常规的new搬到了具体工厂子类中
	}
};

int main() {//常规做法 
	Splitter * splitter = new ImageSplitter();//利用new创建具体细节对象
	Splitter * splitter = new VideoSplitter();//这样的话上层模块还是依赖了具体细节 违背依赖倒置原则
}

int main() { //工厂方法模式
	SplitterFactory*  factory;
	Splitter* splitter = factory->createSplitter();//多态new。移出具体细节,让别人new去
	splitter->split();
}


抽象工厂模式:对工厂方法进行优化

       假设上述imagesplitter和videospiltter除了有公共的split外,还有公共的merge和destroy等,那么需要再来一个merge基类工厂和destroy基类工厂以及对应的具体工厂子类。这样实现比较复杂且代码可读性不好,可将split、merge、destroy放进同一个抽象工厂里,此抽象工厂内含纯虚函数split、merge、destroy。能这样做的前提是split、merge、destroy这三者互相有联系。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值