C++抽象类在设计模式中的应用
C++抽象类基础
什么是抽象类
在C++ 中,抽象类是一种特殊的类,它至少包含一个纯虚函数。纯虚函数是在声明时被初始化为0的虚函数,例如:
class Shape {
public:
virtual double area() const = 0;
};
在上述代码中,Shape
类就是一个抽象类,因为它包含了纯虚函数area
。抽象类不能被实例化,即不能直接创建对象,它主要作为其他具体类的基类,为派生类提供一个通用的接口。
抽象类的作用
- 提供统一接口:通过抽象类,我们可以为一组相关的类定义一个共同的接口。例如,在图形绘制的场景中,
Shape
抽象类可以定义draw
、area
等函数,具体的Circle
、Rectangle
等类继承自Shape
并实现这些函数,这样在使用时可以统一通过Shape
指针或引用来调用这些函数,实现多态性。 - 代码复用与可维护性:抽象类可以将一些通用的属性和行为封装在基类中,派生类继承并根据自身需求进行定制。当需要修改或扩展功能时,只需要在抽象类或具体派生类中进行修改,而不会影响到整个系统的其他部分,提高了代码的可维护性。
C++抽象类在设计模式中的应用
策略模式
- 策略模式概述:策略模式定义了一系列算法,将每个算法封装起来,使它们可以相互替换,并且算法的变化不会影响到使用算法的客户。
- 使用抽象类实现策略模式:
// 抽象策略类
class SortStrategy {
public:
virtual void sort(int* arr, int size) = 0;
};
// 具体策略类:冒泡排序
class BubbleSort : public SortStrategy {
public:
void sort(int* arr, int size) override {
for (int i = 0; i < size - 1; ++i) {
for (int j = 0; j < size - i - 1; ++j) {
if (arr[j] > arr[j + 1]) {
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
}
};
// 具体策略类:快速排序
class QuickSort : public SortStrategy {
private:
int partition(int* arr, int low, int high) {
int pivot = arr[high];
int i = low - 1;
for (int j = low; j < high; ++j) {
if (arr[j] <= pivot) {
++i;
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
int temp = arr[i + 1];
arr[i + 1] = arr[high];
arr[high] = temp;
return i + 1;
}
void quickSort(int* arr, int low, int high) {
if (low < high) {
int pi = partition(arr, low, high);
quickSort(arr, low, pi - 1);
quickSort(arr, pi + 1, high);
}
}
public:
void sort(int* arr, int size) override {
quickSort(arr, 0, size - 1);
}
};
// 上下文类
class Sorter {
private:
SortStrategy* strategy;
public:
Sorter(SortStrategy* s) : strategy(s) {}
void performSort(int* arr, int size) {
strategy->sort(arr, size);
}
};
在上述代码中,SortStrategy
是一个抽象类,定义了排序的接口sort
。BubbleSort
和QuickSort
是具体的策略类,继承自SortStrategy
并实现了sort
函数。Sorter
类是上下文类,它持有一个SortStrategy
指针,并通过这个指针调用具体的排序算法。这样,在运行时可以根据需要动态地选择不同的排序策略。
工厂模式
- 工厂模式概述:工厂模式是一种创建型设计模式,它提供了一种创建对象的方式,将对象的创建和使用分离。工厂模式分为简单工厂、工厂方法和抽象工厂三种类型,这里我们主要探讨使用抽象类实现工厂方法模式。
- 使用抽象类实现工厂方法模式:
// 抽象产品类
class Product {
public:
virtual void operation() = 0;
};
// 具体产品类A
class ProductA : public Product {
public:
void operation() override {
std::cout << "ProductA operation" << std::endl;
}
};
// 具体产品类B
class ProductB : public Product {
public:
void operation() override {
std::cout << "ProductB operation" << std::endl;
}
};
// 抽象工厂类
class Factory {
public:
virtual Product* createProduct() = 0;
};
// 具体工厂类A
class FactoryA : public Factory {
public:
Product* createProduct() override {
return new ProductA();
}
};
// 具体工厂类B
class FactoryB : public Factory {
public:
Product* createProduct() override {
return new ProductB();
}
};
在这个示例中,Product
是抽象产品类,ProductA
和ProductB
是具体产品类。Factory
是抽象工厂类,定义了创建产品的接口createProduct
。FactoryA
和FactoryB
是具体工厂类,分别创建ProductA
和ProductB
。通过这种方式,客户端代码只需要与抽象工厂和抽象产品交互,而不需要关心具体产品的创建细节,提高了代码的可维护性和可扩展性。
模板方法模式
- 模板方法模式概述:模板方法模式在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中实现。模板方法使得子类可以在不改变算法结构的情况下,重新定义算法的某些步骤。
- 使用抽象类实现模板方法模式:
// 抽象基类
class AbstractClass {
public:
void templateMethod() {
primitiveOperation1();
primitiveOperation2();
concreteOperation();
}
protected:
virtual void primitiveOperation1() = 0;
virtual void primitiveOperation2() = 0;
void concreteOperation() {
std::cout << "Concrete operation in AbstractClass" << std::endl;
}
};
// 具体子类
class ConcreteClass : public AbstractClass {
protected:
void primitiveOperation1() override {
std::cout << "Primitive operation 1 in ConcreteClass" << std::endl;
}
void primitiveOperation2() override {
std::cout << "Primitive operation 2 in ConcreteClass" << std::endl;
}
};
在上述代码中,AbstractClass
是抽象基类,其中templateMethod
定义了算法的骨架,包含了primitiveOperation1
、primitiveOperation2
和concreteOperation
。primitiveOperation1
和primitiveOperation2
是纯虚函数,需要子类实现,concreteOperation
是已经实现的具体操作。ConcreteClass
继承自AbstractClass
并实现了primitiveOperation1
和primitiveOperation2
,这样就可以根据具体需求定制算法的部分步骤。
桥接模式
- 桥接模式概述:桥接模式将抽象部分与它的实现部分分离,使它们都可以独立地变化。它通过将一个对象的两个维度分离,抽象和实现可以沿着各自的维度进行扩展。
- 使用抽象类实现桥接模式:
// 抽象实现类
class Implementor {
public:
virtual void operation() = 0;
};
// 具体实现类A
class ConcreteImplementorA : public Implementor {
public:
void operation() override {
std::cout << "ConcreteImplementorA operation" << std::endl;
}
};
// 具体实现类B
class ConcreteImplementorB : public Implementor {
public:
void operation() override {
std::cout << "ConcreteImplementorB operation" << std::endl;
}
};
// 抽象类
class Abstraction {
protected:
Implementor* implementor;
public:
Abstraction(Implementor* imp) : implementor(imp) {}
virtual void operation() {
implementor->operation();
}
};
// 扩展抽象类
class RefinedAbstraction : public Abstraction {
public:
RefinedAbstraction(Implementor* imp) : Abstraction(imp) {}
void operation() override {
std::cout << "RefinedAbstraction pre - operation" << std::endl;
Abstraction::operation();
std::cout << "RefinedAbstraction post - operation" << std::endl;
}
};
在这个例子中,Implementor
是抽象实现类,ConcreteImplementorA
和ConcreteImplementorB
是具体实现类。Abstraction
是抽象类,持有一个Implementor
指针,并在operation
方法中调用Implementor
的operation
方法。RefinedAbstraction
是扩展抽象类,它继承自Abstraction
并可以对operation
方法进行进一步的扩展。通过这种方式,抽象和实现可以独立变化,实现了松耦合。
代理模式
- 代理模式概述:代理模式为其他对象提供一种代理以控制对这个对象的访问。代理对象在客户端和目标对象之间起到中介作用,客户端访问代理对象,代理对象再去访问目标对象。
- 使用抽象类实现代理模式:
// 抽象主题类
class Subject {
public:
virtual void request() = 0;
};
// 真实主题类
class RealSubject : public Subject {
public:
void request() override {
std::cout << "RealSubject is handling request" << std::endl;
}
};
// 代理类
class Proxy : public Subject {
private:
RealSubject* realSubject;
public:
Proxy() : realSubject(nullptr) {}
~Proxy() {
if (realSubject) {
delete realSubject;
}
}
void request() override {
if (!realSubject) {
realSubject = new RealSubject();
}
std::cout << "Proxy pre - request" << std::endl;
realSubject->request();
std::cout << "Proxy post - request" << std::endl;
}
};
在上述代码中,Subject
是抽象主题类,定义了request
接口。RealSubject
是真实主题类,实现了request
方法。Proxy
是代理类,它持有一个RealSubject
指针,并在request
方法中进行一些预处理和后处理操作,然后调用RealSubject
的request
方法。这样,客户端通过代理类访问真实主题,代理类可以对访问进行控制和增强。
装饰器模式
- 装饰器模式概述:装饰器模式动态地给一个对象添加一些额外的职责,就增加功能来说,装饰器模式相比生成子类更为灵活。
- 使用抽象类实现装饰器模式:
// 抽象构件类
class Component {
public:
virtual void operation() = 0;
};
// 具体构件类
class ConcreteComponent : public Component {
public:
void operation() override {
std::cout << "ConcreteComponent operation" << std::endl;
}
};
// 抽象装饰类
class Decorator : public Component {
protected:
Component* component;
public:
Decorator(Component* comp) : component(comp) {}
void operation() override {
component->operation();
}
};
// 具体装饰类A
class ConcreteDecoratorA : public Decorator {
public:
ConcreteDecoratorA(Component* comp) : Decorator(comp) {}
void operation() override {
std::cout << "ConcreteDecoratorA pre - operation" << std::endl;
Decorator::operation();
std::cout << "ConcreteDecoratorA post - operation" << std::endl;
}
};
// 具体装饰类B
class ConcreteDecoratorB : public Decorator {
public:
ConcreteDecoratorB(Component* comp) : Decorator(comp) {}
void operation() override {
std::cout << "ConcreteDecoratorB pre - operation" << std::endl;
Decorator::operation();
std::cout << "ConcreteDecoratorB post - operation" << std::endl;
}
};
在这个示例中,Component
是抽象构件类,ConcreteComponent
是具体构件类。Decorator
是抽象装饰类,它继承自Component
并持有一个Component
指针,在operation
方法中调用被装饰对象的operation
方法。ConcreteDecoratorA
和ConcreteDecoratorB
是具体装饰类,它们可以在调用被装饰对象的operation
方法前后添加额外的功能,从而实现对对象功能的动态扩展。
适配器模式
- 适配器模式概述:适配器模式将一个类的接口转换成客户希望的另一个接口,使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
- 使用抽象类实现适配器模式:
// 目标接口
class Target {
public:
virtual void request() = 0;
};
// 适配者类
class Adaptee {
public:
void specificRequest() {
std::cout << "Adaptee specific request" << std::endl;
}
};
// 适配器类
class Adapter : public Target {
private:
Adaptee adaptee;
public:
void request() override {
adaptee.specificRequest();
}
};
在上述代码中,Target
是目标接口,Adaptee
是适配者类,它有自己特定的接口specificRequest
。Adapter
是适配器类,它继承自Target
并包含一个Adaptee
对象,在request
方法中调用Adaptee
的specificRequest
方法,从而将Adaptee
的接口适配成Target
的接口,使得客户端可以通过Target
接口调用Adaptee
的功能。
总结抽象类在设计模式中的优势与注意事项
优势
- 提高代码的可维护性和可扩展性:通过抽象类,将通用的行为和接口封装起来,具体的实现细节由派生类完成。当需要修改或扩展功能时,只需要在相应的派生类中进行修改,而不会影响到其他部分的代码。例如在策略模式中,新增一种排序策略只需要创建一个新的继承自
SortStrategy
的具体策略类并实现sort
方法,不会影响到Sorter
类和其他策略类。 - 实现多态性:抽象类为多态性的实现提供了基础。通过抽象类的指针或引用,可以在运行时根据对象的实际类型调用相应的函数。在工厂模式中,客户端可以通过抽象工厂和抽象产品的指针或引用来操作具体的产品对象,实现了运行时的多态性,使得系统更加灵活。
- 降低耦合度:在桥接模式和代理模式等设计模式中,抽象类的使用使得不同部分之间的耦合度降低。例如在桥接模式中,抽象和实现通过抽象类分离,它们可以独立地进行变化,互不影响,提高了系统的灵活性和可维护性。
注意事项
- 抽象类不能实例化:这是抽象类的基本特性,开发人员在使用时必须牢记。如果尝试实例化抽象类,编译器会报错。在代码实现过程中,要确保不会意外地创建抽象类的对象。
- 纯虚函数的实现:派生类必须实现抽象类中的纯虚函数,否则派生类也会成为抽象类。在开发过程中,要确保所有的具体派生类都正确地实现了纯虚函数,否则程序在运行时可能会出现未定义行为。
- 内存管理:当在抽象类及其派生类中涉及到动态内存分配时,要注意合理的内存管理。例如在代理模式中,代理类持有真实主题类的指针,在代理类的析构函数中要确保正确地释放真实主题类的内存,避免内存泄漏。
通过合理地运用C++ 抽象类在各种设计模式中,可以构建出更加灵活、可维护和可扩展的软件系统。开发人员在实际项目中,应根据具体的需求和场景,选择合适的设计模式,并正确地使用抽象类来实现系统的功能。