Java进阶知识点1:白捡的扩展性 - 枚举值也是对象

 

一、背景

枚举经常被大家用来储存一组有限个数的候选常量。比如下面定义了一组常见数据库类型:

public enum DatabaseType {
MYSQL,
ORACLE,
SQLSERVER
}

当围绕这一组常量出现功能上的扩展点时,很多人的做法是为新的功能编写一个新类,新类中依赖该枚举类型。

比如要在界面上显示常见数据库类型的官方名称,可以用如下类实现这一功能:

public class DatabaseNameParser {
public String getDatabaseName(DatabaseType databaseType) {
if (DatabaseType.ORACLE.equals(databaseType)) {
return "Oracle数据库";
} else if (DatabaseType.MYSQL.equals(databaseType)) {
return "MySQL数据库";
} else if (DatabaseType.SQLSERVER.equals(databaseType)) {
return "SQL Server数据库";
} else {
throw new RuntimeException("不支持的数据库类型。");
}
} public static void main(String[] args) {
System.out.println(new DatabaseNameParser().getDatabaseName(DatabaseType.MYSQL));
}
}

大量的if - else语句以及对其他类的过渡依赖(几乎每两行代码就会引用一次DatabaseType对象),让上面这段代码散发出浓浓的坏味道,你可能会想如果这些逻辑可以整合在枚举类中实现就好了。答案是当然可以。

我们只需意识到枚举值不只是常量值,枚举值也是一种对象,他几乎拥有面向对象编程的绝大部分功能 -- 封装、多态、继承(不支持,但一定程度上可以模拟类似效果)。

二、用枚举进行面向对象编程

2.1 封装

上面的例子中,数据库类型的官方名称应该封装到每个数据库类型枚举对象中,作为一个属性字段,每个枚举值提供get方法即可直接通过枚举对象获取其对应的官方名称。如下:

public enum DatabaseType {
MYSQL("MySQL数据库"),
ORACLE("Oracle数据库"),
SQLSERVER("SQL Server数据库"); private final String name; DatabaseType(String name) {
this.name = name;
} public String getName() {
return name;
} public static void main(String[] args) {
System.out.println(MYSQL.getName());
}
}

2.2 多态

上面的例子中,比如新增一个需求,要在界面上提供数据库的连接检测功能。而不同数据库类型的连接检测逻辑是不同的。此时可以为枚举对象声明一个抽象方法,每个枚举值负责实现此方法。如下:

public enum DatabaseType {
MYSQL {
@Override
public boolean detect(String ip, int port) {
//为了简化举例环境,此处不真正实现功能
return false;
}
},
ORACLE {
@Override
public boolean detect(String ip, int port) {
return false;
}
},
SQLSERVER {
@Override
public boolean detect(String ip, int port) {
return false;
}
}; public abstract boolean detect(String ip, int port); public static void main(String[] args) {
System.out.println(MYSQL.detect("127.0.0.1", 3306)); }
}

这一特性也常被称之为:定义常量相关方法。

2.3 继承

枚举对象均是final对象,故不支持枚举对象的继承。但我们可以通过实现接口来模拟枚举类型的子类化,这也是子类化枚举类型的唯一方法。如下:

interface DatabaseType {
public boolean detect(String ip, int port); enum RDBMS implements DatabaseType {
MYSQL {
@Override
public boolean detect(String ip, int port) {
return false;
}
},
ORACLE {
@Override
public boolean detect(String ip, int port) {
return false;
}
},
SQLSERVER {
@Override
public boolean detect(String ip, int port) {
return false;
}
}
} enum NOSQL implements DatabaseType {
REDIS {
@Override
public boolean detect(String ip, int port) {
return false;
}
},
HBASE {
@Override
public boolean detect(String ip, int port) {
return false;
}
},
MONGODB {
@Override
public boolean detect(String ip, int port) {
return false;
}
}
}
}

这就模拟了DatabaseType派生出RDBMS和NOSQL两个抽象子类,每个子类下又派生出各自的具体实现类,这种继承结构。

三、用枚举更好地实现设计模式

当枚举值被赋予对象的强大能力后,再结合枚举类型本身的易用性,被聪明的Java开发者们发现了如下两类好用的模式:

3.1 使用枚举定义单例

—— 这是JDK5之后定义单例最好的方法,没有之一。

说起单例模式,常常让我们想起很多技巧或问题:

1、延迟初始化单例要Double Check,Double Check会失效需要使用volatile修饰符;

2、可以使用静态变量初始化单例;

3、单例对象可能可以被多次反序列化,就违背了单例模式的要求了;

... ...

现在不用纠结这些过时的套路了,就按如下方式定义单例,任何已知的问题都没有,一行代码搞定,而且引用起来更紧凑简洁。

public enum Singleton {
INSTANCE; public void anyFunction() {
} public static void main(String[] args) {
Singleton.INSTANCE.anyFunction();
}
}

3.2 使用枚举实现责任链或状态机

public enum StateMachine {
BEGIN {
@Override
public StateMachine handleAndReturnNextState() {
System.out.println("begin");
return PROCESS1;
}
},
PROCESS1 {
@Override
public StateMachine handleAndReturnNextState() {
System.out.println("process1");
if (true/*some condition*/) {
return PROCESS2;
}
return END;
}
},
PROCESS2 {
@Override
public StateMachine handleAndReturnNextState() {
System.out.println("process2");
return END;
}
},
END {
@Override
public StateMachine handleAndReturnNextState() {
System.out.println("end");
return null;
}
}; public abstract StateMachine handleAndReturnNextState(); public static void main(String[] args) {
StateMachine state = BEGIN;
while (state != null) {
state = state.handleAndReturnNextState();
}
}
}

四、总结

1、枚举不只是常量,也是对象,当与枚举相关的功能遇到扩展需求时,可以考虑在枚举对象上扩展功能点,以获得更加简洁紧凑的代码。

2、枚举是实现单例的最好方式。

3、枚举可以用来实现状态机。

Java进阶知识点: 枚举值的更多相关文章

  1. Java进阶知识点1:白捡的扩展性 - 枚举值也是对象

    一.背景 枚举经常被大家用来储存一组有限个数的候选常量.比如下面定义了一组常见数据库类型: public enum DatabaseType { MYSQL, ORACLE, SQLSERVER } ...

  2. Java进阶知识点:协变与逆变

    一.背景 要搞懂Java中的协办与逆变,不得不从继承说起,如果没有继承,协变与逆变也天然不存在了. 我们知道,在Java的世界中,存在继承机制.比如MochaCoffee类是Coffee类的派生类,那 ...

  3. Java进阶知识点2:看不懂的代码 - 协变与逆变

    一.背景 要搞懂Java中的协办与逆变,不得不从继承说起,如果没有继承,协变与逆变也天然不存在了. 我们知道,在Java的世界中,存在继承机制.比如MochaCoffee类是Coffee类的派生类,那 ...

  4. Java进阶知识点:不要只会写synchronized - JDK十大并发编程组件总结

    一.背景 提到Java中的并发编程,首先想到的便是使用synchronized代码块,保证代码块在并发环境下有序执行,从而避免冲突.如果涉及多线程间通信,可以再在synchronized代码块中使用w ...

  5. Java进阶知识点:并发容器背后的设计理念

    一.背景 容器是Java编程中使用频率很高的组件,但Java默认提供的基本容器(ArrayList,HashMap等)均不是线程安全的.当容器和多线程并发编程相遇时,程序员又该何去何从呢? 通常有两种 ...

  6. Java进阶知识点:不可变对象与并发

    一.String的不可变特性 熟悉Java的朋友都知道,Java中的String有一个很特别的特性,就是你会发现无论你调用String的什么方法,均无法修改this对象的状态.当确实需要修改Strin ...

  7. Java进阶知识点:更优雅地关闭资源 - try-with-resource

    一.背景 我们知道,在Java编程过程中,如果打开了外部资源(文件.数据库连接.网络连接等),我们必须在这些外部资源使用完毕后,手动关闭它们.因为外部资源不由JVM管理,无法享用JVM的垃圾回收机制, ...

  8. Java进阶知识点7:不要只会写synchronized - JDK十大并发编程组件总结

    一.背景 提到Java中的并发编程,首先想到的便是使用synchronized代码块,保证代码块在并发环境下有序执行,从而避免冲突.如果涉及多线程间通信,可以再在synchronized代码块中使用w ...

  9. Java进阶知识点6:并发容器背后的设计理念 - 锁分段、写时复制和弱一致性

    一.背景 容器是Java编程中使用频率很高的组件,但Java默认提供的基本容器(ArrayList,HashMap等)均不是线程安全的.当容器和多线程并发编程相遇时,程序员又该何去何从呢? 通常有两种 ...

随机推荐

  1. JS n秒后自动跳转实例

    <p><a href="<?php echo base_url();?>usercenter/index" id="message" ...

  2. H5基本标签

  3. 一点一点看JDK源码(二)java.util.List

    一点一点看JDK源码(二)java.util.List liuyuhang原创,未经允许进制转载 本文举例使用的是JDK8的API 目录:一点一点看JDK源码(〇) 1.综述 List译为表,一览表, ...

  4. Unity3D Errors And Fix

    Author Error: Shader warning in 'Custom/ShowAnimation': Not enough temporary registers, needs 9 (com ...

  5. 苹果内购小结 - iOS

    此篇针对 iOS 支付进行一次小结,很久没碰这块了,有些方法 Apple 官方也进行了优化,故也将随之进行更新. 首先,code 部分将分为两部分,一部分在 appdelegate 中,另一部分单独封 ...

  6. Vue.js与 ASP.NET Core 服务端渲染功能整合

    http://mgyongyosi.com/2016/Vuejs-server-side-rendering-with-aspnet-core/ 原作者:Mihály Gyöngyösi 译者:oop ...

  7. 结合Bootbox将后台JSON数据填充Form表单

    本文介绍了如何结合Bootbox将后台JSON数据填充到Form表单中,同时也介绍了一些需要使用的知识的学习途径,并附上了参考文档地址与学习网址,对此感兴趣的伙伴可以直接访问学习.为了方便介绍,使用了 ...

  8. WEB中需求分析应该考虑的问题

    一. 针对用户群体要考虑因素 1.用户年龄 2.选择素材 3.网站布局 4.颜色搭配 5. 用户体验及动效 6.功能便捷 用户需求.用户兴趣爱好.性格.职业.教育水平高低.消费观念.PC端和移动端哪一 ...

  9. windows service 2012:搭建FTP服务器

    最近公司将服务器从线上移植到线下每次上传项目都要进入服务器感觉好麻烦, 所以就想搭建一个FTP的站点来管理项目,弄这个之前首先要关闭我们的windows service 2012 服务器的防火墙,只有 ...

  10. python中函数参数的引用方式

    值传递和引用传递时C++中的概念,在python中函数参数的传递是变量指向的对象的物理内存地址!!! python不允许程序员选择采用传值还是传引用.Python参数传递采用的肯定是“传对象引用”的方 ...