前言

最近除了搞ASP.NET MVC之外,我也在思考一些编程实践方面的问题。昨天在回家路上,我忽然对一个问题产生了较为清晰的认识。或者说,原先只是有一丝细微的感觉,而现在将它和一些其他的方面进行了联系,也显得颇为“完备”。这就是问题便是:如何对待类中internal成员。我现在认为“类中的internal成员可能是一个坏味道”,换句话说,如果您的类中出现了internal的成员,就可能是设计上的问题了。

可能这个命题说得还有些笼统,所以再详细地描述一下比较妥当。我的意思是,您的类库中出现internal的类型是完全没有问题的(也肯定是无法避免的)。然而,一个经过良好设计的类型,是应该很少出现internal的方法或属性的(字段就不在考虑范围,因为它应该永远是私有的)。其中有例外,如“构造函数”的修饰级别,稍后会再谈到。

C#中一个类中的成员有四种修饰级别:

  • public:完全开放,谁都能访问。
  • private:完全封闭,只有类自身可以访问。
  • internal:只对相同程序集,或使用InternalVisibleToAttribute标记的程序集开放。
  • protected:只对子类开放。

您也可以将protected和internal修饰同一个成员,这使得类中的一个成员可以拥有5种不同的访问权限。我认为,其中pubic、private和protected级别的含义是清晰而纯粹的,而internal的开放程度则是像是一个“灰色地带”。

Internal类中的Internal成员

我们为什么会使用internal修饰符?最简单的答案,自然是为了让相同程序集内类型可以访问,但是不对外部开放。那么我们什么时候会用这种访问级别呢?可能是这样的:

internal class SomeClass
{
    internal void SomeMethod() { }
}

请注意,这里我们在一个internal的类型中使用了internal来修饰这个方法。这是一种累赘,因为它和public修饰效果完全一致,这会造成不清晰的修饰性(灰色地带)。因此,在internal类型中,所有的成员只能是public、private和protected访问级别。也就是说,上面的代码应该改成:

internal class SomeClass
{
    public void SomeMethod() { }
}

于是,内部类中哪些是私有的,哪些是公开的(可以被相同程序集内访问到)一目了然。这个类的职责也非常明确。

Public类的Internal成员

这个问题就麻烦了许多,因为此时类中的internal成员含义就非常明确了:

public class SomeClass
{
    internal void SomeMethod() { }
}

public类中的internal成员可以被相同程序集内的类型访问到,而对外部的程序集是隐藏的。这意味着,这个类的功能分了两部分,一部分对所有人公开,还有一部分对自己人公开,对其他人关闭。在很多时候,这可能意味着一个类拥有了两种职责,一种对外,一种对内,而这种情况显然违背了“单一职责原则”。这时候我们可能需要重构,把一部分对内的职责封装为额外的internal类型,并负责内部逻辑的交互。如此,代码可能就会写成这样:

internal class InternalClass
{
    private SomeClass m_someClass;

    public InternalClass(SomeClass someClass)
    {
        this.m_someClass = someClass;
    }

    public void SomeMethod()
    {
        /* use data on this.m_someClass. */
    }
}

public class SomeClass
{
    // public members
}

不过这可能也是最容易产生争议的地方,因为这“削减”了internal的相当一大部分作用,此外还会造成代码的增加。而事实上,很多时候也应该在public类中使用internal方法,只要不违背“单一职责原则”即可。不过我想,这方面的“权衡”应该也是较为容易的,因为基本上所有的考量都是基于“职责”的。

这也是我思考中经常遇到的问题,就是某种“实践”是不是属于“过度设计”了。我们的目标是快速发布,确保质量,而不是为了遵循原则而去遵循原则。在今后此类文章中,我也会提出类似的“权衡”,如果您有看法,欢迎和我交流。

为了单元测试而使用Internal成员

例如,一个类中有一个复杂的私有方法,我们希望对它进行单元测试。由于private成员无法被外部访问,因此我们会将其写成internal的方法:

public class SomeClass
{
    public void SomeMethod()
    {
        // do something...
        this.ComplexMethod();
        // do something else...
    }

    internal void ComplexMethod() { }
}

由于是internal方法,我们可以使用InternalVisibleToAttribute释放给其他程序集,就可以在那个程序集中编写单元测试代码。但是我认为这个做法不好。

首先,我一直不喜欢为了“单元测试”而改变原有的封装性,即使改成internal成员后,对其他外部程序集来说并没有什么影响。 在MSDN Web Cast或其他一些地方,我可能讲过我们“可以”把private方法改为internal,仅仅是为单元测试。还有便是把protected也改成protected internal——我也会写文章讨论这个问题。

其实这又涉及到是否应该测试私有方法的问题,我最近会再对此进行较为详细的讨论。如果您有一个需要测试的复杂的私有方法,这意味着这个私有方法可能会有独立的职责,独立的算法。我们又值得将其独立提取出来:

internal class ComplexClass
{
    public void ComplexMethod() { }
}

public class SomeClass
{
    private ComplexClass m_complexClass = new ComplexClass();

    public void SomeMethod()
    {
        // do something...
        this.m_complexClass.ComplexMethod();
        // do something else...
    }
}

由于ComplexClass是internal的,我们便可以为其进行独立的单元测试。

一些例外情况

万事都有例外。例如对于构造函数来说,internal在很多时候是一个“必须”的修饰符:

internal class ComplexClass
{
    public virtual void ComplexMethod() { }
}

public class SomeClass
{
    private ComplexClass m_complexClass;

    public SomeClass()
        : this(new ComplexClass())
    { }

    internal SomeClass(ComplexClass complexClass)
    {
        this.m_complexClass = complexClass;
    }

    public void SomeMethod()
    {
        // do something...
        this.m_complexClass.ComplexMethod();
        // do something else...
    }
}

由于其中一个构造函数是internal的,并接受一个对象,因此单元测试便可以利用这个构造函数“注入”一个对象(往往是一个Mock对象)。而对外公开的构造函数,便可以直接提供一个具体的实例,作为真实场景中的使用方式。

讨论

这便是我的观点:“类中的internal成员可能是一种坏味道”。您同意吗?如果您有什么看法,希望能够和我讨论一下。

http://www.cnblogs.com/JeffreyZhao/archive/2009/08/26/internal-member-is-bad-smell.html

类中的internal成员可能是一种坏味道的更多相关文章

  1. 在C++的类中,普通成员函数不能作为pthread_create的线程函数,如果要作为pthread_create中的线程函数,必须是static

    在C++的类中,普通成员函数不能作为pthread_create的线程函数,如果要作为pthread_create中的线程函数,必须是static ! 在C语言中,我们使用pthread_create ...

  2. C++ 类中的static成员的初始化和特点

    C++ 类中的static成员的初始化和特点 #include <iostream> using namespace std; class Test { public: Test() : ...

  3. cc31a_demo--CppPrimer_静态成员与继承-在派生类中访问基类中的static成员的方法

    //*基类中的static成员,在整个继承层次中只有一个实例 //*在派生类中访问基类中的static成员的方法 //1.基类名::成员名 //2.子类名::成员名 //3.对象.成员名 //4.指针 ...

  4. C++类中的常成员和静态成员

    常变量.常对象.常引用.指向常对象或常变量的指针等在定义时都使用了const关键字,这是C++语言引入的一种数据保护机制,称为const数据保护机制.例如通过const关键字主动地将被调函数形参进行限 ...

  5. [转]C++ 类中的static成员的初始化和特点

    在C++的类中有些成员变量初始化和一般数据类型的成员变量有所不同.以下测试编译环境为: ➜ g++ -v Using built-in specs. COLLECT_GCC=g++ Target: x ...

  6. c++:类中的static成员

    首先静态成员可以是public的,也可以是private的,只需在一般的变量.函数声明语句前加上static关键字即可声明一个static变量. 类中的静态成员存在与任何对象之外,所有该类对象的共享一 ...

  7. C++ 类中特殊的成员变量(常变量、引用、静态)的初始化方法

    有些成员变量的数据类型比较特别,它们的初始化方式也和普通数据类型的成员变量有所不同.这些特殊的类型的成员变量包括: a.引用 b.常量 c.静态 d.静态常量(整型) e.静态常量(非整型) 常量和引 ...

  8. 【c++】类中的const成员

    const成员变量 举个例子 #include <iostream> using namespace std; class A { public: A(int size) : SIZE(s ...

  9. Python 类中的"静态"成员变量

    本文环境:Python 2.7 一个类的三个对象实例的属性被同时修改 有段代码如下: class task_queue: queue=[] def append(self,obj): self.que ...

随机推荐

  1. C/51单片机

    1.    串口也可以有多根线的,但是各线之间没有协调同步发送,而是各自是独自发送的.并口是同步发送,同步一次8位同时成功同时失败,类比事务. 2.    ASCII码的前32位是通讯预留的编码即使现 ...

  2. 我为什么喜欢Go语言

    从2000年至今,也写了11年代码了,期间用过VB.Delphi.C#.C++.Ruby.Python,一直在寻找一门符合自己心意和理念的 语言.我很在意写代码时的手感和执行的效率,所以在Go出现之前 ...

  3. codeforces-540C

    题目连接:http://codeforces.com/problemset/problem/540/C C. Ice Cave time limit per test 2 seconds memory ...

  4. 51nod 1062 序列中最大的数【打表】

    1062 序列中最大的数 题目来源: Ural 1079 基准时间限制:1 秒 空间限制:131072 KB 分值: 10 难度:2级算法题  收藏  关注 有这样一个序列a: a[0] = 0 a[ ...

  5. 「kuangbin带你飞」专题二十二 区间DP

    layout: post title: 「kuangbin带你飞」专题二十二 区间DP author: "luowentaoaa" catalog: true tags: - ku ...

  6. [JSOI2012]玄武密码

    题目大意: 给定一个目标串$t(|t|\le10^7)$和$m(m\le10^5)$个模板串$s_i(|s_i|\le100)$,对于每个$s_i$,求$s_i$在$t$中出现过的最长前缀. 思路: ...

  7. 八. 输入输出(IO)操作8.文件的压缩处理

    Java.util.zip 包中提供了可对文件的压缩和解压缩进行处理的类,它们继承自字节流类OutputSteam 和 InputStream.其中 GZIPOutputStream 和 ZipOut ...

  8. Bean装配--xml

    1,bean package com.songyan.zhangpei; import java.util.ArrayList; import com.sun.xml.internal.bind.v2 ...

  9. 第七章 android-UI组件

    一.本章目录 二.用户界面概述 1,用户界面简介 (1)系统和用户之间进行信息交换的媒介 2,设计手机用户界面应解决的问题 (1)需要界面设计和逻辑代码完全分离(布局和逻辑代码分开放) (2)根据不同 ...

  10. 在Spring MVC Controller中注入HttpServletRequest对象会不会造成线程安全的问题

    做法: 1.比如我们在Controller的方法中,通常是直接将HttpServletRequest做为参数,而为了方便节省代码,通常会定义为全局变量,然后使用@Autowire注入. 说明: 1.观 ...