Internal类或Internal成员讲解

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

前言

最近除了搞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类或Internal成员讲解相关推荐

  1. 【Groovy】Groovy 方法调用 ( Java 类成员及 setter 和 getter 方法设置 | Groovy 类自动生成成员的 getter 和 setter 方法 )

    文章目录 一.Java 类成员及 setter 和 getter 方法设置 二.Groovy 类自动生成成员的 getter 和 setter 方法 一.Java 类成员及 setter 和 gett ...

  2. 类的static成员并用其实现一个单例模式

    对于特定类型的全体对象而言,有时候可能需要访问一个全局的变量.比如说统计某种类型对象已创建的数量.如果我们用全局变量会破坏数据的封装,一般的用户代码都可以修改这个全局变量,这时我们可以用类的静态成员来 ...

  3. C#中internal和protect internal的理解

    [internal] internal关键字是用来修饰类和类的成员的,表示该内容只能在本程序集中访问. 程序集:一般输出形式为dll或exe的文件. 例子: 如果某个程序集中,有如下的类: names ...

  4. 在保护继承中基类的共有成员_C++学习刷题13--继承的实现、继承的方式

    一.前言 本部分为C++语言刷题系列中的第13节,主要讲解这几个知识点:继承的实现.继承的方式.欢迎大家提出意见.指出错误或提供更好的题目! 二.知识点讲解 知识点1:继承的实现,可以理解派生类拥有成 ...

  5. 在保护继承中基类的共有成员_C++面向对象:C++ 继承

    面向对象程序设计中最重要的一个概念是继承.继承允许我们依据另一个类来定义一个类,这使得创建和维护一个应用程序变得更容易.这样做,也达到了重用代码功能和提高执行效率的效果. 当创建一个类时,您不需要重新 ...

  6. php键顺序初始化,Java类中各成员初始化的顺序

    了解Java中类各个成员的初始化顺序是非常重要的,这样你可以对类有全局的认识.不说太多,直接看下面的例子 class Father { static{ System. out.println(&quo ...

  7. 如何初始化类的static成员变量?

    如何初始化类的static成员变量? 类的static成员变量不被某一个对象所独有,而是被所有同类型的对象所共有. 只能在头文件中声明类的static成员变量,不可在头文件中初始化,否则会造成重定义. ...

  8. C# 使用new 关键字显式隐藏从基类继承的成员和内部类的使用

    在 C# 中,new 关键字可用作运算符.修饰符或约束. 1)new 运算符:用于创建对象和调用构造函数. 2)new 修饰符:在用作修饰符时,new 关键字可以显式隐藏从基类继承的成员. 3)new ...

  9. c++中delete对象后 调用成员函数_C++类的特殊成员函数及default/delete特性

    本文包含以下内容 1. C++的四类特殊成员函数介绍,重点介绍拷贝构造函数和拷贝复制运算符 2. C++11中的default/delete特性 本文内容侧重个人理解,深入理解其原理推荐https:/ ...

最新文章

  1. 华人小哥开发“黑话”数据集,AI:你连dbq都不知道,xswl!| NAACL 2021
  2. if语句输入月份判断季节_C语言循环及条件语句
  3. MyBatis-11MyBatis动态SQL之【if】
  4. 禁止32位安装包运行在64位操作系统上
  5. 9.2-控制单元CU的功能(学习笔记)
  6. 862. 和至少为 K 的最短子数组
  7. 工作88:vue实现当前页面刷新
  8. Mysq数据库备份(win)
  9. 卸下重负,苏宁易购重组价值逻辑
  10. html代码实现全国地图分布,echarts基于canvas中国地图省市地区介绍代码
  11. 迈普交换机中断计算机网络,迈普3100交换机配置命令大全
  12. Photoshop之通道抠图,适用于扣人像,树叶等细微抠图
  13. python电影名称词云_python爬虫——词云分析最热门电影《后来的我们》
  14. MySQL 百分比排序
  15. 前端二面必会面试题(附答案)
  16. 【excel】开启了循环引用怎么关闭
  17. IPV6----升级点,地址分类及部分协议配置
  18. 韩国媒体:中国手机的崛起,都是依靠“性价比”?
  19. cocos内存使用相关
  20. 高性价比成磷酸铁锂杀手锏

热门文章

  1. CuteOne基于Python3的OneDrive多网盘挂载程序+带会员
  2. Intel Realsense L515 ros节点时间戳不同步解决方法
  3. 画论73 龚贤《龚贤论画》
  4. vivado设计4bit先行进位加法器 并使用 4bit CLA 组合设计一个 16bit 加法器
  5. 去除新房子甲醛的方法
  6. 【Node.js实战】一文带你开发博客项目之初识Koa2(koa2安装使用、搭建开发环境、测试路由)
  7. 发个自用的系统镜像,win7和win10,win10 ltsc系统,自带软件安装器,可纯净,绝对稳定
  8. 一招搞定微软验证蓝色五角星。并通过微软正版验证计划!
  9. Saas模式的优势和缺点总结
  10. opencv的VideoWriter类及参数用于保存视频