2013-06-05 48 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訪問它自己的參數值),但依賴注入。

這種方法沒有體系結構的妥協。