专栏首页撸码那些事【封装那些事】 泄露的封装

【封装那些事】 泄露的封装

泄露的封装

抽象通过公有接口(方法)暴露或泄露实现细节时,将导致这种坏味。需要注意的是,即使抽象不存在“不充分的封装”坏味,其公有接口也有可能泄露实现细节。

为什么不能泄露封装?

为实现有效封装,必须将抽象的接口(即抽象的内容)和实现(即抽象的方式)分离。为遵循隐藏原则,必须对客户程序隐藏抽象的实现方面。

如果通过公有接口暴露了实现细节(违反了隐藏原则)可能会造成:

  • 对实现进行修改时,可能会影响客户程序
  • 暴露的实现细节可能会让客户程序能够通过公有接口访问内部数据结构,进而有意或无意地损坏抽象的内部状态。

泄露的封装的潜在原因

不知道该隐藏哪些东西

开发人员通常会在无意之间泄露实现细节。

使用细粒度接口

类的公有接口直接提供了细粒度的方法,这些细粒度的方法通常会向客户程序暴露不必要的实现细节。更好的做法是在类的公有接口提供粗粒度的方法,在粗粒度方法内部使用细粒度的私有方法。

示例分析一

我们用程序来维护一个待办事项列表。在ToDoList类中,公有方法GetListEntries()返回对象存储的待办事项列表。

public class ToDoList
{
    private List<string> listEntries = new List<string>();

    public List<string> GetListEntries()
    {
        return listEntries;
    }

    public void AddListEntry(string entry)
    {

    }
}

问题出在方法的返回类型上,它暴露了一个内部细节,ToDoList内部使用List来存储待办事项列表。

现在问题来了,如果待办事项列表程序主要执行插入和删除操作,那么选择使用List没啥问题;但是后来发现查找频率比修改频率高,那么使用HashTable可能更合适。然而GetListEntries()的返回类型是List,如果修改这个方法的返回类型,可能破坏依赖于这个方法的客户程序。如果要支持未来数据结构的变更,方法返回类型可以使用IEnumerable(C#中的集合类型都实现的接口类型),这样可以做到在不改变方法签名的条件下(里氏替换原则),替换存储待办事项列表的数据结构。

重构后的代码实现:

使用List数据结构:

public class ToDoList
{
    private List<string> listEntries = new List<string>();

    public IEnumerable GetListEntries()
    {
        return listEntries;
    }

    public void AddListEntry(string entry)
    {

    }
}

使用Hashtable数据结构:

public class ToDoList
{
    private Hashtable listEntries = new Hashtable();

    public IEnumerable GetListEntries()
    {
        return listEntries;
    }

    public void AddListEntry(string entry)
    {

    }
}

方法GetListEntries()存在另一个严重的问题是,它返回一个指向内部数据结构的引用,通过这个引用,客户程序可以绕过AddListEntry()方法直接修改数据结构。当然如果使用IEnumerable这个问题也就迎刃而解了,因为IEnumerable接口没有相应的针对于某一种数据集合的操作。

public interface IEnumerable
{
    //
    // 摘要:
    //     返回循环访问集合的枚举数。
    //
    // 返回结果:
    //     一个可用于循环访问集合的 System.Collections.IEnumerator 对象。
    [DispId(-4)]
    IEnumerator GetEnumerator();
}

示例分析二

假设显式图像包含4个步骤,这些步骤必须按照特定顺序执行,图形才可以正常显式。

现在在Image类中提供4个公有方法Load(),Process(),Validate(),Show()供客户程序使用,但是这样有一个很麻烦的问题是写客户程序的开发人员不一定会按照正确顺序调用方法使用(永远不要给客户选择的权利)。而且客户程序只是想要显式图像,我们为什么要向它们暴露4个内部步骤呢?这就是泄露的封装的潜在原因——使用细粒度接口。

public class Image
{
    public void Load()
    {
    }
    public void Process()
    {
    }
    public void Validate()
    {
    }
    public void Show()
    {
    }
}

要解决这个问题,可以让Image类只向客户程序暴露一个方法Display(),然后在这个方法内部按照特定顺序调用4个步骤方法。

public class Image
{
    private void Load()
    {
    }
    private void Process()
    {
    }
    private void Validate()
    {
    }
    private void Show()
    {
    }

    public void Display()
    {
        Load();
        Process();
        Validate();
        Show();
    }
}

总结

  1. 抽象通过公有接口暴露或泄露了实现细节时,客户程序可能直接依赖于实现细节吗,这种直接依赖性使得难以在不破坏既有客户代码的情况下对设计进行修改或扩展。
  2. 抽象泄露了内部数据结构时,抽象的完整性遭到了破坏。增加了代码运行阶段发生问题的可能性。

参考:《软件设计重构》

本文分享自微信公众号 - 撸码那些事(lumanxs),作者:撸码那些事

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2018-05-04

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 【封装那些事】 泄露的封装

    抽象通过公有接口(方法)暴露或泄露实现细节时,将导致这种坏味。需要注意的是,即使抽象不存在“不充分的封装”坏味,其公有接口也有可能泄露实现细节。

    撸码那些事
  • 【抽象那些事】命令式抽象

    撸码那些事
  • 【抽象那些事】 命令式抽象

    这种坏味是由操作转换为类引起的,表现为类中只定义了一个方法,有时候类名和方法名相同。这种坏味还常常表现为方法操作的数据位于另一个类中。

    撸码那些事
  • 【封装那些事】 泄露的封装

    抽象通过公有接口(方法)暴露或泄露实现细节时,将导致这种坏味。需要注意的是,即使抽象不存在“不充分的封装”坏味,其公有接口也有可能泄露实现细节。

    撸码那些事
  • 设计模式实战-状态模式,让代码更清爽简洁

    状态模式(Allow an object to alter its behavior when its internal state changes.The o...

    架构师修炼
  • Java设计模式之模板方法模式

    假设我们现在要造一批悍马汽车,悍马汽车有两个系列H1和H2,首先不考虑任何设计模式,看看设计的类图:

    CoderJed
  • 数据分析工作能否外包?

    大数据文摘
  • 数据分析工作能否外包?

    人们对大数据兴趣激增,数据分析团队也显得供不应求。大数据能让企业变得更有效率,提升整体的竞争力。具备高级数据分析能力的公司已经找到了构建长期优势的方法。例如联邦...

    小莹莹
  • 【观点】六问数据分析外包公司:您是否应该外包数据分析?

    人们对大数据兴趣激增,数据分析团队也显得供不应求。大数据能让企业变得更有效率,提升整体的竞争力。具备高级数据分析能力的公司已经找到了构建长期优势的方法。例如联邦...

    小莹莹
  • Android Lifecycle结合RxJava&Retrofit实现安全的网络回调

    生命周期感知组件可以响应另一个组件生命周期的变化(例如Activity和Fragment的生命周期状态更改)。 这些(实现了Lifecycle的)组件可帮助你构...

    冰之角

扫码关注云+社区

领取腾讯云代金券