簡體   English   中英

代理對象/參考getter vs setter?

[英]Proxy object/reference getters vs setters?

當我設計一個通用類時,我經常處於以下設計選擇之間的兩難境地:

template<class T>
class ClassWithSetter {
 public:
  T x() const; // getter/accessor for x
  void set_x(const T& x);
  ...
};
// vs
template<class T>
class ClassWithProxy {
  struct Proxy {
    Proxy(ClassWithProxy& c /*, (more args) */);
    Proxy& operator=(const T& x);  // allow conversion from T
    operator T() const;  // allow conversion to T
    // we disallow taking the address of the reference/proxy (see reasons below)
    T* operator&() = delete;
    T* operator&() const = delete;
    // more operators to delegate to T?
   private:
    ClassWithProxy& c_;
  };
 public:
  T x() const; // getter
  Proxy x();  // this is a generalization of: T& x();
  // no setter, since x() returns a reference through which x can be changed
  ...
}; 

筆記:

  • 之所以我在x()operator T()返回T而不是const T& ,是因為如果x只是隱式存儲,則可能無法從類中獲得對x的引用(例如假設T = std::set<int>但是類型T x_存儲為std::vector<int>
  • 代理的假設高速緩存對象和/或x不允許

我想知道什么是一些情況,其中一個人更喜歡一種方法而不是另一種方式,尤其是。 就......而言:

  • 可擴展性/普遍性
  • 效率
  • 開發人員的努力
  • 用戶的努力

您可以假設編譯器足夠智能以應用NRVO並完全內聯所有方法。

目前的個人觀察:

(這部分與回答問題無關;它只是作為一種動機,並說明有時一種方法比另一種更好。)

設定方法存在問題的一個特定情況如下。 假設您正在實現具有以下語義的容器類:

  • MyContainer<T>& (可變,讀寫) - 允許修改容器及其數據實現
  • MyContainer<const T>& (可變,只讀) - 允許修改容器但不修改其數據
  • const MyContainer<T> (不可變,讀寫) - 允許修改數據而不是容器
  • const MyContainer<const T> (不可變,只讀) - 不修改容器/數據

其中“容器修改”是指添加/刪除元素等操作。 如果我用setter方法實現這個天真:

template<class T>
class MyContainer {
 public:
   void set(const T& value, size_t index) const {  // allow on const MyContainer&
     v_[index] = value;  // ooops,
     // what if the container is read-only (i.e., MyContainer<const T>)?
   }
   void add(const T& value);  // disallow on const MyContainer&
   ...
 private:
  mutable std::vector<T> v_;
};

通過引入大量依賴於SFINAE的樣板代碼可以緩解這個問題(例如,通過從實現兩個版本的set()的專用模板助手派生)。 然而,更大的問題是,這會制動通用接口 ,因為我們需要:

  • 確保在只讀容器上調用set()是編譯錯誤
  • 為只讀容器的set()方法提供不同的語義

另一方面,雖然基於代理的方法工作得很好:

template<class T>
class MyContainer {
   typedef T& Proxy;
 public:
   Proxy get(const T& value, size_t index) const {  // allow on const MyContainer&
     return v_[index];  // here we don't even need a const_cast, thanks to overloading
   }
   ...
};

並且通用接口和語義不會被破壞。

我在代理方法中遇到的一個困難是支持Proxy::operator&()因為可能沒有存儲類型T對象/可用引用(參見上面的注釋)。 例如,考慮:

T* ptr = &x();

除非x_實際存儲在某個地方(在類本身中或通過在成員變量上調用的(鏈)方法可訪問),否則無法支持,例如:

template<class T>
T& ClassWithProxy::Proxy::operator&() {
  return &c_.get_ref_to_x();
}

這是否意味着當T&可用時(即顯式存儲x_ ),代理對象引用實際上是優越的,因為它允許:

  • 批處理/延遲更新(例如,想象更改是從代理類析構函數傳播的)
  • 更好地控制緩存?

(在這種情況下,兩難就是在void set_x(const T& value)T& x() 。)

編輯:我更改了setter / accessors的constpos中的拼寫錯誤

像大多數設計困境一樣,我認為這取決於具體情況。 總的來說,我更喜歡getter和setter模式,因為它更簡單的代碼(不需要每個字段的代理類),更容易被另一個人理解(查看你的代碼),在某些情況下更明確。 但是,在某些情況下,代理類可以簡化用戶體驗並隱藏實現細節。 幾個例子:

如果您的容器是某種關聯數組,則可能會重載operator []以獲取和設置特定鍵的值。 但是,如果尚未定義某個鍵,則可能需要執行特殊操作才能添加該鍵。 這里的代理類可能是最方便的解決方案,因為它可以根據需要以不同的方式處理=賦值。 但是,這可能誤導用戶:如果此特定數據結構具有不同的添加時間與設置,則使用代理會使這很難看到,而使用set和put方法設置可以清楚地表明每個操作使用的單獨時間。

如果容器在T上進行某種壓縮並存儲壓縮形式怎么辦? 雖然您可以使用在必要時執行壓縮/解壓縮的代理,但它會隱藏與用戶進行壓縮/解壓縮相關的成本,並且他們可能會使用它,就像它是一個簡單的分配而沒有繁重的計算。 通過使用適當的名稱創建getter / setter方法,可以更明顯地表明它們需要大量的計算工作。

吸氣劑和制定者似乎也更具可擴展性。 為新字段制作getter和setter很容易,而制作代理可以轉發每個屬性的操作將是一個容易出錯的煩惱。 如果您以后需要擴展容器類怎么辦? 使用getter和setter,只需將它們設置為虛擬並在子類中覆蓋它們。 對於代理,您可能必須在每個子類中創建一個新的代理結構。 為了避免破壞封裝,你可能應該讓你的代理結構使用超類的代理結構來完成一些工作,這可能會讓人很困惑。 使用getter / setter,只需調用super getter / setter即可。

總的來說,getter和setter更容易編程,理解和更改,並且可以顯示與操作相關的成本。 所以,在大多數情況下,我更喜歡它們。

我認為你的ClassWithProxy接口是混合包裝器/代理和容器。 對於容器,通常使用類似的訪問器

T& x();
const T& x() const;

就像標准容器一樣,例如std::vector::at() 但通常通過引用訪問成員會破壞封裝。 對於容器來說,它是一種便利,也是設計的一部分。

但是你注意到對T的引用並不總是可用,所以這將減少ClassWithSetter接口的選項,這應該是T處理存儲類型的方式的包裝 (當容器處理存儲對象的方式時) )。 我會改變命名,說清楚,它可能不如普通的get / set有效。

T load() const;
void save(const T&);

或者更多的上下文。 現在應該很明顯,通過使用代理修改T ,再次打破封裝。

順便說一句,沒有理由不在容器內部使用包裝器。

我認為你的set實現可能的一部分問題是你對const MyContainer<T>&表現如何的想法與標准容器的行為方式不一致,因此可能會混淆未來的代碼維護者。 “常量容器,可變元素”的常規容器類型是const MyContainer<T*>& ,您可以在其中添加間接級別以清楚地向用戶表明您的意圖。

這就是標准容器的工作方式,如果使用該機制,則不需要底層容器是可變的,也不需要將set函數設置為const

所有這些說我稍微喜歡set / get方法,因為如果一個特定的屬性只需要一個get你根本不需要寫一個set

但是我不想寫任何直接訪問成員(如get / set或proxy),而是提供一個有意義的命名接口,客戶端可以通過該接口訪問類功能。 在一個簡單的例子中顯示我的意思,而不是set_foo(1); set_bar(2); generate_report(); set_foo(1); set_bar(2); generate_report(); 喜歡generate_report(1, 2);這樣的直接接口generate_report(1, 2); 並避免直接操縱類屬性。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM