C++ 使用部分共享接口组织对象

C++ 使用部分共享接口组织对象,c++,design-patterns,interface,polymorphism,C++,Design Patterns,Interface,Polymorphism,我最近遇到了一些情况,我认为可以用不同的设计来解决,但我不知道有什么模式适合 在所有这些情况下,我有几个类部分共享一个API。例如,记录器类: struct ILogger { virtual void log(string msg) = 0; }; struct StdOutLogger : public ILogger { void log(string msg) override; // Log to stdout }; struct FileLogger : public ILo

我最近遇到了一些情况,我认为可以用不同的设计来解决,但我不知道有什么模式适合

在所有这些情况下,我有几个类部分共享一个API。例如,记录器类:

struct ILogger { virtual void log(string msg) = 0; };
struct StdOutLogger : public ILogger {
    void log(string msg) override; // Log to stdout
};
struct FileLogger : public ILogger {
    void log(string msg) override; // Log to file
};
struct GuiLogger : public ILogger {
    void log(string msg) override; // Log to GUI
    void draw();
    void clear();
};
或许:

struct Graphic {
    virtual void draw();
    virtual void setPosition();
    // etc.
};
struct AnimatedGraphic : public Graphic {
    void draw() override;
    void start();
    void stop();
    void setLooping(bool loop);
};
现在,根据这些对象的所有者,我可能有一个指向公共接口的引用/指针容器:

class LogManager {
    std::vector<std::unique_ptr<ILogger>> _loggers;
    // ...
};
类日志管理器{
标准::矢量记录器;
// ...
};
或者我可以将这些类型分开,并在运行时选择要使用的类型:

// This is already starting to get messy
class SomethingWithGraphic {
    std::unique_ptr<Graphic> _graphic;
    std::unique_ptr<AnimatedGraphic> _animatedGraphic;
    // ...
};
//这已经开始变得一团糟了
用图形分类{
std::唯一的ptr图形;
std::唯一的_ptr _animatedGraphic;
// ...
};
第一个解决方案很好,直到我需要开始使用公共接口之外的功能。第二个解决方案允许我选择所需的一个,但它容易出错,并且到处都需要难看的分支

我已经想出了几个替代方案,但我还没有找到一个真正让人感觉正确的方案

  • 保留一个拥有的容器,并通过不同的接口创建指向拥有的对象的其他容器。(要求容器保持同步)

  • 将所有函数添加到接口,但将不需要额外函数的对象的实现留空。(这些函数实际上不属于该接口的一部分)

  • 存储所有潜在类型的变体。(感觉像黑客,到处都需要访客)

  • 使用记录器示例:

    //// 1 ////
    struct IDrawable {
        virtual void draw() = 0;
        virtual void clear() = 0;
    };
    std::vector<std::unique_ptr<ILogger>> _loggers;
    std::vector<IDrawable*>               _drawableLoggers;
    
    //// 2 ////
    struct ILogger {
        virtual void log(string msg) = 0;
        virtual void draw() {};
        virtual void clear() {};
    };
    struct StdOutLogger : public ILogger {
        void log(string msg) override; // Log to stdout
    };
    struct FileLogger : public ILogger {
        void log(string msg) override; // Log to file
    };
    struct GuiLogger : public ILogger {
        void log(string msg) override; // Log to GUI
        void draw() override;
        void clear() override;
    };
    
    //// 3 ////
    std::vector<std::variant<StdOutLogger, FileLogger, GuiLogger>> _loggers;
    
    ///1////
    结构IDrawable{
    虚空绘制()=0;
    虚空清除()=0;
    };
    标准::矢量记录器;
    std::矢量可绘图记录器;
    //// 2 ////
    结构ILogger{
    虚拟无效日志(字符串消息)=0;
    虚空绘制(){};
    虚空清除(){};
    };
    结构StdOutLogger:公共ILogger{
    void log(字符串msg)override;//记录到标准输出
    };
    结构文件记录器:公共ILogger{
    无效日志(字符串msg)覆盖;//记录到文件
    };
    结构GuiLogger:公共ILogger{
    无效日志(字符串msg)覆盖;//记录到GUI
    void draw()覆盖;
    void clear()覆盖;
    };
    //// 3 ////
    标准::矢量记录器;
    
    #我认为1似乎是最正确的,但仍然不是最好的


    有人知道有什么模式或结构可以解决这个问题吗?

    一种可行的方法:您可以使用指向接口的指针或引用向量,并在所有情况下实现访问者模式,在这些情况下,您希望从一个实例中获取其实际类型,并调用不属于公共接口的方法

    下面是一个简单的工作示例:

    #include<iostream>
    #include<memory>
    #include<vector>
    
    struct Visitor;
    
    struct Interface {
        virtual void method() = 0;
        virtual void accept(Visitor &) = 0;
    };
    
    struct A: Interface {
        void method() override { std::cout << "A::method" << std::endl; }
        void f() { std::cout << "A::f" << std::endl; }
        void accept(Visitor &) override;
    };
    
    struct B: Interface {
        void method() override { std::cout << "B::method" << std::endl; }
        void g() { std::cout << "B::g" << std::endl; }
        void accept(Visitor &) override;
    };
    
    struct Visitor {
        void visit(A &a) { a.f(); }
        void visit(B &b) { b.g(); }
    };
    
    void A::accept(Visitor &v) { v.visit(*this); }
    void B::accept(Visitor &v) { v.visit(*this); }
    
    int main() {
        std::vector<std::unique_ptr<Interface>> vec;
    
        vec.push_back(std::make_unique<A>());
        vec.push_back(std::make_unique<B>());
    
        Visitor visitor;
    
        for(auto &&i: vec) {
            i->method();
            i->accept(visitor);
        }
    }
    
    #包括
    #包括
    #包括
    结构访问者;
    结构接口{
    虚空方法()=0;
    虚拟无效接受(访问者&)=0;
    };
    结构A:接口{
    
    void method()重写{std::cout,这样保存所有
    ILogger
    实例的
    LogManager
    将拥有所有接口?比如
    draw()
    clear()
    来自
    GuiLogger
    flush()
    open()
    close()
    来自
    文件记录器
    ?当您添加越来越多的
    记录器
    时,
    日志管理器
    将变得越来越复杂…接口被发明来隐藏实现,所以通常您不需要打开其他详细信息。
    日志管理器
    应该只知道
    日志
    方法。这是OO键。不要强制使用int如果某个实现不适合,请访问该实现。在这种情况下,如果您想以多种方式记录某个内容,请查看责任链。有3个处理程序,将具体的记录器提供给每个处理程序,并构建一个请求,以便沿该链传递。然后,链中的每个日志处理程序都可以执行任何它想执行的操作。当然,这意味着对于每个记录器实现,您都需要一个新的处理程序,更不用说大量的锅炉板来设置链。