类中的internal成员可能是一种坏味道
前言
最近除了搞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成员可能是一种坏味道的更多相关文章
- 在C++的类中,普通成员函数不能作为pthread_create的线程函数,如果要作为pthread_create中的线程函数,必须是static
在C++的类中,普通成员函数不能作为pthread_create的线程函数,如果要作为pthread_create中的线程函数,必须是static ! 在C语言中,我们使用pthread_create ...
- C++ 类中的static成员的初始化和特点
C++ 类中的static成员的初始化和特点 #include <iostream> using namespace std; class Test { public: Test() : ...
- cc31a_demo--CppPrimer_静态成员与继承-在派生类中访问基类中的static成员的方法
//*基类中的static成员,在整个继承层次中只有一个实例 //*在派生类中访问基类中的static成员的方法 //1.基类名::成员名 //2.子类名::成员名 //3.对象.成员名 //4.指针 ...
- C++类中的常成员和静态成员
常变量.常对象.常引用.指向常对象或常变量的指针等在定义时都使用了const关键字,这是C++语言引入的一种数据保护机制,称为const数据保护机制.例如通过const关键字主动地将被调函数形参进行限 ...
- [转]C++ 类中的static成员的初始化和特点
在C++的类中有些成员变量初始化和一般数据类型的成员变量有所不同.以下测试编译环境为: ➜ g++ -v Using built-in specs. COLLECT_GCC=g++ Target: x ...
- c++:类中的static成员
首先静态成员可以是public的,也可以是private的,只需在一般的变量.函数声明语句前加上static关键字即可声明一个static变量. 类中的静态成员存在与任何对象之外,所有该类对象的共享一 ...
- C++ 类中特殊的成员变量(常变量、引用、静态)的初始化方法
有些成员变量的数据类型比较特别,它们的初始化方式也和普通数据类型的成员变量有所不同.这些特殊的类型的成员变量包括: a.引用 b.常量 c.静态 d.静态常量(整型) e.静态常量(非整型) 常量和引 ...
- 【c++】类中的const成员
const成员变量 举个例子 #include <iostream> using namespace std; class A { public: A(int size) : SIZE(s ...
- Python 类中的"静态"成员变量
本文环境:Python 2.7 一个类的三个对象实例的属性被同时修改 有段代码如下: class task_queue: queue=[] def append(self,obj): self.que ...
随机推荐
- Selenium2+python自动化14-iframe【转载】
前言 有很多小伙伴在拿163作为登录案例的时候,发现不管怎么定位都无法定位到,到底是什么鬼呢,本篇详细介绍iframe相关的切换 以http://mail.163.com/登录页面10为案例,详细介绍 ...
- Google C++ 编码规范(中文版)
Google C++ 编码规范(英文)在线地址:http://google-styleguide.googlecode.com/svn/trunk/cppguide.xml 中文版PDF下载:本地下载
- easyui常用
清空列表选中项 //easyui datagrid 清空勾选 $('#List').datagrid('clearSelections'); 扩展修改查询方法 function QueryList() ...
- J.U.C并发框架源码阅读(十五)CopyOnWriteArrayList
基于版本jdk1.7.0_80 java.util.concurrent.CopyOnWriteArrayList 代码如下 /* * Copyright (c) 2003, 2011, Oracle ...
- (6)sql/puls
host 在sql/puls中使用cmd或linux操作系统的命令
- Python的并发并行[3] -> 进程[0] -> subprocess 模块
subprocess 模块 0 模块描述 / Module Description From subprocess module: """Subprocesses wit ...
- 新疆大学ACM-ICPC程序设计竞赛五月月赛(同步赛)- chess(威佐夫博奕)
---恢复内容开始--- 链接:https://www.nowcoder.com/acm/contest/116/G来源:牛客网 题意:一个棋盘,老王和小人下棋,棋子只能往下或者往左或者往左下走,小人 ...
- 【noip2017 day2T2】【蚯蚓】巧用队列单调性线性处理
(画师当然是武内崇啦) Description 本题中,我们将用符号[c]表示对c向下取整,例如:[3.0」= [3.1」=[3.9」=3.蛐蛐国最近蚯蚓成灾了!隔壁跳蚤国的跳蚤也拿蚯蚓们没办法,蛐蛐 ...
- sql 分组后按时间降序排列再取出每组的第一条记录
原文:sql 分组后按时间降序排列再取出每组的第一条记录 竞价记录表: Aid 为竞拍车辆ID,uid为参与竞价人员ID,BidTime为参与竞拍时间 查询出表中某人参与的所有车辆的最新的一条的竞价记 ...
- 【Mybatis】mybatis查询报错org.apache.ibatis.reflection.ReflectionException: There is no getter for property named 'areaName' in 'class java.lang.String'
mybatis查询报错: Caused by: org.apache.ibatis.reflection.ReflectionException: There is no getter for pro ...