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. 使用Kubespray部署Kubernetes集群

    转载请标明出处: http://blog.csdn.net/forezp/article/details/82730382 本文出自方志朋的博客 Kubespray是Google开源的一个部署生产级别 ...

  2. ArrayList的去重问题

    面试被问及arraylist的去重问题,现将自己想的两种解决方案写在下面 /** * Description: * ClassName:Uniq * Package:com.syd.interview ...

  3. 实现一个shell程序

    实现一个自己的shell程序,这个程序有这些功能:解释执行命令,支持输入输出重定向,支持管道,后台运行 程序.当运行该程序后,它支持以下的命令格式: 1.单个命令,如:ls.2.带l到多个参数的命令, ...

  4. Tomcat性能监控

    Tomcat性能监控工具很多,这里介绍两种1.JMeter 2.probe,使用这两种工具都需要在tomcat的安装目录/conf/tomcat-users.xml添加 <tomcat-user ...

  5. Python入门 —— 05时间日期处理小结

    此文多涉及基础,如果想要深入理解则到文末,有提供链接 涉及对象 1. datetime 2. timestamp 3. time tuple 4. string 5. date - datetime基 ...

  6. Python支付接口汇总大全(包含微信、支付宝等)

    微信接口 wzhifuSDK- 由微信支付SDK 官方PHP Demo移植而来,v3.37下载地址 weixin_pay- 是一个简单的微信支付的接口 weixin_pay- 微信支付接口(V3.3. ...

  7. 树莓派3B+学习笔记:2、更改显示分辨率

    1.打开终端,输入 sudo raspi-config 选择第7行: 2.选择第5行: 3.选择一个自己习惯的分辨率(我选择1024X768),确定后重启,VNC会自动连接: 4.更改分辨率完成,方便 ...

  8. python3 练习题100例 (一)

    断断续续的学了很久的python,有很多又忘记了.从今天开始用实例再进行一次学习,并记录.本人小白一个,请大家多多指教. #!/usr/bin/env python3 # -*- coding: ut ...

  9. ruby语言里的self理解

    关键的一句话:关键看谁调用self,self就属于谁 有3种情况: 1.在class或module的定义中,self代表这个class或者这个module对象,代码如下: class S puts ' ...

  10. C语言小程序-基于链表的学生信息管理

    程序支持增加.查询.删除.存盘和读取操作 一 程序定义和函数声明 头文件studentsys.h定义如下 /* student management system by list */ #ifndef ...