跳转至

ThemeFactory - 主题工厂

ThemeFactory 是创建主题实例的抽象接口,负责从名字或 JSON 数据构造 ICFTheme 对象,以及将现有主题序列化为 JSON。为什么需要工厂而不是直接 new 一个主题?因为主题的创建过程往往比较复杂——需要组合颜色方案、动画规格、圆角尺度等多个组件,工厂模式把这个创建逻辑封装起来,也让加载配置文件成为可能。

工厂接口

工厂是一个纯虚基类,你需要继承它并实现三个方法:

#include "ui/core/theme_factory.h"

class MyThemeFactory : public cf::ui::core::ThemeFactory {
public:
    // 从名字创建主题
    std::unique_ptr<cf::ui::core::ICFTheme> fromName(const char* name) override;

    // 从 JSON 创建主题
    std::unique_ptr<cf::ui::core::ICFTheme> fromJson(const QByteArray& json) override;

    // 将主题序列化为 JSON
    QByteArray toJson(cf::ui::core::ICFTheme* raw_theme) override;
};

注意返回类型是 std::unique_ptr<ICFTheme>,调用者获得主题的所有权。这个设计很自然——工厂负责生产,消费者负责销毁。

从名字创建

fromName 是最常见的创建方式,用于内置主题或预定义的主题变体:

std::unique_ptr<cf::ui::core::ICFTheme> MyThemeFactory::fromName(const char* name) {
    auto theme = std::make_unique<cf::ui::core::ICFTheme>();

    // 根据名字配置不同的组件
    if (strcmp(name, "default") == 0) {
        theme->color_scheme_ = std::make_unique<DefaultColorScheme>();
        theme->motion_spec_ = std::make_unique<DefaultMotionSpec>();
        theme->radius_scale_ = std::make_unique<DefaultRadiusScale>();
        theme->font_type_ = std::make_unique<DefaultFontType>();
    } else if (strcmp(name, "compact") == 0) {
        theme->color_scheme_ = std::make_unique<DefaultColorScheme>();
        theme->motion_spec_ = std::make_unique<FasterMotionSpec>();  // 更快的动画
        theme->radius_scale_ = std::make_unique<CompactRadiusScale>();  // 更小的圆角
        theme->font_type_ = std::make_unique<CompactFontType>();
    } else {
        return nullptr;  // 未知主题返回空指针
    }

    return theme;
}

name 参数来自 ThemeManager::insert_one 时注册的名字,但工厂内部可以有自己的命名空间逻辑——你可以用同一个工厂支持多个主题变体,或者完全忽略名字只返回固定配置。

⚠️ 如果无法创建主题(比如名字不认识),返回 nullptr 即可,不要抛异常。ThemeManager 会检测空指针并优雅处理。

从 JSON 创建

fromJson 让主题可以从配置文件加载,这是实现用户自定义主题的基础:

std::unique_ptr<cf::ui::core::ICFTheme> MyThemeFactory::fromJson(const QByteArray& json) {
    auto theme = std::make_unique<cf::ui::core::ICFTheme>();

    // 使用 QJsonDocument 解析
    QJsonDocument doc = QJsonDocument::fromJson(json);
    if (doc.isNull() || !doc.isObject()) {
        return nullptr;
    }

    QJsonObject root = doc.object();

    // 解析颜色方案
    QJsonObject colors = root["colors"].toObject();
    theme->color_scheme_ = std::make_unique<CustomColorScheme>(
        QColor(colors["background"].toString()),
        QColor(colors["foreground"].toString())
    );

    // 解析动画规格
    QJsonObject motion = root["motion"].toObject();
    theme->motion_spec_ = std::make_unique<CustomMotionSpec>(
        motion["duration"].toInt(200),
        motion["easing"].toString("ease").toStdString()
    );

    // ... 解析其他组件

    return theme;
}

JSON 格式完全由你定义,只要工厂能解析就行。我们建议遵循一定的约定,比如 colors 对象包含颜色、motion 对象包含动画参数,这样不同主题的配置文件可以保持一致性。

序列化到 JSON

toJsonfromJson 的逆操作,用于导出当前主题配置:

QByteArray MyThemeFactory::toJson(cf::ui::core::ICFTheme* raw_theme) {
    if (!raw_theme) {
        return QByteArray();
    }

    QJsonObject root;

    // 序列化颜色方案
    const auto& colors = raw_theme->color_scheme();
    QJsonObject colorObj;
    colorObj["background"] = colors.background().name();
    colorObj["foreground"] = colors.foreground().name();
    root["colors"] = colorObj;

    // 序列化动画规格
    const auto& motion = raw_theme->motion_spec();
    QJsonObject motionObj;
    motionObj["duration"] = motion.default_duration();
    root["motion"] = motionObj;

    // ... 序列化其他组件

    return QJsonDocument(root).toJson(QJsonDocument::Compact);
}

raw_theme 是裸指针而不是引用,这是为了兼容 Qt 的信号槽参数传递习惯。但在实际使用中,你传进来的应该总是一个有效的主题指针。

⚠️ 序列化时可能会丢失信息——如果你的 ICFTheme 实现包含一些无法简单表示的组件(比如引用了外部资源),fromJsontoJson 不必是完全可逆的。只要能保存"用户可配置"的那部分就够了。

注册到管理器

工厂本身不做任何事,需要注册到 ThemeManager 才能发挥作用:

// 注册内置主题
cf::ui::core::ThemeManager::instance().insert_one("builtin", []() {
    return std::make_unique<MyThemeFactory>();
});

// 注册支持 JSON 的主题工厂
cf::ui::core::ThemeManager::instance().insert_one("custom", []() {
    return std::make_unique<JsonThemeFactory>();
});

注意这里用的是 lambda 而不是直接传工厂实例。ThemeManager 会在需要时调用 lambda 来创建工厂,而不是在注册时就创建。这个延迟创建机制可以减少启动时的开销,特别是当你注册了很多插件主题但大部分都不会被使用时。

错误处理

工厂方法的错误处理建议遵循以下原则:

std::unique_ptr<cf::ui::core::ICFTheme> MyThemeFactory::fromJson(const QByteArray& json) {
    // 1. 先检查基本有效性
    if (json.isEmpty()) {
        qWarning() << "Empty JSON data";
        return nullptr;
    }

    // 2. 解析失败返回 nullptr,不要抛异常
    QJsonDocument doc = QJsonDocument::fromJson(json);
    if (doc.isNull()) {
        qWarning() << "Invalid JSON format";
        return nullptr;
    }

    // 3. 缺少必要字段可以回退到默认值
    QJsonObject root = doc.object();
    int duration = root.value("duration").toInt(200);  // 默认 200ms

    // 4. 只在真正无法恢复时返回 nullptr
    if (!root.contains("colors")) {
        qWarning() << "Missing required 'colors' field";
        return nullptr;
    }

    // ... 构造主题
}

返回 nullptr 表示"无法创建",ThemeManager 会把这个情况传达给调用方。我们不建议在工厂方法里抛异常,因为 Qt 的信号槽机制和异常配合得不太好,而且这也让错误处理逻辑更清晰。

实现示例

这是一个完整的工厂实现,支持名字创建和 JSON 序列化:

class ModernThemeFactory : public cf::ui::core::ThemeFactory {
public:
    std::unique_ptr<cf::ui::core::ICFTheme> fromName(const char* name) override {
        auto theme = std::make_unique<cf::ui::core::ICFTheme>();

        // 现代风格使用鲜艳的accent色和更快的动画
        theme->color_scheme_ = std::make_unique<ModernColorScheme>();
        theme->motion_spec_ = std::make_unique<ModernMotionSpec>();
        theme->radius_scale_ = std::make_unique<ModernRadiusScale>();
        theme->font_type_ = std::make_unique<ModernFontType>();

        return theme;
    }

    std::unique_ptr<cf::ui::core::ICFTheme> fromJson(const QByteArray& json) override {
        QJsonDocument doc = QJsonDocument::fromJson(json);
        if (doc.isNull() || !doc.isObject()) {
            return nullptr;
        }

        QJsonObject root = doc.object();
        auto theme = std::make_unique<cf::ui::core::ICFTheme>();

        // 支持自定义 accent 颜色
        if (root.contains("accent_color")) {
            QColor accent(root["accent_color"].toString());
            theme->color_scheme_ = std::make_unique<ModernColorScheme>(accent);
        } else {
            theme->color_scheme_ = std::make_unique<ModernColorScheme>();
        }

        // 支持自定义动画速度
        int duration = root.value("animation_duration").toInt(150);
        theme->motion_spec_ = std::make_unique<ModernMotionSpec>(duration);

        // 其他组件使用默认值
        theme->radius_scale_ = std::make_unique<ModernRadiusScale>();
        theme->font_type_ = std::make_unique<ModernFontType>();

        return theme;
    }

    QByteArray toJson(cf::ui::core::ICFTheme* raw_theme) override {
        if (!raw_theme) {
            return QByteArray();
        }

        QJsonObject root;

        // 导出关键配置,供后续 fromJson 恢复
        const auto& colors = raw_theme->color_scheme();
        root["accent_color"] = colors.accent().name();

        const auto& motion = raw_theme->motion_spec();
        root["animation_duration"] = motion.default_duration();

        // 添加元数据
        root["theme_name"] = "modern";
        root["version"] = "1.0";

        return QJsonDocument(root).toJson();
    }
};

相关文档