`
BradyZhu
  • 浏览: 248247 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类
最新评论

Java面向对象设计最佳实践 - 枚举设计

 
阅读更多

对枚举类型印象大多来自于C 语言,在 C 语言中,枚举类型是一个 HardCode (硬编码)类型,其使用价值并不大。因此,在 Java5 之前,枚举是被抛弃的。然而 Java5 以后的发现版本开始对枚举进行支持,枚举的引入给 Java 世界带来了争议。

笔者比较赞同引入枚举,作为一门通用的静态编程语言,应该是海纳百川的(因此笔者赞成闭包进入Java7 ),多途径实现功能。

如果您不了解枚举的用法,建议参考笔者以前网络资源,了解基本的用法。地址为: http://mercyblitz.blog.ccidnet.com/blog-htm-do-showone-uid-45914-type-blog-itemid-189396.html

枚举是一种特殊的(受限制的)类,它具有以下特点:

  1. 可列性
  2. 常量性
  3. 强类型
  4. 类的特性

留下一个问题-怎么利用这些枚举特点,更好为设计服务呢?根据这些特点,下面向大家分别得介绍设计技巧。

一、 可列性

在设计中,必须搞清楚枚举 使用场景 枚举内部成员都是可列的,或者说固定的。这种硬编码的形式,看上去令人觉得不自在,不过这就是枚举。如果需要动态(不可列)的成员话,请不好使用枚举。

JDK提供不少良好的可列性设计枚举。比如时间单位 java.util.concurrent.TimeUnit 和线程状态枚举 java.lang.Thread.State

假设有一个游戏难度枚举,有三种难度NORMAL,MEDIUM,HARD

Java代码 复制代码
  1. /**
  2. *游戏中的难度枚举:NORMAL,MEDIUM,HARD
  3. *
  4. *@authormercyblitz
  5. */
  6. publicenumDifficulty{
  7. NORMAL,MEDIUM,HARD//注意:枚举成员命名,请使用英文大写形式
  8. }

如果要添加其他成员,只能通过硬编码的方法添加到枚举类。回到枚举Difficulty,低版本的必定会影响二进制兼容性。对于静态语言来说,是无法避免的,不能认为是枚举的短处。

二、 常量性

之所以定性为常量性,是因为枚举是不能改变,怎么证明成员其不变呢?利用上面的Difficulty枚举为例,一段简单的代码得到其原型,如下:

Java代码 复制代码
  1. packageorg.mercy.design.enumeration;
  2. importjava.lang.reflect.Field;
  3. /**
  4. *Difficulty元信息
  5. *@authormercy
  6. */
  7. publicclassMetaDifficulty{
  8. publicstaticvoidmain(String[]args){
  9. MetaDifficultyinstance=newMetaDifficulty();
  10. //利用反射连接其成员的特性
  11. Class<Difficulty>classDifficulty=Difficulty.class;
  12. for(Fieldfield:classDifficulty.getFields()){
  13. instance.printFieldSignature(field);
  14. System.out.println();
  15. }
  16. }
  17. /**
  18. *打印字段签名(signature)
  19. *
  20. *@paramfield
  21. */
  22. privatevoidprintFieldSignature(Fieldfield){
  23. StringBuildermessage=newStringBuilder("字段")
  24. .append(field.getName())
  25. .append("的签名:")
  26. .append(field.toString())
  27. .append("/t");
  28. System.out.print(message);
  29. }

printFieldSignature 方法输出枚举 Difficulty的字段,其结果为:

字段NORMAL的签名:publicstaticfinalorg.mercy.design.enumeration.Difficultyorg.mercy.design.enumeration.Difficulty.NORMAL

字段MEDIUM的签名:publicstaticfinalorg.mercy.design.enumeration.Difficultyorg.mercy.design.enumeration.Difficulty.MEDIUM

字段HARD的签名:publicstaticfinalorg.mercy.design.enumeration.Difficultyorg.mercy.design.enumeration.Difficulty.HARD

这个结果得出了两个结论,其一,每个枚举成员是枚举的字段。其二,每个成员都被 publicstaticfinal。凡是 staticfinal 变量都是 Java 中的“常量”,其保存在常量池中。根据其常量性和命名规则,建议全大写命名每个枚举的成员(前面提到)。

常量性提供了数据一致性,不必担心被被其他地方修改,同时保证了线程安全。因此在设计过程中,不必担心线程安全问题。

枚举类型是常量,那么在判断是可以使用== 符号来做比较。可是如果枚举成员能够克隆 (Clone) 的话 , 那么 == 比较会失效,从而一致性不能得到保证。如果按照类的定义方法,考虑枚举的话,那么枚举类是集成了 java.lang.Object 类,因此,它继承了 protectedjava.lang.Objectclone() 方法,也就是说支持 clone ,虽然需要通过反射的手段去调用。 Java 语言规范提到,每个枚举继承了 java.lang.Enum<E> 抽象基类。

提供一段测试代码来验明真伪:

Java代码 复制代码
  1. /**
  2. *指定的类是枚举java.lang.Enum<E>的子类吗?
  3. *
  4. *@paramklass
  5. *@return
  6. */
  7. privatebooleanisEnum(Class<?>klass){
  8. Class<?>superClass=klass.getSuperclass();
  9. while(true){//递归查找
  10. if(superClass!=null){
  11. if(Enum.class.equals(superClass))
  12. returntrue;
  13. }else{
  14. break;
  15. }
  16. superClass=superClass.getSuperclass();
  17. }
  18. returnfalse;
  19. }

客户端代码调用: instance.isEnum(Difficulty. class ) ; 结果返回true ,那么证明了 Difficulty 枚举继承了java.lang.Enum<E> 抽象基类。

那么java.lang.Enum<E> 有没有覆盖 clone 方法呢?查看一下源代码:

Java代码 复制代码
  1. /**
  2. *ThrowsCloneNotSupportedException.Thisguaranteesthatenums
  3. *arenevercloned,whichisnecessarytopreservetheir"singleton"
  4. *status.
  5. *
  6. *@return(neverreturns)
  7. */
  8. protectedfinalObjectclone()throwsCloneNotSupportedException{
  9. thrownewCloneNotSupportedException();
  10. }

很明显,java.lang.Enum<E> 基类 final 定义了 clone 方法,即枚举不支持克隆,并且 Javadoc 提到要保持单体性。那么这也是面向对象设计的原则之一 - 对于保持单态性的对象而言,尽可能不支持或者不暴露clone 方法。

三、 强类型

前面的两个特性,在前Java5时代,利用了常量字段也可以完成需要。比如可以这么设计Difficulty类的字段,

Java代码 复制代码
  1. publicstaticfinalintNORMAL=1;
  2. publicstaticfinalintMEDIUM=2;
  3. publicstaticfinalintHARD=3;

这么设计有一个缺点-弱类型,因为三个字段都是int原生型。

比如有一个方法用于设置游戏难度,定义如下:

Java代码 复制代码
  1. publicvoidsetDifficulty(intdifficulty)

利用int类型作为参数,可能会有问题-如果参数在NORMAL,MEDIUM,HARD之外int数是可以接受的。利用规约的方法可以避免这个问题,比如设计范围检查:

Java代码 复制代码
  1. /**
  2. *设置游戏难度
  3. *@paramdifficulty难度数
  4. *@throwsIllegalArgumentException
  5. *如果参数不是NORMAL、MEDIUM和Hard其中一个,那么报出IllegalArgumentException
  6. */
  7. publicvoidsetDifficulty(intdifficulty)
  8. throwsIllegalArgumentException

在可能实现方法中,通过三次成员比较,这样比较笨拙和憋足。

如果您在使用Java5以前版本的话,作者提供一个较好的实现方法:

Java代码 复制代码
  1. packageorg.mercy.design.enumeration;
  2. importjava.util.Collections;
  3. importjava.util.HashSet;
  4. importjava.util.Set;
  5. /**
  6. *DifficultyJDK1.4实现 Difficulty枚举
  7. *@authormercy
  8. */
  9. publicclassDifficulty14{
  10. //枚举字段
  11. publicstaticfinalintNORMAL=1;
  12. publicstaticfinalintMEDIUM=2;
  13. publicstaticfinalintHARD=3;
  14. privatefinalstaticSetdifficulties;
  15. static{
  16. //Hash提供快速查找
  17. HashSetdifficultySet=newHashSet();
  18. difficultySet.add(Integer.valueOf(NORMAL));
  19. difficultySet.add(Integer.valueOf(MEDIUM));
  20. difficultySet.add(Integer.valueOf(HARD));
  21. //利用不变的对象是一个好的设计实践
  22. difficulties=Collections.unmodifiableSet(difficultySet);
  23. }
  24. /**
  25. *设置游戏难度
  26. *@paramdifficulty难度数
  27. *@throwsIllegalArgumentException
  28. *如果参数不是NORMAL、MEDIUM和Hard其中一个,那么报出IllegalArgumentException
  29. */
  30. publicvoidsetDifficulty(intdifficulty)
  31. throwsIllegalArgumentException{
  32. if(!difficulties.contains(Integer.valueOf(difficulty)))
  33. thrownewIllegalArgumentException("参数错误");
  34. //设置难度...
  35. }
  36. }

在上面的代码中,尽管提供了范围检查,不过参数范围还是巨大(可以说是无数),并且是运行时检查。因为 setDifficulty 的参数是 int的,客户端调用时候,编译器可以接受 int 以及范围更小的 shortbyte 等类型。那么违反了 一个良好的实践 -在面向对象设计中,类型范围最好在编译时确定而非运行时。

另一个良好的面向对象实践 -利用对象类型,而不是原生型(如果编程语言支持的话)。 那么,如果使用java.lang.Integer 取代 int 类型,并且 Integerfinal 类,没有必要担心多态的情况下,不就可以提供强类型吗?的确,提供了强类型约束,并且更好的锁定类型范围(因为是 final 的)。可是, Integer 的范围在一定程度上,认为是无限的,同时不支持 swtich 语句(仅支持 intshortbyteJava5 枚举类型)。因此 Integer 还是不合适的。

枚举的常量性和可列性,在Difficulty 场景中尤其适合。

四、 类的特性

已知每个枚举都继承了java.lang.Enun<E>基类,其既有常量性,同时也有类的特点。尽管它是一种被限制的类,比如name和ordinal字段状态都是有JVM处理的。不过开发人员可以充分的利用类的特点,作出优美的设计。

枚举既然也是类,那么也遵循类的设计。通过扩张 Difficulty 类,面向对象的方式来设计枚举。

1. 封装设计

如果有一个需求 - Difficulty 持久化,把其存入 d ifficult ies数据库表中,并且提供一个唯一的 id 整型值。面向对象的封装,枚举也适用。示例如下:

Java代码 复制代码
  1. publicenumDifficulty{
  2. //注意:枚举成员命名,请使用英文大写形式
  3. NORMAL(1),MEDIUM(2),HARD(3);
  4. /**
  5. *final修饰字段是一个良好的实践。
  6. */
  7. finalprivateintid;
  8. Difficulty(finalintid){
  9. this.id=id;
  10. }
  11. /**
  12. *获得ID
  13. *@return
  14. */
  15. publicintgetId(){
  16. returnid;
  17. }
  18. }

通过调用getId 方法可以获取枚举成员的 ID 值。

2. 抽象设计

在不同的游戏难度级别中,不同任务的难度值不同(大多数情况是通过值来表示,而非枚举对象本身)。以Difficulty为例,定义一个抽象的方法,计算难度值。

Java代码 复制代码
  1. /**
  2. *获取不同任务的难度值
  3. *
  4. *@parammission
  5. *@return
  6. *@throwsIllegalArgumentException
  7. *如果<code>mission</code>为负数时。
  8. */
  9. publicabstractintgetValue(intmission)throwsIllegalArgumentException;

3. 多态设计

Difficulty枚举中定义抽象方法getValue,那么其子类必须实现这个方法。不过枚举不能被继承,也不能继承其他类,除了默认额java.lang.Enum<E>类以外。因此枚举是一个final的版本,不能实现抽象方法?

枚举的特殊在此,枚举虽然不能显示地利用extends关键字继承,不过它的每个成员都是自己的子类。那么以Difficulty为例,其类层次关系为:java.lang.Enum<E>->Difficulty->HARD。这么看来,每个枚举成员相当于定了一个final的类内置类。

回到Difficulty枚举,实现抽象方法如下:

Java代码 复制代码
  1. NORMAL(1){
  2. @Override
  3. publicintgetValue(intmission)
  4. throwsIllegalArgumentException{
  5. returnmission+this.getId();
  6. }
  7. },
  8. MEDIUM(2){
  9. @Override
  10. publicintgetValue(intmission)
  11. throwsIllegalArgumentException{
  12. returnmission*this.getId();
  13. }
  14. },
  15. HARD(3){
  16. @Override
  17. publicintgetValue(intmission)
  18. throwsIllegalArgumentException{
  19. returnmission<<this.getId();
  20. }
  21. };

4. 继承设计

在上述实现中,可以观察到一点,每个实现getValue 的方法都利用的 getId() 方法。那么再次说明了枚举类本身也是一个特殊基类,可以定义模板方法。

5. 串行化设计

在某些设计中,需要把枚举通过串行化。回到 Difficulty14 的示例中,三个成员变量都是常量,那么 static的变量是不可能被串行化的。如果去掉 static 修饰,那么语义将会被改变。而枚举不同,所有自定义枚举都是 java.lang.Enum<E> 的子类,因此所有的枚举都是可序列化的( java.lang.Enum<E> 实现了java.io.Serializable。这也是枚举相对于常量的优势之一。

实现中可以提供类似这样的方法:

Java代码 复制代码
  1. privatevoidreadObject(ObjectInputStreamin)throwsIOException
  2. privatevoidreadObjectNoData()throwsObjectStreamException

总之,枚举也可以像类那样,实现面向对象的特点,不过值得一提的是, 枚举中应该保持尽量可能少的状态,职责单一的设计。

总结:Java中的枚举本质上也是类,只是类结构上比较特殊,Java5之前叫做实例受控类型安全的类型,Java5把这种类的设计语法化了,并且功能上更强大.

有一点是枚举类型并没有final修饰符修饰,如果有final修饰符修饰了,那么将禁止它在任何地方的子类化,不管是类外还是类内部,
枚举类型的构造方法受private修饰,这样可以在类内部子类化,以提供不同枚举值的行为上的差别.

分享到:
评论

相关推荐

    Java.In.A.Nutshell 7th Covers Java11.pdf

    Chock充满了演示如何充分利用现代Java API和开发最佳实践的示例,这本经过全面修订的书籍包含有关Java Concurrency Utilities的新资料。 本书的第一部分提供了Java编程语言和Java平台的核心运行时方面的快节奏,...

    【Java面试+Java学习指南】 一份涵盖大部分Java程序员所需要掌握的核心知识

    Java注解和最佳实践 JavaIO流 多线程 深入理解内部类 javac和javap Java8新特性终极指南 序列化和反序列化 继承、封装、多态的实现原理 容器 Java集合类总结 Java集合详解1:一文读懂ArrayList,Vector与Stack使用...

    Java工程师面试复习指南

    Java注解和最佳实践 JavaIO流 多线程 深入理解内部类 javac和javap Java8新特性终极指南 序列化和反序列化 继承封装多态的实现原理 集合类 Java集合类总结 Java集合详解:一文读懂ArrayList,Vector与Stack使用方法和...

    廖雪峰 Java 教程.doc

    廖雪峰 Java 教程 Java教程 Java快速入门 Java简介 安装JDK 第一个Java程序 ...Java代码助手 ...Java程序基础 ...最佳实践 单元测试 编写JUnit测试 使用Fixture 异常测试 条件测试 参数化测试

    asp.net知识库

    翻译MSDN文章 —— 泛型FAQ:最佳实践 Visual C# 3.0 新特性概览 C# 2.0会给我们带来什么 泛型技巧系列:如何提供类型参数之间的转换 C#2.0 - Object Pool 简单实现 Attributes in C# 手痒痒,也来个c# 2.0 object ...

    JavaScript权威指南(第6版)(附源码)

    本书第6版涵盖了HTML5和ECMAScript5,很多章节完全重写,增加了当今Web开发的最佳实践的内容,新增的章节包括jQuery、服务器端JavaScript、图形编程以及JavaScript式的面向对象。本书不仅适合初学者系统学习,也适合...

    JavaScript权威指南(第6版)(中文版)

    本书第6版涵盖了HTML5和ECMAScript 5,很多章节完全重写,增加了当今Web开发的最佳实践的内容,新增的章节包括jQuery、服务器端JavaScript、图形编程以及 JavaScript式的面向对象。本书不仅适合初学者系统学习,也...

    JavaScript权威指南(第6版)

    本书第6版涵盖了HTML5和ECMAScript 5,很多章节完全重写,增加了当今Web开发的最佳实践的内容,新增的章节包括jQuery、服务器端JavaScript、图形编程以及 JavaScript式的面向对象。本书不仅适合初学者系统学习,也...

    JavaScript权威指南(第6版)

    本书第6版涵盖了 html5 和 ecmascript 5,很多章节完全重写,增加了当今 web 开发的最佳实践的内容,新增的章节包括 jQuery、服务器端 JavaScript、图形编程以及JavaScript式的面向对象。本书不仅适合初学者系统学习...

    JavaScript权威指南(第6版)中文文字版

    本书第6版涵盖了 html5 和 ecmascript 5,很多章节完全重写,增加了当今 web 开发的最佳实践的内容,新增的章节包括 jQuery、服务器端 JavaScript、图形编程以及JavaScript式的面向对象。本书不仅适合初学者系统学习...

    JavaScript权威指南(第6版) 中文版

    本书第6版涵盖了 html5 和 ecmascript 5,很多章节完全重写,增加了当今 web 开发的最佳实践的内容,新增的章节包括 jQuery、服务器端 JavaScript、图形编程以及JavaScript式的面向对象。本书不仅适合初学者系统学习...

    spring.net中文手册在线版

    Spring.NET AOP最佳实践 第二十七章. .NET Remoting快速入门 27.1.简介 27.2.Remoting实例程序 27.3.实现 27.4.运行程序 27.5.Remoting Schema 27.6.参考资源 第二十八章. Web框架快速入门 28.1.简介 第二十九章. ...

Global site tag (gtag.js) - Google Analytics