封装原则倡导经过隐藏抽象的实现细节和隐藏变化等来实现关注点分离和信息隐藏。算法
以汽车为例,咱们并不须要了解发动机的原理就能够开车。这准确描绘了封装原则的做用:用户无需知道抽象(汽车)的细节,此外,封装原则还让抽象可以隐藏实现细节的变化。发动机是汽油发动机仍是柴油发动机并不会对咱们开车形成影响。编程
隐藏实现细节c#
抽象向客户端程序只暴露其提供的功能,而将实现方式隐藏起来。实现方式(即实现细节)包含抽象的内部表示(如抽象使用的数据成员和数据结构)以及有关方法是如何实现的细节(如方法使用的算法)。数据结构
隐藏变化post
隐藏类型或实现结构的实现变化。经过隐藏变化,更容易在不给客户端程序带来太大影响的状况下修改抽象的实现。性能
咱们这篇博客主要讲解分析不充分的封装坏味,对于其它封装坏味将在后面的博客讲解分析。单元测试
对于抽象的一个或多个成员,声明的访问权限超过了实际需求时,将致使这种坏味。这种坏味的极端表现形式是,存在一些用全局变量、全局数据结构等表示的全局状态,整个软件系统的全部抽象均可以访问它们。学习
封装的主要意图是将接口和实现分离,以便可以几乎独立地修改它们。这种关注点分离让客户端程序只依赖于抽象的接口,从而可以对它们隐藏实现细节。若是暴露了实现细节,将致使抽象和客户端紧密耦合。这是不可取的,每当修改抽象的实现细节时,都将影响客户端程序。提供超过须要的访问权限可能向客户端程序暴露实现细节,这违反了“隐藏原则”。测试
为了方便测试,开发人员经常将抽象的私有方法改为公有的。因为私有方法涉及抽象的实现细节,将其改成公有将破坏抽象的封装。this
咱们都知道代码的可测试性是衡量代码质量的一个重要指标。若是编写的代码没法进行单元测试,代码的质量就没法获得保证。在有些状况下,代码没法编写测试是能够进行代码修改的,咱们称之为重构。可是由于访问权限修改代码不在这些状况下,这样作反而会破坏代码的封装。能够借助反射实现低访问权限成员的测试。
以全局变量的方式暴露多个抽象须要使用的数据,从而致使这种坏味。
/// <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 } }
还有一种极端表现形式:全局变量。对于全局变量,存在两种不一样的情形。
对于第一种情形,要进行重构,能够经过参数传递必要的变量。
对于第二种情形,要进行重构,能够根据其承担的责任建立合适的抽象,并在这些抽象中封装原来的全局变量,这样客户端就会使用这些抽象,而不是直接使用全局变量。
存在不充分的封装坏味时,会使代码的可重用性大打折扣,由于客户程序直接依赖你们均可以访问的状态,致使难以在其它地方重用客户程序。
抽象容许直接访问其数据成员时,确保数据和整个抽象完整性的职责由抽象转移到了各个客户程序。增长了代码运行阶段发生问题的可能性。
相对于使用存取器方法控制对变量访问修改带来的好处,使用存取器方法带来的性能开销能够忽略不计。
参考:《软件设计重构》