来源:SpringForAll社区

抽象类是许多面向对象语言的核心特性,例如Java。也许是因为这个原因,他们往往被过度使用,实际上被误用了。在本文中,我们将使用一些模式和反模式的示例来说明何时使用抽象方法,何时不使用。

虽然本文从Java的角度介绍了该主题,但它也与大多数其他面向对象的语言相关,即使那些没有抽象类概念的语言也是如此。为此,让我们快速定义抽象类。如果您已经知道抽象类是什么,请跳过以下部分。

定义抽象类

从技术上讲,抽象类是一个无法直接实例化的类。相反,它被设计为可以 实例化的具体类的扩展 。抽象类可以 - 通常也可以 - 定义一个或多个抽象方法,这些抽象方法本身不包含主体。相反,需要具体的子类来实现抽象方法。

让我们编写一个简单的例子:

  1. public abstract class Base {

  2. public void doSomething() {

  3.    System.out.println("Doing something...")

  4. }

  5. public abstract void doSomethingElse();

  6. }

请注意 doSomething() - 一个非抽象方法 - 实现了一个主体,而 doSomethingElse() - 一个抽象方法 - 没有实现主体。您无法直接实例化Base实例。试下下面的这段代码,你的编译器会报错:

  1. Base  b  =  new  Base();

事实上,你需要Base的一个子类,如下所示:

  1. public class Sub extends Base {

  2.    public abstract void doSomethingElse() {

  3.        System.out.println("Doin' something else!");

  4.    }

  5. }

请注意该doSomethingElse() 方法的实现 。

并非所有面向对象语言都具有抽象类的概念。当然,即使在没有这种支持的语言中,也可以简单地定义一个目的是被子类实现的类,并定义空方法或抛出异常的方法,作为子类重写的“抽象”方法。

瑞士军刀式的Controller

让我们来看看我经常遇到的抽象类的常见滥用。我一直感到内疚; 你可能也有。虽然这种反模式几乎可以出现在代码库中的任何地方,但我倾向于在控制器层的模型 - 视图 - 控制器(MVC)框架中看到它。出于这个原因,我称之为瑞士军刀式的Controller。

反模式很简单:许多子类只与它们位于技术堆栈中的位置相关,从一个公共抽象基类扩展而来。此抽象基类包含任意数量的共享“实用程序”方法。子类从自己的方法中调用实用程序方法。

瑞士军刀式的Controller 通常会这样存在:

  1. 开发人员使用Jersey 等 MVC框架开始构建Web应用程序 。

  2. 由于他们使用MVC框架,他们在UserController 类中使用端点方法支持他们的第一个面向用户的网页 。

  1. 开发人员创建第二个网页,因此将新端点添加到控制器。一位开发人员注意到两个端点执行相同的逻辑 - 比如,在给定一组参数的情况下构造URL - 并将该逻辑移动到其中的单独 constructUrl() 方法中 UserController。

  1. 团队开始研究面向产品的页面。开发人员创建第二个控制器, ProductController以便不将所有方法塞入单个类中。

  2. 开发人员认识到新控制器可能还需要使用该 constructUrl() 方法。与此同时,他们意识到 嘿!这两个类是控制器! 因此,必须与自然相关。因此,他们创建一个抽象 BaseController 类,移动 constructUrl() 到它,并添加 extends BaseController 到的类定义 UserController 和 ProductController。

  1. 重复此过程,直到 BaseController 有十个子类和75个共享方法。

现在,有很多有用的方法可供具体类控制器使用,只需直接调用即可。所以有什么问题?

第一个问题是设计问题。事实上,所有这些不同的控制器彼此无关。它们可能位于我们堆栈的同一层,并可能执行类似的技术角色,但就我们的应用而言,它们用于不同的目的。然而,我们现在将它们锁定在一个相当随意的对象层次结构中。

第二个更实用。当你第一次需要使用 除控制器以外的其他地方的75个共享方法之一时,你会发现它 ,并且你发现自己实例化了一个控制器类来实现它。

  1. String url = new UserController().constructUrl(key, value);

您将创建一系列有用的方法,现在需要控制器实例才能访问。你的第一个想法可能是这样的, 嘿,我可以在控制器中使用静态方法,并像这样使用它:

  1. String url = UserController.constructUrl(key, value);

这不是更好,实际上,更糟糕。即使您没有实例化控制器,您仍然将控制器绑定到其他类。如果您需要在DAO层中使用该方法,该怎么办?您的DAO层应该对您的控制器一无所知。更糟糕的是,在引入一堆静态方法时,您已经使测试和模拟变得更加困难。

在此强调交互流程非常重要。在此示例中,直接调用其中一个具体子类的方法。然后,在某些时候,此方法调用抽象基类中的一个或多个实用程序方法。

实际上,在这个例子中,从来没有需要抽象的基本控制器类。每个共享方法应该已经移动到适当的服务层类(如果它负责业务逻辑)或者实用程序类(如果它提供一般的补充功能)。当然,如上所述,实用程序类仍应是可实例化的,而不是简单地用静态方法填充。

现在,有一组实用方法可以被任何可能需要它们的类重用。此外,我们可以将这些方法分解为相关的组。上图描绘了一个名为的类 UrlUtility, 它可能只包含与创建和解析URL相关的方法。我们也可以使用与字符串操作相关的方法创建一个类,另一个使用与我们的应用程序当前经过身份验证的用户相关的方法等。

另请注意,此方法也非常适合组合而不是继承的原则。

继承和抽象类是一个强大的构造。因此,许多例子都被滥用,瑞士军刀式的Controller就是一个常见的例子。实际上,我发现抽象类的大多数典型用法都可以被认为是反模式,抽象类有一些很好的用法。

模板方法

话虽如此,让我们看一下模板方法 设计模式描述的最佳用途之一 。我发现模板方法模式是一个鲜为人知的 - 但更有用 - 的设计模式。

您可以阅读有关模式如何在许多地方工作的信息。它最初是在 Gang of Four Design Patterns 一书中描述的; 现在可以在网上找到许多描述 。让我们看看它与抽象类的关系以及如何在现实世界中应用它。

为了保持一致性,我将描述使用MVC控制器的另一个场景。在我们的示例中,我们有一个应用程序,其中存在一些不同类型的用户(现在,我们将定义两个: employee 和 admin)。在创建任一类型的新用户时,根据我们创建的用户类型,存在细微差别。例如,分配角色需要以不同方式处理。除此之外,过程是一样的。此外,虽然我们预计新用户类型不会爆炸,但我们会不时要求我们支持新类型的用户。

在这种情况下,我们 将 要开始为我们的控制器的抽象基类。由于无论用户类型如何,创建新用户的整个过程都是相同的,因此我们可以在基类中定义该过程一次。任何不同的细节都将降级为具体子类将实现的抽象方法:

  1. public abstract class BaseUserController {

  2.    // ... variables, other methods, etc

  3.    @POST

  4.    @Path("/user")

  5.    public UserDto createUser(UserInfo userInfo) {

  6.        UserDto u = userMapper.map(userInfo);

  7.        u.setCreatedDate(Instant.now());

  8.        u.setValidationCode(validationUtil.generatedCode());

  9.        setRoles(u);  // to be implemented in our subclasses

  10.        userDao.save(u);

  11.        mailerUtil.sendInitialEmail(u);

  12.        return u;

  13.    }

  14.    protected abstract void setRoles(UserDto u);

  15. }

然后,我们只需要为每个用户类型扩展一次BaseUserController :

  1. @Path("employee")

  2. public class EmployeeUserController extends BaseUserController {

  3.    protected void setRoles(UserDto u) {

  4.        u.addRole(Role.employee);

  5.    }

  6. }

  7. @Path("admin")

  8. public class AdminUserController extends BaseUserController {

  9.    protected void setRoles(UserDto u) {

  10.        u.addRole(Role.admin);

  11.        if (u.hasSuperUserAccess()) {

  12.            u.addRole(Role.superUser);

  13.        }

  14.    }

  15. }

每当我们需要支持新的用户类型时,我们只需创建一个新的子类 BaseUserController 并setRoles() 适当地实现该 方法。让我们将这里的互动与我们与瑞士军队控制员看到的互动进行对比。

使用模板方法方法,我们看到调用者(在这种情况下,MVC框架本身 - 响应Web请求 - 是调用者)调用抽象基类中的方法,而不是具体的子类。这一点在我们已经使子setRoles() 方法中实现的方法受到保护的事实中表明了 这一点。换句话说,大部分工作在抽象基类中定义一次。只有那些需要专业化的工作部分才能创建具体的实现。

经验法则

我喜欢将软件工程模式简化为简单的经验法则。当然,每条规则都有例外。但是,它能帮助我快速判断使用特定的设计是否是朝着正确的方向发展。

事实证明,在考虑使用抽象类时,有一个很好的经验法则。问问自己:类的调用者是否会调用在抽象基类中实现的方法,或者在具体子类中实现的方法?

如果它是前者,那么您打算只公开在抽象类中实现的方法- 可能性是您创建了一组良好的,可维护的类。

如果是后者,调用者将调用子类中实现的方法,而子类又调用抽象类中的方法。瑞士军队的反模式正在形成的可能性很大。

希望这些可以帮到你!请在下面的评论中告诉我们你的想法。

原文链接:https://dzone.com/articles/when-to-use-java-abstract-classes

作者:Dave Taubler

译者:xuli

号外:最近整理了之前编写的一系列内容做成了PDF,关注我并回复相应口令获取:

001 领取:《Spring Boot基础教程》

- 002 领取:《Spring Cloud基础教程》

更多内容陆续奉上,敬请期待 

- END -

 近期热文:

  • 别看不起分区表:我要为你点个赞

  • Spring Cloud Greenwich 正式发布

  • 用认知和人性来做最棒的程序员

  • Gitlab-CI持续集成的完整实践

  • 在前后端分离的路上承受了多少痛?

  • 你真的会高效的在GitHub上搜索开源项目吗?

  • 中台是个什么鬼?

看完,赶紧点个“好看”鸭

点鸭点鸭

↓↓↓↓

何时(不)使用Java抽象类相关推荐

  1. Java接口和Java抽象类

    Java接口和Java抽象类有太多相似的地方,又有太多特别的地方,究竟在什么地方,才是它们的最佳位置呢?把它们比较一下,你就可以发现了. 1.Java接口和Java抽象类最大的一个区别,就在于Java ...

  2. Java接口和Java抽象类的认识

    在没有好好地研习面向对象设计的设计模式之前,我对Java接口和Java抽象类的认识还是很模糊,很不可理解. 刚学Java语言时,就很难理解为什么要有接口这个概念,虽说是可以实现所谓的多继承,可一个只有 ...

  3. java 抽象类 模板_Java抽象类的构造模板模式用法示例

    搜索热词 本文实例讲述了Java抽象类的构造模板模式用法.分享给大家供大家参考,具体如下: 一 点睛 模板模式的一些简单规则. 抽象父类可以只定义需要使用的某些方法,而不能实现的部分抽象成抽象方法,留 ...

  4. IEnumeratorTItem和IEnumerator Java 抽象类和普通类、接口的区别——看完你就顿悟了...

    IEnumerable 其原型至少可以说有15年历史,或者更长,它是通过 IEnumerator 来定义的,而后者中使用装箱的 object 方式来定义,也就是弱类型的.弱类型不但会有性能问题,最主要 ...

  5. java 抽象接口类,Java接口(interface)和Java抽象类(abstract class)的区别(详诉版)

    1.概述 一个软件设计的好坏,我想很大程度上取决于它的整体架构,而这个整体架构其实就是你对整个宏观商业业务的抽象框架, 当代表业务逻辑的高层抽象层结构合理时,你底层的具体实现需要考虑的就仅仅是一些算法 ...

  6. java定义一个door的类_再探Java抽象类与接口的设计理念差异

    原文:http://blog.csdn.net/sunboard/article/details/3831823 1.概述 一个软件设计的好坏,我想很大程度上取决于它的整体架构,而这个整体架构其实就是 ...

  7. 没有体现JAVA接口功能_深入浅出分析Java抽象类和接口【功能,定义,用法,区别】...

    本文实例讲述了Java抽象类和接口.分享给大家供大家参考,具体如下: 对于OOP编程来说,抽象是它一大特征之一.在Java中,可以通过两种形式来体现OOP的抽象:抽象类和接口. 这两者有相似之处也有很 ...

  8. java抽象类和模板模式_测试抽象类和模板方法模式

    java抽象类和模板模式 摘自Wikipedia,"模板方法定义了算法的程序框架. 子类可以覆盖一个或多个算法步骤,以允许不同的行为,同时确保仍然遵循总体算法." 通常,此模式由两 ...

  9. java 抽象类_什么是final?Java抽象类又是什么?抽象类能使用 final 修饰吗?

    第十三题 讲解人:张小龙     8.23 邮箱:2304940425@qq.com 一.什么是final? final是java中的一个关键字,可以用来修饰变量.方法和类.用关键词final修饰的域 ...

  10. Java 抽象类和抽象方法

    Java 抽象类和抽象方法 抽象类是它的所有子类的公共属性的集合,是包含一个或多个抽象方法的类. 使用抽象类可以充分利用这些公共属性来提高开发和维护程序的效率. 抽象方法必须为public或prote ...

最新文章

  1. 浅谈如何防范电池事故
  2. 广西2021各校高考成绩查询入口,2021年广西高考成绩排名查询系统,广西高考位次排名查询...
  3. python try exception类_Python异常-try、raise语句及自定义异常类
  4. Java——IO(打印流)
  5. Jmeter生成自动化和性能多维度测试报告(性能指标和监听器图表)
  6. Kafka—topic的查询和创建
  7. 微信浏览器中唤醒App
  8. Mac 系统 Arduino IDE 找不到开发板端口的解决方法
  9. 通过拖拽改变图片大小
  10. ADB安装电视应用市场
  11. 『R语言Python』使用logging、log4r写日志
  12. IDA PRO:庆祝成立创新 30 周年
  13. 什么是嵌入式开发?初学者必看嵌入式学习课程
  14. 输出一句英文话语中的单词数量
  15. 中国石油大学《化工热力学》第一阶段在线作业
  16. VB.net参数化界面调用ANSYS求解的详细步骤及错误提示的解决办法
  17. VMware虚拟机安装Mac OS X Lion正式版教程
  18. shell脚本批量修改文件名
  19. XGBoost原理及目标函数推导详解
  20. OOD : A Self-supervised Framework for Unsupervised Deep Outlier Detection e3笔记

热门文章

  1. linux 自动启动shell 和 init概述
  2. linux shell 缺少 ps 命令
  3. centos7离线安装metasploit
  4. linux “大脏牛”漏洞分析(CVE-2017-1000405)
  5. UUID介绍与生成方法
  6. libssh2 和 libssh 比较
  7. Win7 OBJECT_HEADER之TypeIndex解析
  8. Eclipse MyEclipse下常用快捷键介绍
  9. 浅析linux内核中的idr机制
  10. Linux下使用Eclipse开发C/C++程序