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

泄露的封装

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

为什么不能泄露封装?

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

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

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

泄露的封装的潜在原因

不知道该隐藏哪些东西

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

使用细粒度接口

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

示例分析一

我们用程序来维护一个待办事项列表。在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)

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

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

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏跟着阿笨一起玩NET

c#实现打印功能

3732
来自专栏我和未来有约会

Silverlight第三方控件专题

这里我收集整理了目前网上silverlight第三方控件的专题,若果有所遗漏请告知我一下。 名称 简介 截图 telerik 商 RadC...

4405
来自专栏hbbliyong

WPF Trigger for IsSelected in a DataTemplate for ListBox items

<DataTemplate DataType="{x:Type vm:HeaderSlugViewModel}"> <vw:HeaderSlug...

4224
来自专栏pangguoming

Spring Boot集成JasperReports生成PDF文档

由于工作需要,要实现后端根据模板动态填充数据生成PDF文档,通过技术选型,使用Ireport5.6来设计模板,结合JasperReports5.6工具库来调用渲...

1.4K7
来自专栏转载gongluck的CSDN博客

cocos2dx 打灰机

#include "GamePlane.h" #include "PlaneSprite.h" #include "BulletNode.h" #include...

7226
来自专栏大内老A

The .NET of Tomorrow

Ed Charbeneau(http://developer.telerik.com/featured/the-net-of-tomorrow/) Exciti...

38610
来自专栏一个爱瞎折腾的程序猿

sqlserver使用存储过程跟踪SQL

USE [master] GO /****** Object: StoredProcedure [dbo].[sp_perfworkload_trace_s...

2890
来自专栏落花落雨不落叶

canvas画简单电路图

85211
来自专栏张善友的专栏

Miguel de Icaza 细说 Mix 07大会上的Silverlight和DLR

Mono之父Miguel de Icaza 详细报道微软Mix 07大会上的Silverlight和DLR ,上面还谈到了Mono and Silverligh...

2997
来自专栏张善友的专栏

Mix 10 上的asp.net mvc 2的相关Session

Beyond File | New Company: From Cheesy Sample to Social Platform Scott Hansel...

2787

扫码关注云+社区