当使用封装和“告诉,不要问” -principle时,没有理由要求对象提供信息。 但是,我遇到了一种情况(让我知道这个设计本身是否很糟糕),我有一个对象,其成员变量指向类外的函数。
在我的应用程序的某个时刻,我的对象需要调用该函数,然后该函数应该根据我的对象的状态进行操作。
这是一个示例类:
typedef void(*fptr)(Foo*);
class Foo {
public:
Foo(string name, fptr function);
void activate()
{
m_function(this);
}
private:
string m_name;
fptr m_function;
};
这是类,现在开发人员可以像这样使用类;
void print(Foo *sender)
{
cout << "Print works!" << endl;
}
int main(int argc, char **argv)
{
Foo foo("My foo", &print);
foo.activate();
// output: "Print works!"
}
这一切都运行正常,但如果我想打印发件人的姓名怎么办?
所有函数都是在类之外由其他开发人员定义的,因此无法访问私有变量。
在C#
中,您只需使用partial
关键字向现有类添加方法即可。
这在C++
中是不可能的。
我可以忽略封装并为name
和将来可能需要的所有其他属性创建一个setter和getter。
这是非常可怕的解决方案,我应该基本上为我的课程中的所有内容创建setter和getter,因为该函数可以对我的对象做任何事情。
除了封装的原因之外,如果我想在我想要的时候忽略它呢?
另一个解决方案是在其中包含所需属性的结构:
struct FooBar {
string name;
};
typedef void(*fptr)(FooBar);
void Foo::activate()
{
FooBar fb;
fb.name = m_name;
m_function(fb);
}
但这与不使用封装没有太大区别,它似乎也不是一个太好的解决方案。 这个问题的最佳方法是什么?
答案 0 :(得分:1)
从外部看,私有变量不存在,因此开发人员不可能“想要”打印它们。
如果他们确实需要,那么类成员(或者更好,类中的查询返回其内容)应该是公共的,函数是类的成员,或者在特定情况下可以使用某些friend
机制
总结一下,不打算打破封装 - 而是重新考虑封装背后的抽象,如果需要,还会为您的类的属性创建新查询在课程设计时没有预见到有用的东西 - 现在却是。
答案 1 :(得分:1)
我会使activate()
成为一个抽象方法,并保护所有类的属性。
此外,不需要fptr
:
class Foo {
public:
Foo(string name);
virtual void activate() = 0;
protected:
string m_name;
};
现在当有人想要使用你的课程时,他只是从中继承了自己的课程:
class MyFoo : public Foo {
public:
MyFoo(string name);
virtual void activate()
{
cout << m_name << " says: Hello World!" << endl;
}
};
int main(int argc, char **argv)
{
MyFoo foo("My foo");
foo.activate();
// output: "My Foo says: Hello World!"
}
如果你需要许多不同功能的Foo
,只需继承多个类而不是声明多个函数。
编辑:您可以使用所有不同的方法为所有这些实例继承一个类,而不是为每个不同的Foo
实例继承一个新类。
现在剩下的所有激活都是决定调用哪种方法;使用enum
:
enum MyFooFunction {
printName,
printHello
};
class MyFoo : public Foo {
public:
MyFoo(string name, MyFooFunction function);
void printName() { cout << m_name << endl; }
void printHello() { cout << "Hello!" << endl; }
virtual void activate()
{
switch(m_function) {
case printName:
printName();
break;
case printHello:
printHello();
break;
}
}
protected:
MyFooFunction m_function;
};
答案 2 :(得分:1)
让我们面对现实,C ++访问控制的设计考虑了一些用例,并且通常可用,但从未声称涵盖所有内容。如果你不能用私人和朋友来解决这种情况,并且必须允许任意函数访问内部,那么最好的办法就是让它们公开并继续前进。
Setters肯定不会让你前进,只会增加复杂性。如果数据是有效的,公众不要试图掩盖这样的事实,假装它不是。
寻找根本原因 - 为什么外面的人想要你的成员并重新安排它。
答案 3 :(得分:1)
如果函数应该能够看到字符串,您可能希望将函数参数类型更改为const string &
,但外部世界的其他人不会看到它。您也可以考虑使用std::function<void(const string &)>
而不是函数类型。这有两个基本优点:您可以将闭包(也称为lambdas)传递给构造函数,您可以更轻松地读取它。编辑后的代码如下所示:
class Foo {
public:
template <typename F>
Foo(string name, F && function)
: m_name (std::move(name))
, m_function(std::forward<F>(function))
{
}
void activate()
{
m_function(m_name);
}
private:
string m_name;
std::function<void(const string &)> m_function;
};
客户端代码看起来像
int main(int argc, char **argv)
{
Foo foo("My foo", [](const string & s){ cout << s << endl; });
foo.activate();
// output: "My foo"
}
您看到客户端不需要定义额外的功能,但可以简单地“内联”。
答案 4 :(得分:1)
你所问的是“我如何保持我的会员私密,但仍然以某种方式回访他们?”
当你这样看时,你的struct FooBar
解决方案实际上是非常合理的。唯一的问题是它有点低效。您最好通过const FooBar&
而不是按值传递FooBar
。
您的struct FooBar
解决方案甚至比部分类更好,因为您可以准确指定回调应该有权访问哪些成员。
编辑:更仔细地阅读struct FooBar
解决方案,我发现您在将这些成员传递给回调之前,正在考虑单独复制这些成员。您可以通过在FooBar
类中放置Foo
对象来跳过所有这些,如下所示:
struct FooBar {
string name;
};
typedef void(*fptr)(const FooBar&);
class Foo {
public:
Foo(string name, fptr function);
void activate()
{
m_function(data);
}
private:
FooBar data;
fptr m_function;
};
值得指出的是,使用此解决方案,回调无法访问m_function,除非您决定将其放入FooBar
。这就是我的意思,当我说你可以确切地指定回调应该有权访问哪些成员时。
答案 5 :(得分:0)
我可以忽略封装并为名称和将来可能需要的所有其他属性创建一个setter和getter。这是非常可怕的解决方案,我基本上应该为我班上的所有内容创建setter和getter,因为该函数可以对我的对象做任何事情。
是的 - 这基本上是将实现细节公开(在大多数情况下,不是你应该做的事情)。
另一个解决方案是在其中包含所需属性的结构:
[...]但这与不使用封装没有太大区别,它似乎也不是一个太好的解决方案。这个问题的最佳方法是什么?
实际上它是非常不同的。考虑到您实际上正在使用普通参数调用外部函数:
struct EventData { string name, yadayada; }
class Foo
{
public:
void activate()
{
m_function( EventData(m_name, yadayada) );
}
};
这不是访问私有数据(Foo访问它自己的私有数据,m_function访问它自己的参数值),但依赖注入。
这种方法没有架构妥协。