• 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对象)。而对外公开的构造函数,便可以直接提供一个具体的实例,作为真实场景中的使用方式。

  • 相关阅读:
    九.Spring Boot JPAHibernateSpring Data
    JIRA安装过程中链接mysql的问题!
    五、案例-指令参考-freemarker指令、表达式
    四、模版指令操作
    三、freemarker数据、模版指令
    二、freemarker.controller半自动静态化+Tomcat虚拟资源映射
    一、springMVC、freemarker页面半自动静态化
    window窗口-button(按钮)-dialog(对话框,带按钮)
    panel面板
    python 给定年份、月份,返回当月对应天数
  • 原文地址:https://www.cnblogs.com/debuging/p/2725583.html
Copyright © 2020-2023  润新知