如何为 object 提供不同的接口(最佳)
How to provide different interfaces to an object (optimally)
我需要一种方法来从单个 object.
提供不同的接口
例如。用户一应该可以呼叫 Foo::bar()
,用户 2 应该可以呼叫 Foo::baz()
,但是用户一不能呼叫 Foo::baz()
,用户二不能呼叫 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;
};
有没有更好的方法来实现这个。也许使用包装器 objects。我真的不需要用 new
(std::make_shared
) 分配这个 foo object 我什至不想这样做,但我不能使用原始指针和智能指针来提供不必要的开销和系统调用。
编辑:我会试着举个例子。
有一辆车。用户一是 driver。他可以操纵方向盘、加速或使用刹车。用户二是乘客,例如他可以控制收音机。
我不希望乘客能够使用休息时间或 driver 能够使用收音机。
而且他们都在车里,所以用户一的行为会对用户二产生影响,反之亦然。
您本质上需要的是两个对象之间的共享数据。继承不是一个很好的选择,因为你不仅不需要 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
中实现您的方法。后者是更 "data oriented" 的编程,与我选择演示的更传统的 "object oriented" 相比,现在很受欢迎。
您可以单独申报您的数据
struct Data
{
/* member variables */
};
有一个接口class能够操纵所述数据,所有成员都将受到保护
class Interface
{
protected:
Interface(Data &data) : m_data{data} {}
void bar() { /* implementation */ }
void baz() { /* implementation */ }
Data &m_data;
};
已派生 classed 使 public 个特定成员
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();
}
在我看来这似乎是有效的,因为无论您有多少种用户类型,您都只有一组数据和一个单一的数据操作实现。
我需要一种方法来从单个 object.
提供不同的接口
例如。用户一应该可以呼叫 Foo::bar()
,用户 2 应该可以呼叫 Foo::baz()
,但是用户一不能呼叫 Foo::baz()
,用户二不能呼叫 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;
};
有没有更好的方法来实现这个。也许使用包装器 objects。我真的不需要用 new
(std::make_shared
) 分配这个 foo object 我什至不想这样做,但我不能使用原始指针和智能指针来提供不必要的开销和系统调用。
编辑:我会试着举个例子。
有一辆车。用户一是 driver。他可以操纵方向盘、加速或使用刹车。用户二是乘客,例如他可以控制收音机。
我不希望乘客能够使用休息时间或 driver 能够使用收音机。
而且他们都在车里,所以用户一的行为会对用户二产生影响,反之亦然。
您本质上需要的是两个对象之间的共享数据。继承不是一个很好的选择,因为你不仅不需要 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
中实现您的方法。后者是更 "data oriented" 的编程,与我选择演示的更传统的 "object oriented" 相比,现在很受欢迎。
您可以单独申报您的数据
struct Data
{
/* member variables */
};
有一个接口class能够操纵所述数据,所有成员都将受到保护
class Interface
{
protected:
Interface(Data &data) : m_data{data} {}
void bar() { /* implementation */ }
void baz() { /* implementation */ }
Data &m_data;
};
已派生 classed 使 public 个特定成员
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();
}
在我看来这似乎是有效的,因为无论您有多少种用户类型,您都只有一组数据和一个单一的数据操作实现。