【封装那些事】 不充分的封装

封装原则倡导通过隐藏抽象的实现细节隐藏变化等来实现关注点分离和信息隐藏。

以汽车为例,我们并不需要了解发动机的原理就可以开车。这准确描绘了封装原则的作用:用户无需知道抽象(汽车)的细节,此外,封装原则还让抽象能够隐藏实现细节的变化。发动机是汽油发动机还是柴油发动机并不会对我们开车造成影响。

封装原则的实现手法

  • 隐藏实现细节 抽象向客户端程序只暴露其提供的功能,而将实现方式隐藏起来。实现方式(即实现细节)包含抽象的内部表示(如抽象使用的数据成员和数据结构)以及有关方法是如何实现的细节(如方法使用的算法)。
  • 隐藏变化 隐藏类型或实现结构的实现变化。通过隐藏变化,更容易在不给客户端程序带来太大影响的情况下修改抽象的实现。

违反封装原则导致的坏味

我们这篇博客主要讲解分析不充分的封装坏味,对于其它封装坏味将在后面的博客讲解分析。

不充分的封装

对于抽象的一个或多个成员,声明的访问权限超过了实际需求时,将导致这种坏味。这种坏味的极端表现形式是,存在一些用全局变量、全局数据结构等表示的全局状态,整个软件系统的所有抽象都可以访问它们。

为什么要有充分的封装?

封装的主要意图是将接口和实现分离,以便能够几乎独立地修改它们。这种关注点分离让客户端程序只依赖于抽象的接口,从而能够对它们隐藏实现细节。如果暴露了实现细节,将导致抽象和客户端紧密耦合。这是不可取的,每当修改抽象的实现细节时,都将影响客户端程序。提供超过需要的访问权限可能向客户端程序暴露实现细节,这违反了“隐藏原则”。

不充分的封装的潜在原因

为方便测试

为了方便测试,开发人员常常将抽象的私有方法改成公有的。由于私有方法涉及抽象的实现细节,将其改为公有将破坏抽象的封装。

我们都知道代码的可测试性是衡量代码质量的一个重要指标。如果编写的代码无法进行单元测试,代码的质量就无法得到保证。在有些情况下,代码无法编写测试是可以进行代码修改的,我们称之为重构。但是因为访问权限修改代码不在这些情况下,这样做反而会破坏代码的封装。可以借助反射实现低访问权限成员的测试。

在面向对象编程中采用过程型思维

以全局变量的方式暴露多个抽象需要使用的数据,从而导致这种坏味。

示例分析

/// <summary>
/// 消息发布类
/// </summary>
public class Publisher
{
    /// <summary>
    /// 频道号 范围1-100
    /// </summary>
    public int channel;
    /// <summary>
    /// 创建一个特定频道的发布者对象
    /// </summary>
    /// <param name="channel">频道号 范围1-100</param>
    public Publisher(int channel)
    {
        this.channel =  channel;
    }

    public vois Publish(string message)
    {
        //向频道channel发布消息message
    }
}

上面代码示例就是不充分的封装的典型,频道号变量channel被设置为public是不合适的,因为创建消息发布对象时就已经指定发布的频道号,channel被设置为public,频道号在客户端使用的时候就可以随意的被访问修改,这样客户端就会了解消息发布类的内部实现,造成了直接依赖,违反了“高内聚,低耦合”原则。这样每当修改内部实现时都会对客户端造成影响。更重要的一点是频道号变量channel是有范围限定的(1-100),客户端使用的时候随意的修改channel,可能会造成channel越界的错误。所以正确的做法是将channel变量设置为私有的,并且为其提供合适的存取器方法。

重构后的代码实现:

/// <summary>
/// 消息发布类
/// </summary>
public class Publisher
{
    /// <summary>
    /// 频道号 范围1-100
    /// </summary>
    private int channel;
    /// <summary>
    /// channel赋值,支持范围限定
    /// </summary>
    /// <param name="channel">频道号 范围1-100</param>
    public void SetChannel(int channel)
    {
        if(channel < 1 || channel > 100)
        {
            throw new ArgumentOutOfRangeException("超出频道号 范围1-100");
        }
        this.channel = channel;
    }

    /// <summary>
    /// 创建一个特定频道的发布者对象
    /// </summary>
    /// <param name="channel">频道号 范围1-100</param>
    public Publisher(int channel)
    {
        SetChannel(channel);
    }

    public vois Publish(string message)
    {
        //向频道channel发布消息message
    }
}

还有一种极端表现形式:全局变量。对于全局变量,存在两种不同的情形。

  • 将一个或多个成员设置为全局可见的,但是只有少量类会访问它们。
  • 将一个或多个成员设置为全局可见的,有大量的类会访问它们。

对于第一种情形,要进行重构,可以通过参数传递必要的变量。

对于第二种情形,要进行重构,可以根据其承担的责任创建合适的抽象,并在这些抽象中封装原来的全局变量,这样客户端就会使用这些抽象,而不是直接使用全局变量。

总结

  1. 存在不充分的封装坏味时,会使代码的可重用性大打折扣,因为客户程序直接依赖大家都可以访问的状态,导致难以在其它地方重用客户程序。
  2. 抽象允许直接访问其数据成员时,确保数据和整个抽象完整性的职责由抽象转移到了各个客户程序。增加了代码运行阶段发生问题的可能性。
  3. 相对于使用存取器方法控制对变量访问修改带来的好处,使用存取器方法带来的性能开销可以忽略不计。

参考:《软件设计重构》

                                                     -----END-----

喜欢本文的朋友们,欢迎扫一扫下图关注公众号撸码那些事,收看更多精彩内容

原创声明,本文系作者授权云+社区发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

编辑于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏java思维导图

架构师必须掌握的 10 条设计原则

函数是程序员的工具中最重要的抽象形式。它们能更多地被重复使用,你需要编写的代码就越少,代码也因此变得更可靠。较小的函数遵循单一职责原则更有可能被重复使用。

8330
来自专栏程序员的碎碎念

redis不难,benny带你入门

关于redis的学习,相信在各大博客、公众号上和教学视频教程里有很多,benny学习了一段时间总算是入门了,在会不定期的更新redis学习日记。

14240
来自专栏Java技术栈

Maven精选系列--classifier元素妙用

先来看这么一个依赖 <dependency> <groupId>net.sf.json-lib</groupId> <artifactId>js...

361100
来自专栏张善友的专栏

VS2010测试方面的文章

      VS 2010 带来了更多崭新的功能,这些新功能贯穿了整个测试周期 : 测试计划、测试执行和测试执行进度跟踪,VS 2010 引入了一个全新的工具,...

188100
来自专栏逸鹏说道

记一次w3wp占用CPU过高的解决过程(Dictionary和线程安全)

项目上线以来一直存在一个比较揪心的问题,和一个没有信心处理的BUG,那就是在应用程序启动时有可能会导致cpu跑满99%或持续在一个值如50%左右,这样一来对服务...

28850
来自专栏IT可乐

深入理解计算机系统(3.1)------汇编语言和机器语言

  《深入理解计算机系统》第三章——程序的机器级表示。作者首先讲解了汇编代码和机器代码的关系,阐述了汇编承上启下的作用;接着从机器语言IA32着手,分别讲述了如...

42690
来自专栏Golang语言社区

论Go语言中goroutine的使用

go中的goroutine是go语言在语言级别支持并发的一种特性。初接触go的时候对go的goroutine的欢喜至极,实现并发简便到简直bt的地步。但是在项目...

52480
来自专栏编程微刊

前端最常用的模板引擎-Handlebars

接触过的模板引擎不算多,只用过jsp和ejs,jsp属于Java语系范畴也不算难,对于大前端来说,Handlebars怎么能够缺席,前端必须掌握技能之一,模板引...

16710
来自专栏http://www.cnblogs.com

员工信息表程序

1.需求 (1).工信息表程序,实现增删改查操作: (2).可进行模糊查询,语法至少支持下面3种: select name,age from staff_...

383110
来自专栏做全栈攻城狮

程序员带你十天快速入门Python,玩转电脑软件开发(二)

声明:本次教程主要适用于已经习得一门编程语言的程序员。想要学习第二门语言。有梦想,立志做全栈攻城狮的你

8710

扫码关注云+社区

领取腾讯云代金券