2

当正确使用封装和“告诉,不要问” - 原则时,应该没有理由向对象询问信息。但是,我遇到了一种情况(如果这种设计本身很糟糕,请告诉我),我有一个对象,其成员变量指向类外的函数。

在我的应用程序的某个时刻,我的对象需要调用该函数,然后该函数应根据我的对象的状态进行操作。

这是一个示例类:

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

但这与不使用封装并没有太大区别,而且似乎也不是一个很好的解决方案。解决这个问题的最佳方法是什么?

4

6 回答 6

1

从外部看,私有变量不存在,因此开发人员不可能“想要”打印它们。

如果他们确实想要,那么类成员(或者更好的是,类中返回其内容的查询)应该是公共的,函数是类的成员,或者在特定情况下friend可以使用某种机制。

总而言之,不要着手打破封装——相反,重新考虑封装背后的抽象,如果需要,为你的类的属性创建新的查询,这些查询在设计类时并没有被预见到有用——但现在是.

于 2013-06-05T12:20:20.917 回答
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;
};
于 2013-06-05T12:24:42.547 回答
1

让我们面对现实吧,C++ 访问控制在设计时考虑了一些用例,并且通常可用,但从未声称涵盖所有内容。如果你不能只用私人和朋友解决这种情况,并且必须允许任意函数访问内部,那么最好的方法是将它们公开并继续。

二传手肯定不会让你前进,只会增加复杂性。如果数据是有效的,公众不要试图掩盖这个事实,假装它不是。

寻找根本原因——为什么外面的人想要你的成员并重新安排它。

于 2013-06-05T12:27:34.493 回答
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"
}

您会看到客户端不需要定义额外的函数,而只需“内联”即可。

于 2013-06-05T12:33:43.503 回答
1

你要问的是“我怎样才能让我的成员保密,但仍然给回调一些访问它们的方式?”

当你这样看时,你的struct FooBar解决方案实际上是相当合理的。唯一的问题是它的效率有点低。您最好传递 aconst 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. 这就是我说您可以准确指定回调应该访问哪些成员时的意思。

于 2013-06-05T12:36:24.690 回答
0

我可以忽略封装,为 name 和将来该函数可能需要的所有其他属性创建一个 setter 和 getter。这是一个非常糟糕的解决方案,我基本上应该为我的类中的所有内容创建 setter 和 getter,因为该函数可以对我的对象做任何事情。

是的 - 这基本上是公开实现细节(在大多数情况下,这不是你应该做的事情)。

另一种解决方案是在其中包含所需属性的结构:

[...] 但这与不使用封装没有太大区别,而且它似乎也不是一个很好的解决方案。解决这个问题的最佳方法是什么?

实际上它是非常不同的。考虑一下您实际上是在使用普通参数调用外部函数:

struct EventData { string name, yadayada; }

class Foo
{
public:
    void activate()
    {
        m_function( EventData(m_name, yadayada) );
    }
};

这不是访问私有数据(Foo 访问它自己的私有数据,m_function 访问它自己的参数值),而是依赖注入。

这种方法没有架构妥协。

于 2013-06-05T12:39:17.343 回答