计算机系统应用教程网站

网站首页 > 技术文章 正文

C++的23种设计模式(上篇-创建型模式)

btikc 2024-09-24 08:08:07 技术文章 21 ℃ 0 评论

设计模式是软件开发人员在软件开发过程中面临的一般问题的解决方案,这些解决方案是软件开发人员经过相当长的一段时间的试验和错误总结出来的,可以被反复使用。本文将以C++为例讲解设计模式的概念,其中代码示例大多改自菜鸟教程中的Java版。

设计原则


  1. 开闭原则(Open Close Principle):对扩展开放,对修改关闭。在程序需要进行拓展的时候,不能去修改原有的代码,这可能会给旧代码引入错误,也有可能使我们不得不对整个功能进行重构,并且原有代码需要重新经过测试。想要达到这样的效果,我们需要使用接口和抽象类。
  2. 里氏代换原则(Liskov Substitution Principle):里氏代换原则是面向对象设计的基本原则之一。任何基类可以出现的地方,子类一定可以出现,只有当派生类可以替换掉基类,且软件功能不受到影响时,基类才能真正被复用,而派生类也能够在基类的基础上增加新的行为。里氏代换原则是对开闭原则的补充。实现开闭原则的关键步骤就是抽象化,而基类与子类的继承关系就是抽象化的具体实现,所以里氏代换原则是对实现抽象化的具体步骤的规范。(1)子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法。(2)子类中可以增加自己特有的方法。(3)当子类的方法重载父类的方法时,方法的前置条件(即方法的形参)要比父类方法的输入参数更宽松。(4)当子类的方法实现父类的抽象方法时,方法的后置条件(即方法的返回值)要比父类更严格。
  3. 依赖倒转原则(Dependence Inversion Principle):这个原则是开闭原则的基础:针对接口编程,依赖于抽象而不依赖于具体,即用到具体类时不与具体类交互,而与具体类的上层接口交互。
  4. 接口隔离原则(Interface Segregation Principle)每个接口中不应该存在子类用不到却必须实现的方法,否则就应该将接口拆分。使用多个隔离的接口,比使用单个接口要好。不应该强迫客户程序依赖他们不用的方法;接口应该小而完备。(1)接口尽量小,但是要有限度。如果过小,则会造成接口数量过多,使设计复杂化。(2)为依赖接口的类定制服务,只暴露给调用的类它需要的方法,它不需要的方法则隐藏起来。(3)提高内聚,减少对外交互,使接口用最少的方法去完成最多的事情。
  5. 迪米特法则/最少知道原则(Demeter Principle):最少知道原则是指一个实体应当尽量少地与其他实体之间发生相互作用,使得系统功能模块相对独立。一个对象应该对其他对象保持最少的了解。另外切记不要过分使用迪米特原则,否则会产生大量的这样的中介和传递类。最少知道原则要求只与直接朋友通信。类之间只要有耦合关系就叫朋友关系,出现为成员变量、方法参数、方法返回值中的类为直接朋友,而局部变量、临时变量则不是直接朋友。
  6. 合成复用原则(Composite Reuse Principle):尽量使用合成/聚合的方式,而不是使用继承。
  7. 单一职责原则(Single Responsibility Principle):不要存在多于一个导致类变更的原因,也就是说每个类应该实现单一的职责,否则就应该把类拆分。

设计模式

常用的设计模式共有23种,它们可以分成创建型、结构型和行为型模式三种,其中,:

创建型模式包括工厂模式、抽象工厂模式、单例模式、建造者模式、原型模式;

结构型模式包括适配器模式、桥接模式、过滤器模式、组合模式、装饰器模式、外观模式、享元模式、代理模式;

行为型模式包括责任链模式、命令模式、解释器模式、迭代器模式、中介者模式、备忘录模式、观察者模式、状态模式、空对象模式、策略模式、模板模式、访问者模式。

工厂模式(Factory Pattern)

工厂模式是最常用的设计模式之一,它提供了一种创建对象的最佳方式。在工厂模式中,我们在创建对象时不会对客户暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象。

工厂方法由一个抽象产品类和一个抽象工厂组成,它让子类自己决定实例化哪一个工厂类,每一个工厂类对应了一个产品类。

优点: 1、调用者想创建一个对象,只要知道其名称就可以了。 2、扩展性高,如果想增加一个产品,只要扩展一个工厂类就可以。 3、屏蔽产品的具体实现,调用者只关心产品的接口。

缺点:每次增加一个产品时,都需要增加一个具体类和对象实现工厂,使得系统中类的个数成倍增加,在一定程度上增加了系统的复杂度,同时也增加了系统具体类的依赖。

C++示例:

我们有Rectangle和Circle两个具体的产品类,他们可以抽象出一个抽象产品类Shape。同时有FactoryRectangle和FactoryCircle两个具体工厂类,他们继承自抽象工厂类Factory。每一个具体工厂类与一个具体产品类相对应。

// 形状接口,抽象产品类
class Shape{
public:
      virtual void draw() = 0;
};

// 具体产品1
class Rectangle : public Shape {
public:
      Rectangle(){}
      void draw() { cout<<"矩形类"<<endl; }
};

// 具体产品2
class Circle : public Shape {
public:
      Circle(){}
      void draw() { cout<<"圆形类"<<endl; }
};

// 抽象工厂类
class Factory {
public:
          virtual Shape *creatShape() = 0;
};

// 具体工厂1
class FactoryRectangle : public Factory {
public:
          Shape *creatShape() { return new Rectangle(); }
};

// 具体工厂2
class FactoryCircle : public Factory {
public:
          Shape *creatShape() { return new Circle(); }
};

// 客户
int main() {
      Factory *factory1 = new FactoryRectangle();
      Factory *factory2 = new FactoryCircle();
      Shape *rectangle = factory1->creatShape();
      Shape *circle = factory2->creatShape();
      rectangle->draw();
      circle->draw();
      return 0;
}

/* 输出
矩形类
圆形类
*/

抽象工厂模式 (Abstract Factory Pattern)

抽象工厂模式是围绕一个超级工厂来创建其他工厂。在工厂模式中,一个工厂对应着一种产品,而抽象工厂中,一个工厂对应着一类产品,即提供一个创建一系列相关或相互依赖对象的接口。

优点:当一个产品族中的多个对象被设计成一起工作时,它能保证客户端始终只使用同一个产品族中的对象。

缺点:产品族扩展非常困难,要增加一个系列的某一产品,既要在抽象的 Creator 里加代码,又要在具体的里面加代码。

C++示例:

有单核和多核两种处理器,即抽象产品类SingleCore和MultiCore,每种类型处理器又分为A和B两种型号,因此一共有SingleCoreA,SingleCoreB,MultiCoreA和MultiCoreB四种具体产品类,其中工厂A即FactoryA类负责A型号的生产,工厂B即FactoryB负责B型号的生产。

// 抽象产品类-单核  
class SingleCore {  
public:  
        virtual void Show() = 0;
};  
// 具体产品类-单核
class SingleCoreA: public SingleCore {  
public:  
        SingleCoreA(){ this->Show(); }
        void Show() { cout<<"单核A"<<endl; }  
};  
class SingleCoreB :public SingleCore {  
public:  
        SingleCoreB(){ this->Show(); }
        void Show() { cout<<"单核B"<<endl; }  
};  

// 抽象产品类-多核  
class MultiCore {  
public:  
      virtual void Show() = 0;
};  
// 具体产品类-多核
class MultiCoreA : public MultiCore {  
public:  
      MultiCoreA(){ this->Show(); }
      void Show() { cout<<"多核A"<<endl; }  
};  
class MultiCoreB : public MultiCore {  
public:  
      MultiCoreB(){ this->Show(); }
      void Show() { cout<<"多核B"<<endl; }  
};  

// 抽象工厂类  
class CoreFactory {  
public:  
      virtual SingleCore* CreateSingleCore() = 0;
      virtual MultiCore* CreateMultiCore() = 0;
};  
// 工厂A 
class FactoryA :public CoreFactory {  
public:  
        SingleCore* CreateSingleCore() { return new SingleCoreA(); }  
        MultiCore* CreateMultiCore() { return new MultiCoreA(); }  
};  
// 工厂B
class FactoryB : public CoreFactory {  
public:  
        SingleCore* CreateSingleCore() { return new SingleCoreB(); }  
        MultiCore* CreateMultiCore() { return new MultiCoreB(); }  
}; 

// 客户
int main() {
      CoreFactory *factorya = new FactoryA();
      CoreFactory *factoryb = new FactoryB();
      //创建A型号的单核和多核
      SingleCore *singleCoreA = factorya->CreateSingleCore();
      MultiCore *multiCoreA = factorya->CreateMultiCore();
      //创建B型号的单核和多核
      SingleCore *singleCoreB = factoryb->CreateSingleCore();
      MultiCore *multiCoreB = factoryb->CreateMultiCore();
      return 0;
}

/* 输出
单核A
多核A
单核B
多核B
*/

单例模式(Singleton Pattern)

单例模式是最简单的设计模式之一。它涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。它保证一个类仅有一个实例,并提供一个访问它的全局访问点。由于构造函数是私有的,因此无法通过构造函数实例化,唯一的方法就是通过调用静态函数GetInstance。

优点:1、在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例(比如管理学院首页页面缓存)。2、避免对资源的多重占用(比如写文件操作)。

缺点:没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。

注意事项:1、单例类只能有一个实例。2、单例类必须自己创建自己的唯一实例。3、单例类必须给所有其他对象提供这一实例。

C++示例

单例模式的实现有多种方式,如懒汉式、饿汉式等。

1、懒汉式,在第一次获取实例时实例化对象

class Singleton {
public:
    Static Singleton* Instance() {
        if(instance == null) {
          	instance = new Singleton();
				}
				return instance;
		}
private:
    Singleton() {}
    static Singleton* instance;
}

该方式是实现单例模式最简单的方法,但是由于它没有锁,因此不能保证多线程的安全。

2、懒汉式,且多线程安全

class Singleton {
private:
    Singleton(){}
 
    static Singleton* instance;
    static mutex single_mutext;
public:
    static Singleton* Instance() {
				single_mutext.lock();
				if (instance == NULL) {
						instance = new Singleton();
				}
				single_mutext.unlock();
				return instance;
		}
};
Singleton * Singleton::instance = NULL;

3、饿汉式,该方式在类创建时就已经初始化,不管之后是否使用,它都占据了一块内存,因此它不需要加锁就是线程安全的。该方式实现简单且常用,在第一次使用时速度比懒汉式要快,但容易产生垃圾对象。

class Singleton {
private:
    Singleton(){}
 
    static Singleton* instance;
public:
    static singleton* Instance() {
        return instance;
    }
};
Singleton* Singleton::instance = new Singleton();

建造者模式(Builder Pattern)

建造者模式使用多个简单的对象一步一步构建成一个复杂的对象,它将一个复杂的构建与其表示相分离,使得同样的构建过程可以创建不同的表示。

优点: 1、建造者独立,容易扩展。 2、便于控制细节风险。

缺点: 1、产品必须有共同点,范围有限制。 2、如内部变化复杂,会有很多的建造类。

注意事项:与工厂模式的区别是:建造者模式更加关注与零件装配的顺序。

C++示例

汽车(Car类)按品牌分有宝马车(BMWCar类)、奔驰车(BenzCar类)等,它们由各个复杂的零配件所组成,CarBuilder类是建造者抽象类,创造产品各个部件的抽象接口,BMWCarBuilder和BenzCarBuilder是具体的建造者,创造产品各个部件的具体实现接口。建造者们通过引导者(CarDirector类)来实现一个产品的构造流程。客户只需要和Director交互获得想要的产品。

//产品基类
class Car {
public:
    string GetEngine() { return m_engine; }
    string GetGearBox() { return m_gearbox; }
    string GetChassis() { return m_chassis; }

    void SetEngine(string engine) {
        m_engine = engine;
        cout << "组装" << engine << endl;
    }
    void SetGearBox(string gearbox) {
        m_gearbox = gearbox;
        cout << "组装" << gearbox << endl;
    }
    void SetChassis(string chassis) {
        m_chassis = chassis;
        cout << "组装" << chassis << endl;
    }
private:
    string m_engine;
    string m_gearbox;
    string m_chassis;
};

//宝马车产品
class BMWCar : public Car {
public:
    BMWCar() { cout << "开始组装宝马" << endl; }
};
//奔驰车产品
class BenzCar : public Car {
public:
    BenzCar() { cout << "开始组装奔驰" << endl; }
};

//建造者抽象类
class CarBuilder{
public:
    virtual Car* BuildCar() = 0;
    virtual void BuildEngine() {}
    virtual void BuildGearBox() {}
    virtual void BuildChassis() {}
};

//宝马建造者具体类
class BMWCarBuilder : public CarBuilder {
public:
    BMWCarBuilder() { m_car = new BMWCar(); }
    void BuildEngine() { m_car->SetEngine("宝马引擎"); }
    void BuildGearBox() { m_car->SetGearBox("宝马变速箱"); }
    void BuildChassis() { m_car->SetChassis("宝马底盘"); }
    Car* BuildCar() { return m_car; }
private:
    Car* m_car;
};
//奔驰建造者具体类
class BenzCarBuilder : public CarBuilder {
public:
    BenzCarBuilder() { m_car = new BenzCar(); }
    void BuildEngine() { m_car->SetEngine("奔驰引擎"); }
    void BuildGearBox() { m_car->SetGearBox("奔驰变速箱"); }
    void BuildChassis() { m_car->SetChassis("奔驰底盘"); }
    Car* BuildCar() { return m_car; }
private:
    Car* m_car;
};

//Director 指导者
class CarDirector {
public:
    Car* ConstructCar(CarBuilder* carBuilder) {
        carBuilder->BuildEngine();
        carBuilder->BuildGearBox();
        carBuilder->BuildChassis();
        cout << "汽车组装完毕" << endl;
        return carBuilder->BuildCar();
    }
};

// 客户
int main() {
    CarDirector carDirector;
    Car* bmwCar = carDirector.ConstructCar(new BMWCarBuilder());
    cout << endl;
    Car* benzCar = carDirector.ConstructCar(new BenzCarBuilder());

	return 0;
}

/* 输出
开始组装宝马
组装宝马引擎
组装宝马变速箱
组装宝马底盘
汽车组装完毕

开始组装奔驰
组装奔驰引擎
组装奔驰变速箱
组装奔驰底盘
汽车组装完毕
*/

原型模式(Prototype Pattern)

原型模式用于创建重复的对象,并保证了性能。它实现了一个原型接口,该接口用于创建当前对象的克隆。当直接创建对象的代价比较大时,则采用这种模式。例如,一个对象需要在一个高代价的数据库操作之后被创建。我们可以缓存该对象,在下一个请求时返回它的克隆,在需要的时候更新数据库,以此来减少数据库调用。原型模式实现的关键就是实现Clone函数,在c++中即拷贝构造函数。

优点: 1、性能提高。 2、逃避构造函数的约束。

C++示例

//抽象原型类
class Prototype {
protected:
    string m_CarModel;
    string m_Engine;
public:
    Prototype() {}
    virtual ~Prototype() {}
    virtual Prototype* Clone() { return NULL; }
    virtual void Show() {}
};

//具体原型类 BMW
class PrototypeBMW : public Prototype {
public:
    PrototypeBMW() {
        m_CarModel = string("宝马汽车");
        m_Engine = string("宝马引擎");
    }
    //拷贝构造函数
    PrototypeBMW(const PrototypeBMW& bmw) {
        m_CarModel = bmw.m_CarModel;
        m_Engine = bmw.m_Engine;
    }
    ~PrototypeBMW() {}
    PrototypeBMW* Clone() {
        return new PrototypeBMW(*this);
    }
    void Show() {
        cout << m_CarModel << endl;
        cout << m_Engine << endl;
    }
};

// 客户
int main(int argc, char* argv[])
{
    Prototype* p_car1 = new PrototypeBMW();
    Prototype* p_car3 = p_car1->Clone();
    p_car1->Show();
    //删除p_car1
    delete p_car1;
    p_car1  = NULL;
    //深拷贝所以对p_car3无影响
    p_car3->Show();
    delete p_car3;
    p_car3 = NULL;
    return 0;
}

小结

这一篇介绍了软件开发过程中23种设计模式中的创建型模式,包括工厂模式、抽象工厂模式、单例模式、建造者模式和原型模式五种,它提供了一种在创建对象的同时隐藏创建逻辑的方式,这使得程序在判断针对某个给定实例需要创建哪些对象时更加灵活。在下一篇文章将对结构型模型和行为型模式进行介绍。

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表