不完整的抽象

抽象未支持所有互补或相关的方法时,将导致这种坏味。

为什么要有完整的抽象?

一种重要的抽象实现手法是创建内聚而完整的抽象。抽象未支持相关的方法时,可能会影响抽象的内聚性和完整性。如果抽象只支持部分相关的方法,其使用者就可能不得不自己去实现其他的功能。客户程序可能尝试直接访问抽象的内部实现细节,此时带来的副作用是违反封装原则。

一些常见的互补方法对

Min/Max Open/Close Create/Destroy Get/Set
Read/Write Print/Scan First/Last Begin/End
Start/Stop Lock/Unlock Show/Hide Up/Down
Source/Target Insert/Delete First/Last Push/Pull
Enable/Disable Acquire/Release Left/Right On/Off

现实考虑

禁止特定行为

设计人员可能有意识地做出不提供堆成或配套方法的设计决策。例如在只读集合中,包含Add()方法,而不包含Remove()方法。

使用单个方法而不是成对的方法

例如用SetEnabled(bool)替换Enable()和Disable(),传入true代表启用,fasle代表禁用。

多方面抽象

抽象被赋予不止一项职责时,将导致这种坏味。

为什么不可以有多方面抽象?

单一职责原则指出,抽象必须承担单一而明确的职责,且必须完全封装该职责。抽象承担了多种职责时,意味着它将受多种原因的影响而需要修改,设计的修改频率与其缺陷数之间存在很强的正相关关系。这意味着多方面抽象存在的缺陷可能更多。

多方面抽象的潜在原因

通用抽象

引入使用通用名(如Item,Order,Product,Image)的抽象时,它常常会成为占位符,用于提供所有相关(但未必属于它)的功能。抽象的命名代表了这个抽象的职责,命名太通用,随着系统的迭代,抽象会慢慢承担多种职责。感同身受!!!

未定期重构

对类进行了大量修改而没有定期重构,长此以往,可能就会在类中引入了额外的职责。

混合关注点

没有对关注点分离给予足够的重视。

重构建议

类承担了多种职责时,就不是内聚的。可以使用“提取类”来进行重构。

未用的抽象

创建的抽象未用(未被直接使用或继承)时,将导致这种坏味。有以下两种表现形式:

  • 未引用的抽象:未用的具体类
  • 鳏寡抽象:没有任何派生抽象的接口/抽象类

为什么不可以有未用的抽象?

设计中的抽象未被使用,就没有发挥任何作用,因此违反了抽象原则。未实现的抽象类和接口时多余的或凭空想象出来的概括,因此是不需要的。

未用的抽象潜在原因

凭空想象的设计

试图设计"永不过时"的系统或在其中包含"未来可能用得着"的抽象时,将导致这种坏味。

不断变化的需求

需求不断变化,为满足早期需求而创建的抽象可能已经不再需要。如果将其留在设计中,它将变成未用的抽象。

维护过程中留下的垃圾

维护或重构时,如果不清理旧的抽象,可能留下未引用的抽象。这点深有体会,所以一直要求组员在重构的过程中,一定要把旧代码删除。如果不这样做,过期的和未用的代码将导致代码库急剧膨胀,重构的代码和未重构的代码纠缠在一起,代码的可理解性、阅读体验极差。而且如果你重构的旧代码你不负责删除,其他人就更不知道如何下手了,久而久之这些旧代码就会变成BUG的温床。注释掉旧代码也不是一个好的选择,太影响阅读体验。其次,现在的代码版本控制工具功能强大,即使删除错了代码,也可以通过版本控制工具找回。所以旧代码必须死。

担心破坏既有代码

不确定是否还有其他代码在使用想要删除的旧代码。

重构建议

将未用的抽象从设计中删除。对于可能还有客户程序在使用的API,直接删除不可行,可将这些抽象标记为''过期的''或"已摒弃",明确地指出在新开发的客户程序中不得使用它们。

[Obsolete]
public class Report
{
}

现实考虑

类库和框架通常以抽象类或接口的方式提供扩展点,这些抽象类可能在库或框架中未被使用,但它们是供客户程序使用的扩展点,因此不属于未用的抽象。

重复的抽象

两个抽象的名称、实现或两者相同时,将导致这种坏味。

  • 名称相同

    两个不同的抽象重名将影响可理解性。

  • 实现相同

    多个抽象的成员定义在语义上相同,但在设计上没有捕获并使用这些实现中相同的元素。在继承层析结构中,如果多个兄弟抽象的实现相同,可能意味着存在的是"未归并的层次结构"坏味。

  • 名称实现都相同

为什么不可以有重复的抽象?

重复代码是软件万恶之首。所以我们要极力避免重复。

如果多个抽象的名称相同,将影响设计的可理解性:客户代码开发人员将不知道使用哪个抽象。

如果多个抽象的实现相同(代码相同),将难以维护:修改其中一个抽象的实现时,常常需要修改其它所有重复抽象的实现。这不仅增加了修改负担,还可能引入难以发现的微小BUG。为缩小修改范围,必须尽可能避免重复。

重复的抽象潜在原因

复制粘贴编程手法

CV程序员复制并粘贴代码,而不应用合适的抽象。

即兴维护

经过多年的修复或改进后,软件将包含"残留",其中有大量重复的代码。

交流不畅

不同时期通常由不同的人员负责维护软件,他们对软件的了解不彻底,编写了原来就有的类或方法,导致软件包含重复的代码。

类被声明为不可扩展的

类被声明为不可扩展的,无法重用代码,只能复制代码,创建修订版本。

重构建议

对于名称相同的重复抽象,可以将其中一个抽象改为不同的名称。

对于实现相同的重复抽象,如果实现完全相同,可将其中一个抽象删除。如果实现稍有差异,可将相同的实现归并到另一个类中:这可以是层次结构中的基类,也可以是重复的抽象可引用或使用的既有类或新类。

现实考虑

适应变化

导致重复抽象的一个原因是,要同时支持同步和非同步变种。

在不同的上下文中使用相同的类型名

对于大型系统,建立完全统一的领域模型要么不可行要么不划算。领域驱动设计提供的一种解决方案是,将大型系统分成多个"界限上下文"。采用这种方式,不同上下文中的模型可能包含同名的类型,但是这是可以接受的。

语言未提供重复避免支持

在JDK中,有很多的重复的方法和类,这是因为没有对基本类型提供泛型支持。但是在.Net中就不会有这么多重复的方法和类,因为C#对基本类型提供了泛型支持。

参考:《软件设计重构》

来源:http://songwenjie.cnblogs.com/

声明:本文为博主学习感悟总结,水平有限,如果不当,欢迎指正。如果您认为还不错,不妨点击一下下方的【推荐】按钮,谢谢支持。转载与引用请注明出处。
微信公众号:

【抽象那些事】不完整的抽象&多方面抽象&未用的抽象&重复的抽象的更多相关文章

  1. Scala Class etc.

    Classes 一个源文件可包含多个类,每个类默认都是 public 类字段必须初始化,编译后默认是 private,自动生成 public 的 getter/setter :Person 示例 pr ...

  2. 关于API的设计和需求抽象

    一,先来谈抽象吧,因为抽象跟后面的API的设计是息息相关的 有句话说的好(不知道谁说的了):计算机科学中的任何问题都可以抽象出一个中间层就解决了. 抽象是指在思维中对同类事物去除其现象的.次要的方面, ...

  3. Java基础-四大特性理解(抽象、封装、继承、多态)

    抽象: 象就是有点模糊的意思,还没确定好的意思. 就比方要定义一个方法和类.但还没确定怎么去实现它的具体一点的子方法,那我就可以用抽象类或接口.具体怎么用,要做什么,我不用关心,由使用的人自己去定义去 ...

  4. ORACLE抽象数据类型

    ORACLE抽象数据类型 *抽象数据类型*/1,抽象数据类型 概念包含一个或多个子类型的数据类型不局限于ORACLE的标准数据类型可以用于其他数据类型中 2,创建抽象数据类型 的语法(必须用NOT F ...

  5. 流畅python学习笔记:第十一章:抽象基类

    __getitem__实现可迭代对象.要将一个对象变成一个可迭代的对象,通常都要实现__iter__.但是如果没有__iter__的话,实现了__getitem__也可以实现迭代.我们还是用第一章扑克 ...

  6. JavaScript的工作原理:解析、抽象语法树(AST)+ 提升编译速度5个技巧

    这是专门探索 JavaScript 及其所构建的组件的系列文章的第 14 篇. 如果你错过了前面的章节,可以在这里找到它们: JavaScript 是如何工作的:引擎,运行时和调用堆栈的概述! Jav ...

  7. 流畅的python学习笔记:第十一章:抽象基类

    __getitem__实现可迭代对象.要将一个对象变成一个可迭代的对象,通常都要实现__iter__.但是如果没有__iter__的话,实现了__getitem__也可以实现迭代.我们还是用第一章扑克 ...

  8. 用C# (.NET Core) 实现抽象工厂设计模式

    用C# (.NET Core) 实现抽象工厂设计模式   本文的概念性内容来自深入浅出设计模式一书. 上一篇文章讲了简单工厂和工厂方法设计模式 http://www.cnblogs.com/cgzl/ ...

  9. 创建型模式(三) 抽象工厂模式(Abstract Factory)

    一.动机(Motivation) 在软件系统中,经常面临着“一系列相互依赖的对象”的创建工作:同时,由于需求的变化,往往存在更多系列对象的创建工作. 如何应对这种变化?如何绕过常规的对象创建方法(ne ...

随机推荐

  1. EFI怎么装系统? UEFI BIOS

    关于EFI的介绍,就不赘述了. 大家可以看看这个帖子 http://benyouhui.it168.com/thread-2488583-1-1.html 总之,新电脑都是这玩意,win8也做了相应E ...

  2. jquery easyui datagrid 分页实现---善良公社项目

    接着上篇文章,接下来给大家分享分页的实现,分页其实多多少少见过很有几种,框架中带的图片都特别的好看,会给用户以好的使用效果,具体实现,需要自己来补充代码: 图示1: 通常情况下页面数据的分页显示分成真 ...

  3. C++ Primer 有感(类)

    1.在类内部,声明成员函数时必需 的,而定义成员函数则是可选的.在类内部定义的函数默认为inline. 2.const成员函数不能改变其所操作的对象的数据成员.const必须同时出现在声明和定义中,若 ...

  4. ORACLE收集统计信息

    1.     理解什么是统计信息 优化器统计信息就是一个更加详细描述数据库和数据库对象的集合,这些统计信息被用于查询优化器,让其为每条SQL语句选择最佳的执行计划.优化器统计信息包括: ·       ...

  5. Css3盒子模型-css学习之旅(5)

    主要内容: 盒子模型内边距,外边距,边框,外边距合并 主要包括:margin(外边距)padding(内边距) border(边框)centent(内容) 内边距:padding,paddinglef ...

  6. Android高级控件(五)——如何打造一个企业级应用对话列表,以QQ,微信为例

    Android高级控件(五)--如何打造一个企业级应用对话列表,以QQ,微信为例 看标题这么高大上,实际上,还是运用我么拿到listview去扩展,我们讲什么呢,就是研究一下QQ,微信的这种对话列表, ...

  7. Android热插拔事件处理详解

    一.Android热插拔事件处理流程图 Android热插拔事件处理流程如下图所示: 二.组成 1. NetlinkManager:        全称是NetlinkManager.cpp位于And ...

  8. android ndk编译项目(android-ndk-16r1)

    由于采用android-ndk-16r1版本的ndk来编译 编译的环境之类在这里省略,注意是最后编译的命令如下 Administrator@WIN-AF6P80LVIJ0 ~ $ cd $ANDROI ...

  9. 安全退出app,activoty栈管理

    前言 由于一个同学问到我如何按照一个流程走好之后回到首页,我以前看到过4个解决方案,后来发现有做个记录和总结的必要,就写了这篇博文.(之前看小强也写过一篇,这里通过自身的分析完整的总结一下以下6种方案 ...

  10. C语言之鞍点的查找

    鞍点(Saddle point)在微分方程中,沿着某一方向是稳定的,另一条方向是不稳定的奇点,叫做鞍点.在泛函中,既不是极大值点也不是极小值点的临界点,叫做鞍点.在矩阵中,一个数在所在行中是最大值,在 ...