C++ 对象需要让函数访问其所有数据
当正确使用封装和“告诉,不要问”原则时,应该没有理由向对象询问信息。 然而,我遇到了这样一种情况(让我知道这个设计本身是否糟糕),我有一个对象,其中一个成员变量指向类外的函数 在我的应用程序的某个点上,我的对象需要调用函数,然后函数应该根据对象的状态进行操作 下面是一个示例类:C++ 对象需要让函数访问其所有数据,c++,design-patterns,encapsulation,C++,Design Patterns,Encapsulation,当正确使用封装和“告诉,不要问”原则时,应该没有理由向对象询问信息。 然而,我遇到了这样一种情况(让我知道这个设计本身是否糟糕),我有一个对象,其中一个成员变量指向类外的函数 在我的应用程序的某个点上,我的对象需要调用函数,然后函数应该根据对象的状态进行操作 下面是一个示例类: typedef void(*fptr)(Foo*); class Foo { public: Foo(string name, fptr function); void activ
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!"
}
但这与不使用封装并没有太大区别,而且它似乎也不是一个太好的解决方案。
解决这个问题的最佳方法是什么?从外部看,私有变量不存在,因此开发人员不可能“想要”打印它们 如果他们确实想要,那么类成员(或者更好,类中返回其内容的查询)应该是公共的,函数应该是类的成员,或者在特定情况下可以使用一些
朋友
机制
总之,不要开始破坏封装——相反,重新考虑封装背后的抽象,如果需要,为类的属性创建新的查询,这些属性在设计类的时候是没有用的,但现在是。我会让
激活()
一个抽象方法和所有类的属性都受到保护。
此外,不需要使用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!"
}
让我们面对它,C++访问控制是在考虑到一些用例的情况下设计的,并且通常是可用的,但从来没有声明要覆盖所有的东西。如果您不能仅使用private和friend来解决这种情况,并且必须允许任意函数访问内部,那么最好的方法是将它们公开并继续
二传手肯定不会推动你前进,只是徒劳地增加复杂性。如果数据是有效的,不要假装它不是,试图掩盖事实寻找根本原因——为什么外部需要您的成员并重新安排。如果函数应该能够看到字符串,那么您可能希望将函数参数类型更改为
常量字符串&
,但外部世界的其他人将看不到它。您也可以考虑使用<代码> STD::函数< /C> >代替您的函数类型。这有两个基本的优点:可以将闭包(也称为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;
};
class-Foo{
公众:
模板
Foo(字符串名称、F&函数)
:m_名称(标准::移动(名称))
,m_函数(标准::前进(函数))
{
}
无效激活()
{
m_函数(m_名称);
}
私人:
字符串m_name;
std::函数m_函数;
};
客户端代码如下所示
int main(int argc, char **argv)
{
Foo foo("My foo", [](const string & s){ cout << s << endl; });
foo.activate();
// output: "My foo"
}
int main(int argc,char**argv)
{
Foo-Foo(“我的Foo”,[](const-string&s){cout您要问的是“我如何保持我的成员的隐私,但仍然以某种方式访问它们?”
从这个角度来看,您的struct FooBar
解决方案实际上相当合理。唯一的问题是它有点效率低下。您最好传递const FooBar&
而不是按值传递FooBar
您的struct FooBar
解决方案甚至比分部类更好,因为您可以精确地指定回调应该访问哪些成员
编辑:更仔细地阅读您的struct FooBar
解决方案,我发现您正在考虑在将成员传递到回调之前逐个复制成员。您可以通过在Foo
类中放置FooBar
对象跳过所有这些,如下所示:
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_函数,除非您决定将其放入FooBar
。这就是我所说的,您可以指定回调应该访问哪些成员
我可以忽略封装,为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);
}
[…]但这与不使用封装没有太大区别,而且它似乎也不是一个太好的解决方案。解决这个问题的最佳方法是什么
实际上,它是非常不同的。考虑一下,你实际上调用了一个具有正常参数的外部函数:
struct EventData { string name, yadayada; }
class Foo
{
public:
void activate()
{
m_function( EventData(m_name, yadayada) );
}
};
这不是访问私有数据(Foo访问它自己的私有数据,m_函数访问它自己的参数值),而是依赖注入
这种方法没有架构上的折衷。请参见。@JoachimPilborg我认为他试图避免使用getter和setter,而不是试图找到使用它们的理由。如果您只需要观察数据,那么使用getter和no setter似乎是最好的解决方案。您希望数据是公共的或私有的。能够访问任何人创建的函数中的数据都会使数据有效地公开。@JurajBlaho回调是在非常特定的情况下调用的,因此使成员只对回调可用是一种有用的访问控制形式。如果你试图滥用回调来“使数据有效地公开”,你就是在挖苦你
struct EventData { string name, yadayada; }
class Foo
{
public:
void activate()
{
m_function( EventData(m_name, yadayada) );
}
};