[OOD-More C++ Idioms] 律師與委託人 (Attorney-Client)

Horky發表於2015-10-17

律師與委託人 (Attorney-Client)

目的

控制訪問類實現細節的粒度。

動機

C++中的friend會開始類內部的所有細節,也因此破壞了封裝性。C++沒有提供可以選擇性使用某一部分私有成員的方式,要麼全部開放,要麼全部拒絕。例如下面例子中的類Foo宣告Bar為其友元,可以訪問它的所有私有成員。這樣增加了耦合性,類Bar也無法單獨釋出,這樣並不太合適。

class Foo
{
private:
  void A(int a);
  void B(float b);
  void C(double c);
  friend class Bar;
};

class Bar {
// 這個類只需要使用Foo::A和Foo::B.
// 但它其實可以訪問Foo所有的成員.
};

如果能選擇確定需要使用到的一組成員,而不是全部,就可以降低耦合性。而這個律師與委託人的慣用法就可以精準的控制友元所能使用的成員。

解決方案及示例

基本思路是增加一箇中間層進行控制。一個客戶類可以指定一個律師(Attorney)類作為其友元類,再由此律師類擔當其它類使用客戶類的代理。與典型的代理(proxy)類不同的是,律師類會限制一個可以使用的成員子集。比如上面的例子中,將Foo改為Client, 再提供一箇中間類限制只允許訪問Client::A和Client::B。程式碼如下:

class Client
{
private:
  void A(int a);
  void B(float b);
  void C(double c);
  friend class Attorney;  // 這裡指定友元類
};

class Attorney {
private:
  static void callA(Client & c, int a) {
    c.A(a);
  }
  static void callB(Client & c, float b) {
    c.B(b);
  }
  friend class Bar;
};

class Bar {
// 現在Bar通過Attorney類就只能使用Client::A和Client::B了。
};

類圖如下:

general

Attorney類只有私有的inline static函式,每個都持有一個Client例項的引用,再轉而呼叫合適的方法。僅保持私有實現,可以避免被其它類使用。所有需要使用Client都要在Attorney中宣告為友元類。如果沒有Attorney類,就需要直接修改Client類。

另外還可以使用多個律師(Attorney)類分離出對不同私有實現的訪問。

還有一個有趣的案例是提供一個律師(Attorney)類作為多個類的中間人(mediator),來統一提供對它們私有實現的訪問。這個設計可以用於解決C++中無法繼承友元類所帶來的問題,因為私有實現的虛擬函式是可以被基類呼叫的。 下面這個例子裡,Derived::Func是一個多型實現。通過這個慣用法同樣可以使用到Derived中的私有實現。

#include <cstdio>

class Base {
private:
  virtual void Func(int x) = 0;
  friend class Attorney;
public:
  virtual ~Base() {}
};

class Derived : public Base {
private:
  virtual void Func(int x)  {
    printf("Derived::Func\n"); // 雖然沒有繼承基類中的友元關係,但仍然可以被訪問。
  }

public:
  ~Derived() {}
};

class Attorney {
private:
  static void callFunc(Base & b, int x) {
    return b.Func(x);
  }
  friend int main (void);
};

int main(void) {
  Derived d;
  Attorney::callFunc(d, 10);
}

類圖如下:

special case

已知的應用

參考

相關文章