封装原则倡导通过隐藏抽象的实现细节和隐藏变化等来实现关注点分离和信息隐藏。
以汽车为例,我们并不需要了解发动机的原理就可以开车。这准确描绘了封装原则的作用:用户无需知道抽象(汽车)的细节,此外,封装原则还让抽象能够隐藏实现细节的变化。发动机是汽油发动机还是柴油发动机并不会对我们开车造成影响。
我们这篇博客主要讲解分析不充分的封装坏味,对于其它封装坏味将在后面的博客讲解分析。
对于抽象的一个或多个成员,声明的访问权限超过了实际需求时,将导致这种坏味。这种坏味的极端表现形式是,存在一些用全局变量、全局数据结构等表示的全局状态,整个软件系统的所有抽象都可以访问它们。
封装的主要意图是将接口和实现分离,以便能够几乎独立地修改它们。这种关注点分离让客户端程序只依赖于抽象的接口,从而能够对它们隐藏实现细节。如果暴露了实现细节,将导致抽象和客户端紧密耦合。这是不可取的,每当修改抽象的实现细节时,都将影响客户端程序。提供超过需要的访问权限可能向客户端程序暴露实现细节,这违反了“隐藏原则”。
为了方便测试,开发人员常常将抽象的私有方法改成公有的。由于私有方法涉及抽象的实现细节,将其改为公有将破坏抽象的封装。
我们都知道代码的可测试性是衡量代码质量的一个重要指标。如果编写的代码无法进行单元测试,代码的质量就无法得到保证。在有些情况下,代码无法编写测试是可以进行代码修改的,我们称之为重构。但是因为访问权限修改代码不在这些情况下,这样做反而会破坏代码的封装。可以借助反射实现低访问权限成员的测试。
以全局变量的方式暴露多个抽象需要使用的数据,从而导致这种坏味。
/// <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
}
}
还有一种极端表现形式:全局变量。对于全局变量,存在两种不同的情形。
对于第一种情形,要进行重构,可以通过参数传递必要的变量。
对于第二种情形,要进行重构,可以根据其承担的责任创建合适的抽象,并在这些抽象中封装原来的全局变量,这样客户端就会使用这些抽象,而不是直接使用全局变量。
参考:《软件设计重构》
-----END-----
喜欢本文的朋友们,欢迎扫一扫下图关注公众号撸码那些事,收看更多精彩内容
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。