(译注:我想在大多数情况下我会选择直接把Bar声明成Foo的友元。第二个例子倒是挺有意义的。)
目的
控制对一个类的实现细节的访问颗粒度。
动机
C++中的友元声明赋予被声明者对一个类的内部的完全访问权限。友元声明因此遭到指责 - 他们破坏了精心打造的封装。C++的友元关系特性没有提供任何可以选择性的对私有成员的一个子集授予权限的方法。友元关系在C++中是一个“所有或者一个也不”的命题。例如,下面的类Foo声明类Bar为其友元。类Bar因此拥有访问类Foo中所有私有成员的权限。这可能不是所期望的,因为它增加了耦合性 - 类Bar不能在没有类Foo的情况下被分发。
class Foo { private: void A(int a); void B(float b); void C(double c); friend class Bar; }; class Bar { // This class needs access to Foo::A and Foo::B only. // C++ friendship rules, however, give access to all the private members of Foo. };
提供对成员一个子集的可选择的访问性是理想之选,因为其他的(私有)成员可以在有需要的情况下改变接口。
解决方案与示例代码
代理-客户(Attorney-Client)惯用法通过增加一层间接层而工作。一个客户类希望控制对自己内部细节的访问,它指派了一个代理并将其声明为朋友 - 一个C++友元!代理类被精心打造为客户的一个代理。不像典型的代理类,Attorney代理类只复制客户的私有接口的一个子集。例如,考虑到类Foo希望控制对自己实现细节的访问。 为了使情况更加明了,我们将其改名为Client。Client希望他的Attorney代理只提供对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 now has access to only Client::A and Client::B through the Attorney. };
类Attorney限制对一组内聚的函数的访问。它的所有成员函数都是静态内联函数,每个函数接受一个Client类的引用,并将函数调用转发给Client。Attorney类有一些惯用的技巧。它的函数实现完全是私有的,防止了期望之外的其他类获得对Client类内部的访问权限。Attorney类决定哪些其他类,成员函数或者普通函数(free funtion)可以访问Client。它将它们声明为友元以让其访问自己的实现并最终访问Client。如果没有Attorney类,Client类需给多少外部对象对自己内部不受限制的权限,就需要声明多少友元。
可以通过多个代理类为客户实现细节的不同集合提供内聚的访问。例如AttorneyC代理类只提供对Client::C的访问。当一个代理类作为多个不同类的中间人出现,并提供对他们实现细节内聚的访问时,会有一些有趣的例子。在有继承结构存在时,因为友元关系在C++里是不可继承的,很容易联想到这样的设计。如果基类中的私有虚拟函数可以被访问,派生类中的私有虚函数也将可以被调用。因此不再需要将友元关系扩展到派生类中。在下面的例子中,代理-客户惯用法被应用到Base类和main函数。Derived::Fun函数通过多态被调用到。要访问Derived类的实现细节,仍然可以对其应用同样的惯用法。
#include <cstdio> class Base { private: virtual void Func(int x) = 0; friend class Attorney; public: ~Base() {} }; class Derived : public Base { private: virtual void Func(int x) { printf("Derived::Func\n"); // This is called even though main is not a friend of Derived. } public: virtual ~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); }
已知应用
相关惯用法
参考资料
Friendship and the Attorney-Client Idiom (Dr. Dobb's Journal)
原文链接
http://en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Friendship_and_the_Attorney-Client
原文链接: https://www.cnblogs.com/shawnhue/archive/2011/12/14/Friendship_and_the_Attorney-Client.html
欢迎关注
微信关注下方公众号,第一时间获取干货硬货;公众号内回复【pdf】免费获取数百本计算机经典书籍
原创文章受到原创版权保护。转载请注明出处:https://www.ccppcoding.com/archives/38559
非原创文章文中已经注明原地址,如有侵权,联系删除
关注公众号【高性能架构探索】,第一时间获取最新文章
转载文章受原作者版权保护。转载请注明原作者出处!