2013-06-05 51 views
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); 
} 

但这并不是由于没有使用封装太大的不同,它似乎并不像一个太好的解决办法。 这个问题最好的办法是什么?

+0

请参阅[这个老问题](http://stackoverflow.com/questions/1568091/why-use-getters-and-setters)。 –

+1

@JoachimPilborg我认为他正试图*避免* getters和setter,而不是试图找到使用它们的理由。 – 2013-06-05 12:27:43

+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; 
}; 
+0

我可能有多达20种不同类型的'Foo',定义20个函数似乎更合理,而不是继承20个类... –

+0

为什么downvote? – 2013-06-05 12:39:07

+0

编辑中的这种设计是完全不好的,因为外部世界应该能够添加新功能而不触及课程内部。完全违背开放/封闭的原则。 –

1

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

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

总之,没有要打破封装 - 相反,重新考虑你的封装背后的抽象和,如果需要的话,对于未曾预料有用回来时,你的类的属性创建新查询这个班是设计的 - 但现在是。

1

让我们面对它,C++访问控制的设计采用了一些使用情况下考虑的,一般可用,但从来没有声称自己面面俱到。如果你不能只靠私人和朋友解决问题,必须允许任意功能访问内部,那么最好的办法就是让它们公开并继续前进。

安装人员不会让您前进,肯定会增加复杂性。如果数据是有效的,那么不要试图掩盖事实,假装它不是。

寻找根本原因 - 为什么外面的人希望你的成员和重新排列。

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" 
} 

你看到客户端并不需要定义一个额外的功能,但可以简单地做“内联” 。

+0

为什么downvote? –

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。当我说你可以指定回调应该访问哪些成员时,这就是我的意思。

0

我可以忽略封装并为名称和函数将来可能需要的所有其他属性创建setter和getter。这是非常糟糕的解决方案,我应该为我的课程中的所有内容创建setter和getter,因为函数可以对我的对象执行任何操作。

确实 - 这基本上是公开实现细节(并且在大多数情况下,不应该这样做)。

的其他解决办法是持有它里面所需要的性能的结构:

[...]但是,这是不是从没有使用封装太大的不同,它似乎并不像一个太好的解决方案。这个问题最好的办法是什么?

其实它是非常不同的。想想看,你实际上是调用外部功能与正常参数:

struct EventData { string name, yadayada; } 

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

这不是访问私人数据(美孚访问它自己的私人数据,m_function访问它自己的参数值),但依赖注入。

这种方法没有体系结构的妥协。