我需要一种方法来从单个对象提供不同的接口。
例如。用户 1 应该能够呼叫Foo::bar()
用户 2 应该能够呼叫Foo::baz()
但用户 1 不能呼叫Foo::baz()
用户 2 分别不能呼叫Foo::bar()
。
我确实设法做到了这一点,但我认为这不是最佳的。
class A
{
public:
virtual void bar() = 0;
virtual ~A() = 0;
};
class B
{
public:
virtual void baz() = 0;
virtual ~B() = 0;
};
class Foo : public A, public B
{
public:
Foo() = default;
void baz() override;
void bar() override;
};
class Factory
{
public:
Factory()
{
foo = std::make_shared<Foo>();
}
std::shared_ptr<A> getUserOne()
{
return foo;
}
std::shared_ptr<B> getUserTwo()
{
return foo;
}
private:
std::shared_ptr<Foo> foo;
};
有没有更好的方法来实现这一目标。也许带有包装器对象。我真的不需要用new
(std::make_shared
)分配这个foo对象,我什至不喜欢,但我不能使用原始指针,智能指针会提供不必要的开销和系统调用。
编辑:我会尝试举一个例子。
有一辆车。用户 1 是驱动程序。他可以操纵方向盘,加速或使用休息时间。用户二是乘客,例如,他可以控制收音机。 我不希望乘客能够使用休息时间或司机能够使用收音机。
此外,它们都在车内,因此用户一的动作将对用户二产生影响,反之亦然。
您基本上需要的是两个对象之间的共享数据。继承不是一个很好的选择,因为您不仅不需要is A
关系,而且您明确希望避免它。因此,组合是您的答案,特别是因为您有工厂:
class Data
{
public:
void bar();
void baz();
};
然后,您将使用组合而不是继承:
class A
{
public:
A(Base *base) : mBase(base) {}
void bar() { mBase->bar(); }
private:
Base *mBase = nullptr;
};
//class B would be the same only doing baz()
最后Factory
:
class Factory
{
public:
A *getUserOne() { return &mA; }
B *getUserTwo() { return &mB; }
private:
Base mBase;
A mA(&mBase);
B mB(&mBase);
};
关于此解决方案的几点。虽然它不会在堆上分配,但只要有用户,就需要保持Factory
活动状态。出于这个原因,在OP中使用std::shared_ptr
可能是一个聪明的主意。:-)但当然,原子参考计数的成本也随之而来。
其次,A
与B
没有任何关系。这是设计使然,与原始解决方案不同,不允许A
和B
之间的dynamic_cast
。
最后,实施地点取决于您。你可以把它全部放在Data
,A
,B
只是调用它(如上所示),但你也可以Data
成为一个只保存你的数据的struct
,并分别在A
和B
中实现你的方法。后者是更"面向数据"的编程,如今非常流行,而不是我选择演示的更传统的"面向对象"。
您可以单独声明数据
struct Data
{
/* member variables */
};
有一个能够操纵所述数据的接口类将保护所有成员
class Interface
{
protected:
Interface(Data &data) : m_data{data} {}
void bar() { /* implementation */ }
void baz() { /* implementation */ }
Data &m_data;
};
具有派生的分类,使公共特定成员
class A : private Interface
{
public:
A(Data &data) : Interface{data} {}
using Interface::bar;
};
class B : private Interface
{
public:
B(Data &data) : Interface{data} {}
using Interface::baz;
};
这样,您还可以让用户能够重叠访问某些功能,而无需多次实现它。
class Admin : private Interface
{
public:
Admin(Data &data) : Interface{data} {}
using Interface::bar;
using Interface::baz;
};
当然,根据您使用数据的方式,您可能需要一个指针或共享指针,可能会在来自多个线程的访问之间添加一些同步。
使用此模型的示例代码:
void test()
{
Data d{};
auto a = A{d};
a.bar();
// a.baz is protected so illegal to call here
auto b = B{d};
b.baz();
// b.bar is protected so illegal to call here
auto admin = Admin{d};
admin.bar();
admin.baz();
}
在我看来,这似乎很有效,因为无论您有多少用户类型,您都只有一组数据和单个数据操作实现。