• 读书笔记_Effective_C++_条款二十三:宁以nonmember、nonfriend替换member函数


    本条款还是讨论封装的问题,举书上的例子:

    1 class WebBrower
    2 {
    3 public:
    4     void ClearCach();
    5     void ClearHistory();
    6     void RemoveCookies();
    7 };

    定义了一个WebBrower的类,里面执行对浏览器的清理工作,包括清空缓存,清除历史记录和清除Cookies,现在需要将这三个函数打包成一个函数,这个函数执行所有的清理工作,那是将这个清理函数放在类内呢,还是把他放在类外呢?

    如果放在类内,那就像这样:

     1 class WebBrower
     2 {
     3  4     void ClearEverything()
     5     {
     6         ClearCach();
     7         ClearHistory();
     8         RemoveCookies();
     9     }
    10 11 };

    如果放在类外,那就像这样:

    1 void ClearWebBrowser(WebBrower& w)
    2 {
    3     w.ClearCach();
    4     w.ClearHistory();
    5     w.RemoveCookies();
    6 }

    根据面向对象守则的要求,数据以及操作数据的函数应该捆绑在一起,都放在类中,这意味着把它放在类内会比较好。但从封装性的角度而言,它却放在类外好,为什么?

    为了区分开,我们把在类内的总清除函数称之为ClearEverything,而把类外的总清除函数称之为ClearWebBrower。ClearEverything对封装性的冲击更大,因为它位于类内,这意味着它除了访问这三个公有函数外,还可以访问到类内的私有成员,是的,你也许会说现在这个函数里面只有三句话,但随着功能的扩充,随着程序员的更替,这个函数的内容很可能会逐渐“丰富”起来。而越“丰富”说明封装性就会越差,一旦功能发生变更,改动的地方就会很大。

    再回过头来看看类外的实现,在ClearWebBrowser()里面,是通过传入WebBrower的形参对象来实现对类内公有函数的访问的,在这个函数里面是绝对不会访问到私有成员变量(编译器会为你严格把关)。因此,ClearWebBrowser的封装性优于类内的ClearEverything。

    但这里有个地方需要注意,ClearWebBrower要是类的非友元函数,上面的叙述才有意义,因为类的友元函数与类内成员函数对封装性的冲击程度是相当的。

    看到这里,你也许会争辩,把这个总清除的功能函数放在类外,就会割离与类内的关联,逻辑上看,这个函数就是类内函数的组合,放在类外会降低类的内聚性。

    为此,书上提供了命名空间的解决方案,事实上,这个方案也是C++标准程序库的组织方式,好好学习这种用法很有必要!像这样:

    1 namespace WebBrowserStuff
    2 {
    3 4     class WebBrowser();
    5     void ClearWebBrowser(WebBrowser& w);
    6 7 }

    namespace与class不同,前者可以跨越多个源码文件,而后者不能。通过命名空间的捆绑,是在封装和内聚之间非常好的平衡。

    更有意思的是,与WebBrower相关的其他功能,比如书签、cookie管理等等,他们与WebBrower紧密相关,但放在单看书签和cookie,两者又是逻辑不同的,喜欢书签管理的用户不一定喜欢cookie管理,这时如果把他们统统放在类内,会显得有些不妥。书上提供了很好的命名空间组织方式,像这样:

     1 // 头文件webbrowser.h
     2 namespace WebBrowserStuff
     3 {
     4     class WebBrowser(); // 核心功能
     5     void ClearWebBrowser(WebBrowser& w); // non-member non-friend函数
     6 }
     7 
     8 // 头文件webbrowserbookmarks.h
     9 namespace WebBrowserStufff
    10 {
    11// 与书签相关的函数
    12 }
    13 
    14 // 头文件 webbrowsercookies.h
    15 namespace WebBrowserStuff
    16 {
    17// 与cookie管理相关的函数
    18 }

    将他们放在不同的头文件中,但位于同一个namespace中,是内聚与封装平衡的极佳处理方式。

    C++标准库中,容器是std命名空间的重要组成部分,但容器也有好多种,比如vector,set和map等等,如果把它们写在一个头文件中,会很臃肿。C++把它们放在不同的头文件中,但又位于同一个命名空间里,方便用户使用,比如用户只想用vector,那么只要包含#include <vector>就行了,而不必#include <set>,并且一句using namespace std即可不加前缀地使用其内定义的各种名称。

    最后总结一下,本条款强调封装性优先于类的内聚逻辑,这是因为“愈多东西被封装,愈少人可以看到它,而愈少人看到它,我们就有愈大的弹性去改变它,因为我们的改变仅仅影响看到改变的那些人或事物”。采用namespace可以对内聚性进行良好的折中。

    一句话:“宁可拿non-member non-friend函数替换member函数,这样可以增加封装性、包裹弹性和机能扩充性”。

  • 相关阅读:
    WPF程序设计 :第四章 按钮与其他控件(Buttons and Other Controls)
    C#参考 : 枚举类型
    C#3.0 新特性学习笔记(3):匿名类型
    F#语言2008年9月CTP版已经更新
    C#3.0 新特性学习笔记(1): 对象集合初始化器
    WPF程序设计基础:属性系统
    C#3.0 新特性学习笔记(2):var 隐式声明变量
    MSSql行列转换的Sql语法 详解与实例
    WPF程序设计 :第一章 应用程序和窗口(The Application and the Window)
    WPF程序设计 :第二章 基本画刷(Basic Brushes)
  • 原文地址:https://www.cnblogs.com/jerry19880126/p/3138542.html
Copyright © 2020-2023  润新知