使用部分共享界面组织对象

Organizing objects with partially shared interface

本文关键字:对象 界面 共享 用部      更新时间:2023-10-16

我最近遇到了一些我认为可以用不同设计清理的情况,但我不知道任何适合的模式。

在所有这些情况下,我有几个类部分共享一个 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;
// ...
};

第一个解决方案很好,直到我需要开始使用不属于通用接口的功能。 第二种解决方案允许我选择我需要的解决方案,但它容易出错,并且到处都需要丑陋的分支。

我想出了几个替代解决方案,但我还没有找到一个真正感觉正确的解决方案。

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

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

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

使用记录器示例:

//// 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似乎是最正确的,但仍然不是最好的。

有谁知道任何模式或结构可以清理它?

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

下面是一个最小的工作示例:

#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);
}
}