11.设计模式实战:从创建型到结构型的全面解析

设计模式实战:从创建型到结构型的全面解析

前言

设计模式是软件开发中解决常见问题的经典方案,掌握设计模式不仅能提升代码质量,还能让我们与团队更好地沟通设计思想。本文将从创建型模式结构型模式两大类别出发,结合实际C++代码,详解10个最常用的设计模式。

工厂模式、建造者模式、原型模式、桥接模式、适配器模式、装饰模式、组合模式、享元模式、外观模式、代理模式


一、创建型模式

创建型模式关注对象创建机制,使系统在创建对象时更加灵活、解耦。

1. 工厂模式

工厂模式的核心思想是将对象的创建和使用分离,由专门的“工厂”负责创建对象。

工厂方法

客户端

工厂接口

具体工厂A

具体工厂B

产品A

产品B

简单工厂

客户端

简单工厂

产品A

产品B

三种工厂模式对比
模式核心思想何时使用
简单工厂一个工厂根据参数创建不同产品产品种类少,不想为每个类建工厂
工厂方法每个产品对应一个工厂,工厂也抽象化产品会不断扩展,需要符合开闭原则
抽象工厂创建一组相关或依赖的对象(产品族)需要切换整套产品系列
代码示例:工厂方法
// 抽象产品
class Car {
public:
    virtual void run() = 0;
    virtual ~Car() = default;
};

// 具体产品
class Bmw : public Car {
public:
    void run() override { std::cout << "宝马在飞驰" << std::endl; }
};

// 抽象工厂
class CarFactory {
public:
    virtual std::unique_ptr<Car> createCar() = 0;
    virtual ~CarFactory() = default;
};

// 具体工厂
class BmwFactory : public CarFactory {
public:
    std::unique_ptr<Car> createCar() override {
        return std::make_unique<Bmw>();
    }
};

// 使用
int main() {
    std::unique_ptr<CarFactory> factory = std::make_unique<BmwFactory>();
    auto car = factory->createCar();
    car->run(); // 输出:宝马在飞驰
}

2. 建造者模式

建造者模式将复杂对象的构建与表示分离,使同样的构建过程可以创建不同的表示。

Director

+construct()

«interface»

Builder

+buildPartA()

+buildPartB()

+getResult()

ConcreteBuilder

+buildPartA()

+buildPartB()

+getResult()

Product

+setPart()

经典实现 vs 链式调用
对比维度经典模式链式调用
角色完整性完整(Director+Builder+Product)简化(Builder+Product)
构建流程控制Director统一控制客户端直接控制
构建顺序复用Director可复用需要客户端重新写调用链
代码示例:链式调用
class Pizza {
public:
    class Builder {
        Pizza pizza_;
    public:
        Builder& setSize(const std::string& size) {
            pizza_.size_ = size;
            return *this;
        }
        Builder& addCheese() {
            pizza_.cheese_ = true;
            return *this;
        }
        Pizza build() { return pizza_; }
    };
    
private:
    std::string size_;
    bool cheese_ = false;
};

// 使用
Pizza pizza = Pizza::Builder()
    .setSize("大份")
    .addCheese()
    .build();

3. 原型模式

原型模式通过复制现有对象来创建新对象,避免昂贵的初始化操作。

clone

clone

clone

原型对象

新对象1

新对象2

新对象3

深拷贝 vs 浅拷贝
拷贝方式说明适用场景
浅拷贝只复制对象本身,引用成员共享成员都是值类型
深拷贝递归复制所有引用成员对象包含动态资源
代码示例
// 抽象原型
class Shape {
public:
    virtual std::unique_ptr<Shape> clone() const = 0;
    virtual void draw() const = 0;
    virtual ~Shape() = default;
};

// 具体原型
class Circle : public Shape {
private:
    int radius;
    std::string color;
    
public:
    Circle(int r, const std::string& c) : radius(r), color(c) {}
    
    // 拷贝构造函数(深拷贝)
    Circle(const Circle& other) = default;
    
    std::unique_ptr<Shape> clone() const override {
        return std::make_unique<Circle>(*this);
    }
    
    void draw() const override {
        std::cout << "Circle: radius=" << radius << ", color=" << color << std::endl;
    }
};

// 使用
int main() {
    auto original = std::make_unique<Circle>(10, "red");
    auto cloned = original->clone();  // 多态克隆
    cloned->draw();
}

二、结构型模式

结构型模式关注如何将类和对象组合成更大的结构。

4. 桥接模式

桥接模式将抽象部分与实现部分分离,使它们可以独立变化。

Shape

#color: Color

+draw()

«interface»

Color

+applyColor()

Circle

+draw()

Red

+applyColor()

Blue

+applyColor()

解决什么问题?

问题:有M种形状和N种颜色,使用继承需要创建 M×N 个类。

解决:桥接模式只需 M + N 个类。

代码示例
// 实现部分:颜色接口
class Color {
public:
    virtual void applyColor() const = 0;
};

class Red : public Color {
public:
    void applyColor() const override {
        std::cout << "应用红色" << std::endl;
    }
};

// 抽象部分:形状
class Shape {
protected:
    std::shared_ptr<Color> color_;
public:
    Shape(std::shared_ptr<Color> color) : color_(color) {}
    virtual void draw() const = 0;
};

class Circle : public Shape {
public:
    Circle(std::shared_ptr<Color> color) : Shape(color) {}
    void draw() const override {
        std::cout << "绘制圆形,";
        color_->applyColor();
    }
};

// 使用
int main() {
    auto red = std::make_shared<Red>();
    auto circle = Circle(red);
    circle.draw();  // 绘制圆形,应用红色
}

5. 适配器模式

适配器模式让不兼容的接口能够一起工作

调用

实现

转换调用

客户端

目标接口

适配器

适配者

类适配器 vs 对象适配器
对比维度类适配器对象适配器
实现方式多重继承对象组合
耦合度高(编译时绑定)低(运行时绑定)
灵活性高(可动态切换)
推荐度⭐⭐⭐⭐⭐⭐⭐
代码示例:对象适配器
// 目标接口(客户端期望)
class ChineseSocket {
public:
    virtual void charge_220v() const {
        std::cout << "使用220V电压充电" << std::endl;
    }
};

// 适配者(需要适配的类)
class USASocket {
public:
    void charge_110v() const {
        std::cout << "使用110V电压充电" << std::endl;
    }
};

// 适配器
class USAAdapter : public ChineseSocket {
private:
    std::unique_ptr<USASocket> usa_socket_;
public:
    USAAdapter(std::unique_ptr<USASocket> socket) 
        : usa_socket_(std::move(socket)) {}
    
    void charge_220v() const override {
        std::cout << "[适配器] 将110V转换为220V: ";
        usa_socket_->charge_110v();
    }
};

// 使用
int main() {
    auto usaSocket = std::make_unique<USASocket>();
    USAAdapter adapter(std::move(usaSocket));
    adapter.charge_220v();  // 美国插座也能在中国使用
}

6. 装饰模式

装饰模式动态地给对象添加新功能,比继承更灵活。

核心组件

装饰器1

装饰器2

装饰器3

装饰模式 vs 继承

装饰模式

核心

装饰器A

装饰器B

装饰器C

继承方式

父类

子类1

子类2

孙类1-1

孙类1-2

代码示例:咖啡订单系统
// 抽象组件
class Beverage {
public:
    virtual std::string getDescription() const = 0;
    virtual double cost() const = 0;
    virtual ~Beverage() = default;
};

// 具体组件
class Espresso : public Beverage {
public:
    std::string getDescription() const override { return "浓缩咖啡"; }
    double cost() const override { return 25.0; }
};

// 抽象装饰类
class CondimentDecorator : public Beverage {
protected:
    std::unique_ptr<Beverage> beverage_;
public:
    CondimentDecorator(std::unique_ptr<Beverage> bev) 
        : beverage_(std::move(bev)) {}
};

// 具体装饰:牛奶
class Milk : public CondimentDecorator {
public:
    Milk(std::unique_ptr<Beverage> bev) 
        : CondimentDecorator(std::move(bev)) {}
    
    std::string getDescription() const override {
        return beverage_->getDescription() + " + 牛奶";
    }
    double cost() const override { return beverage_->cost() + 5.0; }
};

// 使用
int main() {
    auto coffee = std::make_unique<Espresso>();
    coffee = std::make_unique<Milk>(std::move(coffee));
    coffee = std::make_unique<Milk>(std::move(coffee));  // 双份牛奶
    
    std::cout << coffee->getDescription() << ": ¥" << coffee->cost() << std::endl;
    // 输出:浓缩咖啡 + 牛奶 + 牛奶: ¥35
}

7. 组合模式

组合模式将对象组合成树形结构,使客户端对单个对象和组合对象的使用具有一致性

根节点 Composite

叶子 Leaf

子节点 Composite

叶子 Leaf

叶子 Leaf

代码示例:公司组织架构
// 抽象组件
class Employee {
public:
    virtual void showInfo(int depth = 0) = 0;
    virtual void add(std::shared_ptr<Employee>) {}
    virtual ~Employee() = default;
};

// 叶子:普通员工
class OrdinaryEmployee : public Employee {
private:
    std::string name_;
    std::string position_;
public:
    OrdinaryEmployee(std::string name, std::string pos) 
        : name_(name), position_(pos) {}
    
    void showInfo(int depth = 0) override {
        std::string indent(depth * 2, ' ');
        std::cout << indent << "- " << name_ << " (" << position_ << ")" << std::endl;
    }
};

// 组合:经理
class Manager : public Employee {
private:
    std::string name_;
    std::string position_;
    std::vector<std::shared_ptr<Employee>> subordinates_;
public:
    Manager(std::string name, std::string pos) 
        : name_(name), position_(pos) {}
    
    void add(std::shared_ptr<Employee> emp) override {
        subordinates_.push_back(emp);
    }
    
    void showInfo(int depth = 0) override {
        std::string indent(depth * 2, ' ');
        std::cout << indent << "+ " << name_ << " (经理 - " << position_ << ")" << std::endl;
        for (auto& emp : subordinates_) {
            emp->showInfo(depth + 1);
        }
    }
};

// 使用
int main() {
    auto ceo = std::make_shared<Manager>("张三", "CEO");
    auto techManager = std::make_shared<Manager>("李四", "技术总监");
    auto dev = std::make_shared<OrdinaryEmployee>("王五", "工程师");
    
    ceo->add(techManager);
    techManager->add(dev);
    ceo->showInfo();
}

8. 享元模式

享元模式通过共享大量细粒度对象来减少内存占用。

享元模式

共享对象A

引用1

引用2

共享对象B

引用3

传统方式

对象1

对象2

对象3

对象4

内在状态 vs 外在状态
状态类型说明示例
内在状态可共享,存储在享元对象内部字符的ASCII码
外在状态不可共享,由客户端传递字体大小、颜色
代码示例:字符共享
// 享元接口
class Character {
public:
    virtual void display(int fontSize, const std::string& color) = 0;
    virtual ~Character() = default;
};

// 具体享元
class ConcreteCharacter : public Character {
private:
    char symbol_;  // 内在状态(可共享)
public:
    ConcreteCharacter(char c) : symbol_(c) {}
    
    void display(int fontSize, const std::string& color) override {
        std::cout << "字符: " << symbol_ 
                  << ", 字号: " << fontSize 
                  << ", 颜色: " << color << std::endl;
    }
};

// 享元工厂
class CharacterFactory {
private:
    std::unordered_map<char, std::shared_ptr<Character>> pool_;
public:
    std::shared_ptr<Character> getCharacter(char key) {
        if (pool_.find(key) == pool_.end()) {
            pool_[key] = std::make_shared<ConcreteCharacter>(key);
            std::cout << "创建新字符: " << key << std::endl;
        }
        return pool_[key];
    }
    
    size_t size() const { return pool_.size(); }
};

// 使用
int main() {
    CharacterFactory factory;
    std::string text = "Hello";
    
    for (char c : text) {
        auto ch = factory.getCharacter(c);
        ch->display(12, "Black");
    }
    // 输出:只创建了4个字符对象(H,e,l,o),l被重用
    std::cout << "实际创建对象数: " << factory.size() << std::endl;
}

9. 外观模式

外观模式为子系统提供统一的简化接口

SubCSubBSubAFacadeClientSubCSubBSubAFacadeClientoperation()doA()doB()doC()完成
代码示例:家庭影院
// 子系统类
class Projector {
public:
    void on() { std::cout << "投影仪打开" << std::endl; }
    void off() { std::cout << "投影仪关闭" << std::endl; }
};

class SoundSystem {
public:
    void on() { std::cout << "音响打开" << std::endl; }
    void setVolume(int level) { std::cout << "音量: " << level << std::endl; }
};

class BluRayPlayer {
public:
    void on() { std::cout << "播放器打开" << std::endl; }
    void play(const std::string& movie) { std::cout << "播放: " << movie << std::endl; }
};

// 外观类
class HomeTheaterFacade {
private:
    Projector* projector_;
    SoundSystem* sound_;
    BluRayPlayer* player_;
    
public:
    HomeTheaterFacade(Projector* p, SoundSystem* s, BluRayPlayer* b)
        : projector_(p), sound_(s), player_(b) {}
    
    void watchMovie(const std::string& movie) {
        std::cout << "=== 开始观影 ===" << std::endl;
        projector_->on();
        sound_->on();
        sound_->setVolume(30);
        player_->on();
        player_->play(movie);
        std::cout << "=== 观影中 ===" << std::endl;
    }
    
    void endMovie() {
        std::cout << "=== 结束观影 ===" << std::endl;
        projector_->off();
        sound_->off();
        player_->off();
    }
};

// 使用
int main() {
    Projector proj;
    SoundSystem sound;
    BluRayPlayer player;
    
    HomeTheaterFacade theater(&proj, &sound, &player);
    theater.watchMovie("盗梦空间");
    theater.endMovie();
}

10. 代理模式

代理模式为另一个对象提供替身以控制访问

«interface»

Subject

+request()

RealSubject

+request()

Proxy

-realSubject: RealSubject

+request()

代理模式分类
类型用途示例
虚拟代理延迟加载图片懒加载
保护代理访问控制权限检查
远程代理隐藏地址空间RPC调用
智能引用附加操作日志、缓存
代码示例:保护代理
// 抽象主题
class Document {
public:
    virtual void read() = 0;
    virtual void write(const std::string& content) = 0;
    virtual ~Document() = default;
};

// 真实主题
class RealDocument : public Document {
private:
    std::string content_;
public:
    void read() override { std::cout << "阅读: " << content_ << std::endl; }
    void write(const std::string& c) override { content_ = c; }
};

// 代理:带权限控制
class DocumentProxy : public Document {
private:
    std::unique_ptr<RealDocument> realDoc_;
    std::string userRole_;
    
    bool hasWritePermission() const {
        return userRole_ == "admin" || userRole_ == "editor";
    }
    
public:
    DocumentProxy(const std::string& role) 
        : realDoc_(std::make_unique<RealDocument>()), userRole_(role) {}
    
    void read() override {
        realDoc_->read();  // 所有人都可读
    }
    
    void write(const std::string& content) override {
        if (hasWritePermission()) {
            realDoc_->write(content);
            std::cout << "写入成功" << std::endl;
        } else {
            std::cout << "权限不足,无法写入" << std::endl;
        }
    }
};

// 使用
int main() {
    auto userDoc = DocumentProxy("user");
    userDoc.write("新内容");  // 失败
    
    auto adminDoc = DocumentProxy("admin");
    adminDoc.write("重要内容");  // 成功
}

三、模式对比总结

创建型模式对比

模式核心问题解决方案
工厂方法创建单个对象将创建延迟到子类
抽象工厂创建产品族用工厂创建相关对象
建造者创建复杂对象分步构建
原型模式创建相似对象克隆现有对象

结构型模式对比

模式核心思想关键特征
桥接分离抽象与实现两个独立维度变化
适配器接口转换让不兼容接口协作
装饰动态添加功能比继承更灵活
组合树形结构统一对待叶子与容器
享元共享对象减少内存占用
外观简化接口封装复杂子系统
代理控制访问延迟加载/权限控制

四、选择建议

遇到设计问题

需要创建对象?

对象创建复杂?

建造者模式

需要克隆?

原型模式

产品会扩展?

工厂方法

简单工厂

需要接口转换?

适配器模式

需要控制访问?

代理模式

需要减少内存?

享元模式

有树形结构?

组合模式

需要简化接口?

外观模式

多维度变化?

桥接模式

装饰模式

核心原则

别一开始就上最复杂的设计。先用简单方案解决问题,当发现需要扩展时,再平滑地重构为更合适的设计模式。


五、设计模式应用场景速查表

1.创建型模式

模式核心问题典型应用场景何时使用何时避免
简单工厂根据参数创建不同产品解析配置文件创建对象、日志记录器创建、数据库驱动加载产品种类少(<5种),调用者只需传参,不想关心创建细节产品频繁增加,需要经常修改工厂类
工厂方法创建单个对象,支持扩展跨平台UI组件创建、文档编辑器支持多种格式、游戏角色生成器产品会不断扩展,需要符合开闭原则,每个产品有专属创建逻辑产品类型固定不变,或创建逻辑极其简单
抽象工厂创建一系列相关对象(产品族)跨平台UI工具包(Windows/Mac/Linux风格)、数据库连接族(连接/命令/事务)、游戏皮肤系统需要切换整套产品系列,需要保证产品族内对象兼容性产品族不会变化,或产品间没有关联关系
建造者分步创建复杂对象SQL查询构建器、HTTP请求构建器、PDF文档生成器、披萨订单系统构造参数多且部分可选(>5个),需要控制构建顺序,需要生成不同表示形式对象构造简单(参数<3个),或不需要多种表示形式
原型模式通过克隆创建对象游戏中的怪物复制、图形编辑器的复制粘贴、单元格样式克隆、大对象缓存对象创建成本高(复杂计算/数据库查询),需要运行时动态配置对象类型对象有循环引用,或克隆实现极其复杂

2.结构型模式

模式核心问题典型应用场景何时使用何时避免
适配器让不兼容接口协作不同日志库统一接口、新旧系统API对接、第三方支付集成、电源适配器需要集成第三方库但接口不匹配,需要兼容旧版API,需要统一多个相似接口可以修改原始类接口,或系统设计阶段就能统一接口
桥接分离抽象与实现跨平台绘图(形状+渲染引擎)、消息系统(消息类型+发送方式)、遥控器(设备+控制)有两个以上独立变化的维度,需要避免M×N的类爆炸,需要在运行时切换实现只有一个变化维度,或维度间没有独立变化的需求
组合树形结构统一处理文件系统(目录+文件)、组织架构(部门+员工)、GUI控件(容器+控件)、菜单系统有部分-整体的层次结构,需要统一对待叶子节点和容器节点结构是扁平的,或不需要递归操作
装饰动态添加功能咖啡配料系统、数据流加密/压缩、GUI控件添加边框/滚动条、权限动态增强需要在不修改原类的情况下动态添加功能,功能组合数量大(避免类爆炸),需要运行时撤销功能功能层级固定且少,或继承结构已经足够清晰
外观简化复杂子系统家庭影院系统、编译器前端、视频转换工具、微服务聚合层需要为复杂子系统提供简单入口,需要分层解耦,需要为遗留系统提供新接口子系统本身很简单,或客户端需要直接访问底层功能
享元共享细粒度对象文本编辑器字符渲染、游戏中的子弹/粒子系统、数据库连接池、字符串常量池存在大量重复对象(>1000个),对象大部分状态可外部化,内存是瓶颈对象数量少,或状态无法分离为内/外在状态
代理控制对象访问图片懒加载、权限控制(不同角色不同权限)、远程服务调用(RPC)、日志/性能监控需要延迟加载(虚拟代理),需要访问控制(保护代理),需要添加透明操作(智能引用)不需要任何控制或附加操作,或代理类变得过于复杂

3.快速决策矩阵

遇到的情况推荐模式一句话理由
创建对象时有一堆if-else工厂方法把创建逻辑封装起来
构造函数参数太多(>5个)建造者链式调用,清晰优雅
需要复制一个复杂对象原型模式克隆比重新创建更高效
两个维度都在变化桥接模式避免类爆炸
第三方库接口不匹配适配器加一层转换
需要动态给对象加功能装饰模式比继承灵活
有树形结构需要处理组合模式统一对待叶子与容器
大量相似对象内存爆炸享元模式共享内在状态
复杂子系统难用外观模式给个简化入口
需要控制对象访问代理模式加个中间人
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值