68.More C++ Idioms - Policy-Based Design基于策略设计模式 and more

enter image description here

好的,详细介绍一下Policy-based design模式?


ChatGPT 说: Policy-Based Design(基于策略的设计) Policy-Based Design(策略模式)是一种基于模板(templates)的 C++ 设计模式,允许开发者通过不同的策略类(policy classes)组合对象的行为,而无需继承或运行时多态。

与传统的 面向对象编程(OOP) 不同,策略模式使用编译期(compile-time)多态,可以实现更灵活和高效的代码复用,避免虚函数调用开销,提高优化机会。

1. Policy-Based Design 的核心思想

1️⃣ 策略类(Policy Classes) 负责定义某个特定行为,例如日志策略、内存管理策略等。 这些类通常是无状态(stateless)的,只有 static 函数。

2️⃣ 模板参数(Template Parameters) 让 Host(宿主类)通过模板参数选择不同的策略,而不是继承多个类。

3️⃣ 静态多态(Static Polymorphism)

运行时多态(virtual)是基于对象的,静态多态是基于模板的代码替换(code substitution)。 不需要虚函数(virtual),可以完全在编译期决定行为,提高效率。 2. Policy-Based Design 示例

示例 1:日志策略 我们通过 Policy 来选择不同的日志输出方式,而不需要 if-else 判断:

#include <iostream>

// 策略 1: 控制台日志
struct ConsoleLogger {
    static void log(const std::string& msg) {
        std::cout << "[Console] " << msg << std::endl;
    }
};

// 策略 2: 文件日志(示例)
struct FileLogger {
    static void log(const std::string& msg) {
        // 这里只是示例,实际应用时需要文件操作
        std::cout << "[File] " << msg << std::endl;
    }
};

// 宿主类(Host),使用模板参数选择不同策略
template <typename LoggerPolicy>
class Application {
public:
    void run() {
        LoggerPolicy::log("Application started");
    }
};

int main() {
    Application<ConsoleLogger> app1;
    app1.run();  // 输出到控制台

    Application<FileLogger> app2;
    app2.run();  // 输出到“文件”
}

关键点 Application 和 Application 在编译期选择不同的策略,不会有额外的运行时开销。 避免了虚函数(virtual),编译器可以进行优化,提高性能。

3. Policy-Based Design 在 STL 设计中的应用 示例 2:STL 中的 std::allocator C++ 标准库的 STL 容器(如 std::vector)使用策略模式选择不同的 Allocator(分配器),如下:

template <typename T, typename Allocator = std::allocator<T>>
class MyVector {
    Allocator alloc;  // 选择分配器策略
};

在使用 std::vector 时,我们可以自定义内存分配策略:

std::vector<int, MyCustomAllocator<int>> v;

这正是 Policy-Based Design 的一个典型案例!

4. Policy 组合:Layered Policy Design 有时候,我们希望将多个策略组合,可以使用多模板参数:

#include <iostream>

// 策略 1: 线程安全策略
struct ThreadSafe {
    void lock() { std::cout << "Locking...\n"; }
    void unlock() { std::cout << "Unlocking...\n"; }
};

// 策略 2: 非线程安全策略
struct NonThreadSafe {
    void lock() {}  // 空实现
    void unlock() {}
};

// 策略 3: 默认存储策略
template <typename T>
struct DefaultStorage {
    T data;
};

// 宿主类:组合多个策略
template <typename StoragePolicy, typename ThreadPolicy>
class ResourceManager : private StoragePolicy, private ThreadPolicy {
public:
    using StoragePolicy::data;

    void access() {
        ThreadPolicy::lock();
        std::cout << "Accessing resource: " << data << std::endl;
        ThreadPolicy::unlock();
    }
};

int main() {
    ResourceManager<DefaultStorage<int>, ThreadSafe> manager1;
    manager1.data = 42;
    manager1.access();  // 线程安全访问

    ResourceManager<DefaultStorage<int>, NonThreadSafe> manager2;
    manager2.data = 99;
    manager2.access();  // 非线程安全访问
}

关键点 ✅ 策略可以自由组合(如 StoragePolicy + ThreadPolicy)。 ✅ 避免继承层次过深的问题,提高灵活性和可维护性。 ✅ 可以在编译期决定行为,提高性能,减少 if-else 开销。

5. Policy-Based Design 的优缺点 ✅ 优点 ✔ 运行期零开销:所有决策在编译期完成,无需 if-else 或 virtual 开销。 ✔ 灵活组合:策略类可以自由组合,而不会导致继承层次过深的问题。 ✔ 提升代码复用性:多个类可以复用相同的 Policy,提高代码一致性。

❌ 缺点 ❌ 编译时间增加:模板展开导致编译时间增加,尤其在大项目中。 ❌ 可读性下降:对于不熟悉模板编程的开发者,代码可读性可能变差。 ❌ 二进制膨胀(Code Bloat):模板可能会导致多个实例化版本,增加可执行文件大小。

6. 什么时候使用 Policy-Based Design? ✅ 需要灵活可配置的设计,例如日志、内存管理、锁策略。 ✅ 性能敏感场景,避免虚函数调用开销,如 std::vector 的 Allocator 设计。 ✅ 库设计,让用户在不修改核心代码的情况下自定义行为。 ✅ 避免继承层次过深,让多个策略可以灵活组合。

7. 结合 Attorney-Client 设计 Policy-Based Design 还可以和 Friendship and the Attorney-Client 结合,比如:

Server 类使用 Attorney 控制访问, Attorney 使用 Policy 选择不同的访问规则。 如果你感兴趣,我们可以进一步讨论如何结合这两种模式,实现一个 可配置权限管理系统!🚀

您说: 介绍得有点深入,我得慢慢消化,意思是这个policy-based design与attorney-client本身是不想管得策略,但是可以组合一起构建更复杂得应用场景? 比如不同得策略对应不同得attorney?


ChatGPT 说: 没错!你理解得非常正确!Policy-Based Design 和 Attorney-Client Idiom 本身的设计理念并没有直接关系,但它们确实可以结合在一起,来构建更复杂、灵活的应用场景。

如何将这两种模式结合? 我们可以将 Policy-Based Design 用于定义不同的策略,而将 Attorney-Client Idiom 用于控制访问,从而在实现时通过不同的策略选择来管理 访问控制 的细粒度。

具体组合方式 每个 Attorney 可能对应一个特定的策略,不同的策略决定了访问权限或行为。 通过策略选择访问方式,而不仅仅是访问某个功能或数据。 在 Attorney 中实现不同的行为,通过模板和策略来决定如何操作数据。 例子:结合 Attorney-Client 与 Policy-Based Design 假设你有一个 Server 类,其中包含不同的资源管理操作(比如读取、写入),而每种操作都有不同的策略控制访问权限。

  • 步骤 1:定义策略(Policy)

每种策略控制一种行为,比如是否可以读取数据,是否可以修改数据。

// 读取权限策略
struct ReadPolicy {
    static void access() { std::cout << "Reading data\n"; }
};

// 写入权限策略
struct WritePolicy {
    static void access() { std::cout << "Writing data\n"; }
};
  • 步骤 2:定义 Attorney 类

然后,你通过 Attorney 控制客户端(Client)的访问,根据不同的策略决定访问行为。

class Server {
private:
    // 只有Attorney可以访问的私有数据
    int secretData = 42;

    // 将策略类作为模板参数传递
    template <typename Policy>
    friend class Attorney;
};

// Attorney 类,根据不同的策略提供访问权限
template <typename Policy>
class Attorney {
public:
    static void access(Server& server) {
        Policy::access();  // 调用策略方法,执行不同操作
    }
};

步骤 3:客户端(Client) 客户端会根据需要访问的功能选择不同的策略。

int main() {
    Server server;

    // 使用不同的策略访问 Server
    Attorney<ReadPolicy>::access(server);  // 执行读取操作
    Attorney<WritePolicy>::access(server); // 执行写入操作
}

如何工作: - Server 类包含一些敏感数据(secretData),而访问这些数据的权限是通过 策略 来控制的。 - Attorney 类通过 策略 选择不同的行为(如读取、写入),确保只有被授权的操作能够执行。 - 客户端不直接访问 Server,而是通过 Attorney 来执行特定操作。 总结与扩展: 灵活组合策略和访问控制:

你可以为每个 Attorney 配置不同的 策略,实现不同级别的控制。 例如:ReadPolicy 允许读取,WritePolicy 允许写入,AdminPolicy 可以读取和写入。 更加细粒度的权限管理:

利用模板和策略,你可以非常精细地控制每个 Attorney 拥有的权限。 如果需要扩展系统,你只需添加新的策略,而不需要改变已有的代码。 更复杂的应用场景:

你可以构建一个可扩展的权限系统,支持多种操作类型、不同的授权策略,甚至可以结合其他设计模式(如 策略模式、责任链模式)来扩展功能。 扩展应用场景示例: Web 权限管理:根据不同的用户角色(管理员、普通用户、访客)创建不同的策略,控制用户的操作权限(如 ReadPolicy,EditPolicy,DeletePolicy)。 多策略组合:例如,ReadPolicy 和 CachePolicy 组合,允许读取并缓存数据,避免重复请求。 结语 简单来说,Policy-Based Design 可以让你灵活地管理行为策略,而 Attorney-Client Idiom 则控制访问权限。两者结合,可以实现一个非常灵活、可扩展的系统设计,从而在编译期优化代码,避免运行时的性能损失,并提供精细化的权限管理。

您说: 基本领会了Policy-Based Design核心思想: 策略类,透过模板实现,模板参数提供策略选择选项, 这样实现了静态多态性


ChatGPT 说: 太棒了,你对 Policy-Based Design 的核心思想已经有了很好的理解!确实,策略类和模板参数的组合为我们提供了一种非常灵活且高效的设计方式,利用静态多态(通过模板)来实现代码的可配置性和优化。

总结一下你的理解:

  • 策略类(Policy Classes):

这些类通常是无状态的,通过 static 方法定义特定的行为(例如,日志记录策略、内存管理策略等)。

  • 模板参数:

使用模板类作为宿主类的参数,来选择不同的策略。 策略选择在编译期进行,这意味着我们可以避免运行时的 if-else 判断,完全通过静态多态实现不同的行为。

  • 静态多态性:

静态多态性(static polymorphism)是通过模板来在编译期选择行为,而不是在运行时选择。这不仅提高了效率,还避免了虚函数调用的开销。 如何进一步深入? 如果你想更深入地掌握 Policy-Based Design,可以从以下几个方面进一步研究:

  • 复杂策略组合:

在实际应用中,我们可能需要组合多个策略。可以通过多个模板参数来实现多个策略的组合,或者使用 模板特化 来为特定策略组合提供不同的实现。

  • 使用 SFINAE 和 std::enable_if:

在实际应用中,你可能会使用 SFINAE(Substitution Failure Is Not An Error)和 std::enable_if 来实现更加动态和精细的策略选择,条件化地启用某些策略组合。

  • 策略优化与代码实例:

实际项目中,了解不同策略的优化方案很重要。例如,可以将不同策略分配给不同的类或函数,以确保资源和性能的最优利用。 进一步扩展: - 模板元编程(Template Metaprogramming):Policy-Based Design 经常与模板元编程一起使用,进一步提高代码复用性和效率。 - 与其他设计模式结合:比如和 策略模式、责任链模式 等设计模式结合,能够提供更加丰富和灵活的设计。 如果你有更具体的使用场景或问题,随时可以继续讨论!

评论