Java面向对象设计最佳实践 - 枚举设计
对枚举类型印象大多来自于C 语言,在 C 语言中,枚举类型是一个 HardCode (硬编码)类型,其使用价值并不大。因此,在 Java5 之前,枚举是被抛弃的。然而 Java5 以后的发现版本开始对枚举进行支持,枚举的引入给 Java 世界带来了争议。
笔者比较赞同引入枚举,作为一门通用的静态编程语言,应该是海纳百川的(因此笔者赞成闭包进入Java7 ),多途径实现功能。
如果您不了解枚举的用法,建议参考笔者以前网络资源,了解基本的用法。地址为: http://mercyblitz.blog.ccidnet.com/blog-htm-do-showone-uid-45914-type-blog-itemid-189396.html
枚举是一种特殊的(受限制的)类,它具有以下特点:
- 可列性
- 常量性
- 强类型
- 类的特性
留下一个问题-怎么利用这些枚举特点,更好为设计服务呢?根据这些特点,下面向大家分别得介绍设计技巧。
一、 可列性
在设计中,必须搞清楚枚举 使用场景 。 枚举内部成员都是可列的,或者说固定的。这种硬编码的形式,看上去令人觉得不自在,不过这就是枚举。如果需要动态(不可列)的成员话,请不好使用枚举。
JDK提供不少良好的可列性设计枚举。比如时间单位 java.util.concurrent.TimeUnit 和线程状态枚举 java.lang.Thread.State 。
假设有一个游戏难度枚举,有三种难度NORMAL,MEDIUM,HARD
- /**
- *游戏中的难度枚举:NORMAL,MEDIUM,HARD
- *
- *@authormercyblitz
- */
- publicenumDifficulty{
- NORMAL,MEDIUM,HARD//注意:枚举成员命名,请使用英文大写形式
- }
/** * 游戏中的难度枚举:NORMAL , MEDIUM, HARD * * @author mercyblitz */ public enum Difficulty { NORMAL, MEDIUM, HARD //注意:枚举成员命名,请使用英文大写形式 }
如果要添加其他成员,只能通过硬编码的方法添加到枚举类。回到枚举Difficulty,低版本的必定会影响二进制兼容性。对于静态语言来说,是无法避免的,不能认为是枚举的短处。
二、 常量性
之所以定性为常量性,是因为枚举是不能改变,怎么证明成员其不变呢?利用上面的Difficulty枚举为例,一段简单的代码得到其原型,如下:
- packageorg.mercy.design.enumeration;
- importjava.lang.reflect.Field;
- /**
- *Difficulty元信息
- *@authormercy
- */
- publicclassMetaDifficulty{
- publicstaticvoidmain(String[]args){
- MetaDifficultyinstance=newMetaDifficulty();
- //利用反射连接其成员的特性
- Class<Difficulty>classDifficulty=Difficulty.class;
- for(Fieldfield:classDifficulty.getFields()){
- instance.printFieldSignature(field);
- System.out.println();
- }
- }
- /**
- *打印字段签名(signature)
- *
- *@paramfield
- */
- privatevoidprintFieldSignature(Fieldfield){
- StringBuildermessage=newStringBuilder("字段")
- .append(field.getName())
- .append("的签名:")
- .append(field.toString())
- .append("/t");
- System.out.print(message);
- }
package org.mercy.design.enumeration; import java.lang.reflect.Field; /** * Difficulty 元信息 * @author mercy */ public class MetaDifficulty { public static void main(String[] args) { MetaDifficulty instance = new MetaDifficulty(); // 利用反射连接其成员的特性 Class<Difficulty> classDifficulty = Difficulty.class; for (Field field : classDifficulty.getFields()) { instance.printFieldSignature(field); System.out.println(); } } /** * 打印字段 签名(signature) * * @param field */ private void printFieldSignature(Field field) { StringBuilder message = new StringBuilder("字段 ") .append(field.getName()) .append(" 的签名:") .append(field.toString()) .append("/t"); System.out.print(message); } }
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.lang.Enum<E>的子类吗?
- *
- *@paramklass
- *@return
- */
- privatebooleanisEnum(Class<?>klass){
- Class<?>superClass=klass.getSuperclass();
- while(true){//递归查找
- if(superClass!=null){
- if(Enum.class.equals(superClass))
- returntrue;
- }else{
- break;
- }
- superClass=superClass.getSuperclass();
- }
- returnfalse;
- }
/** * 指定的类是枚举java.lang.Enum<E>的子类吗? * * @param klass * @return */ private boolean isEnum(Class<?> klass) { Class<?> superClass = klass.getSuperclass(); while (true) { // 递归查找 if (superClass != null) { if (Enum.class.equals(superClass)) return true; } else { break; } superClass = superClass.getSuperclass(); } return false; }
客户端代码调用: instance.isEnum(Difficulty. class ) ; 结果返回true ,那么证明了 Difficulty 枚举继承了java.lang.Enum<E> 抽象基类。
那么java.lang.Enum<E> 有没有覆盖 clone 方法呢?查看一下源代码:
- /**
- *ThrowsCloneNotSupportedException.Thisguaranteesthatenums
- *arenevercloned,whichisnecessarytopreservetheir"singleton"
- *status.
- *
- *@return(neverreturns)
- */
- protectedfinalObjectclone()throwsCloneNotSupportedException{
- thrownewCloneNotSupportedException();
- }
/** * Throws CloneNotSupportedException. This guarantees that enums * are never cloned, which is necessary to preserve their "singleton" * status. * * @return (never returns) */ protected final Object clone() throws CloneNotSupportedException{ throw new CloneNotSupportedException(); }
很明显,java.lang.Enum<E> 基类 final 定义了 clone 方法,即枚举不支持克隆,并且 Javadoc 提到要保持单体性。那么这也是面向对象设计的原则之一 - 对于保持单态性的对象而言,尽可能不支持或者不暴露clone 方法。
三、 强类型
前面的两个特性,在前Java5时代,利用了常量字段也可以完成需要。比如可以这么设计Difficulty类的字段,
public static final int NORMAL = 1; public static final int MEDIUM = 2; public static final int HARD = 3;
这么设计有一个缺点-弱类型,因为三个字段都是int原生型。
比如有一个方法用于设置游戏难度,定义如下:
public void setDifficulty(int difficulty)
利用int类型作为参数,可能会有问题-如果参数在NORMAL,MEDIUM,HARD之外int数是可以接受的。利用规约的方法可以避免这个问题,比如设计范围检查:
- /**
- *设置游戏难度
- *@paramdifficulty难度数
- *@throwsIllegalArgumentException
- *如果参数不是NORMAL、MEDIUM和Hard其中一个,那么报出IllegalArgumentException
- */
- publicvoidsetDifficulty(intdifficulty)
- throwsIllegalArgumentException
/** * 设置游戏难度 * @param difficulty 难度数 * @throws IllegalArgumentException * 如果参数不是 NORMAL、MEDIUM和Hard其中一个,那么报出IllegalArgumentException */ public void setDifficulty(int difficulty) throws IllegalArgumentException
在可能实现方法中,通过三次成员比较,这样比较笨拙和憋足。
如果您在使用Java5以前版本的话,作者提供一个较好的实现方法:
- packageorg.mercy.design.enumeration;
- importjava.util.Collections;
- importjava.util.HashSet;
- importjava.util.Set;
- /**
- *DifficultyJDK1.4实现 Difficulty枚举
- *@authormercy
- */
- publicclassDifficulty14{
- //枚举字段
- publicstaticfinalintNORMAL=1;
- publicstaticfinalintMEDIUM=2;
- publicstaticfinalintHARD=3;
- privatefinalstaticSetdifficulties;
- static{
- //Hash提供快速查找
- HashSetdifficultySet=newHashSet();
- difficultySet.add(Integer.valueOf(NORMAL));
- difficultySet.add(Integer.valueOf(MEDIUM));
- difficultySet.add(Integer.valueOf(HARD));
- //利用不变的对象是一个好的设计实践
- difficulties=Collections.unmodifiableSet(difficultySet);
- }
- /**
- *设置游戏难度
- *@paramdifficulty难度数
- *@throwsIllegalArgumentException
- *如果参数不是NORMAL、MEDIUM和Hard其中一个,那么报出IllegalArgumentException
- */
- publicvoidsetDifficulty(intdifficulty)
- throwsIllegalArgumentException{
- if(!difficulties.contains(Integer.valueOf(difficulty)))
- thrownewIllegalArgumentException("参数错误");
- //设置难度...
- }
- }
package org.mercy.design.enumeration; import java.util.Collections; import java.util.HashSet; import java.util.Set; /** * Difficulty JDK1.4实现 Difficulty枚举 * @author mercy */ public class Difficulty14 { //枚举字段 public static final int NORMAL = 1; public static final int MEDIUM = 2; public static final int HARD = 3; private final static Set difficulties; static { // Hash 提供快速查找 HashSet difficultySet = new HashSet(); difficultySet.add(Integer.valueOf(NORMAL)); difficultySet.add(Integer.valueOf(MEDIUM)); difficultySet.add(Integer.valueOf(HARD)); //利用不变的对象是一个好的设计实践 difficulties= Collections.unmodifiableSet(difficultySet); } /** * 设置游戏难度 * @param difficulty 难度数 * @throws IllegalArgumentException * 如果参数不是NORMAL、MEDIUM和Hard其中一个,那么报出IllegalArgumentException */ public void setDifficulty(int difficulty) throws IllegalArgumentException { if(!difficulties.contains(Integer.valueOf(difficulty))) throw new IllegalArgumentException("参数错误"); //设置难度... } }
在上面的代码中,尽管提供了范围检查,不过参数范围还是巨大(可以说是无数),并且是运行时检查。因为 setDifficulty 的参数是 int的,客户端调用时候,编译器可以接受 int 以及范围更小的 short 、 byte 等类型。那么违反了 一个良好的实践 -在面向对象设计中,类型范围最好在编译时确定而非运行时。
另一个良好的面向对象实践 -利用对象类型,而不是原生型(如果编程语言支持的话)。 那么,如果使用java.lang.Integer 取代 int 类型,并且 Integer 是 final 类,没有必要担心多态的情况下,不就可以提供强类型吗?的确,提供了强类型约束,并且更好的锁定类型范围(因为是 final 的)。可是, Integer 的范围在一定程度上,认为是无限的,同时不支持 swtich 语句(仅支持 int 、 short 、 byte 和 Java5 枚举类型)。因此 Integer 还是不合适的。
枚举的常量性和可列性,在Difficulty 场景中尤其适合。
四、 类的特性
已知每个枚举都继承了java.lang.Enun<E>基类,其既有常量性,同时也有类的特点。尽管它是一种被限制的类,比如name和ordinal字段状态都是有JVM处理的。不过开发人员可以充分的利用类的特点,作出优美的设计。
枚举既然也是类,那么也遵循类的设计。通过扩张 Difficulty 类,面向对象的方式来设计枚举。
1. 封装设计
如果有一个需求 - Difficulty 持久化,把其存入 d ifficult ies数据库表中,并且提供一个唯一的 id 整型值。面向对象的封装,枚举也适用。示例如下:
- publicenumDifficulty{
- //注意:枚举成员命名,请使用英文大写形式
- NORMAL(1),MEDIUM(2),HARD(3);
- /**
- *final修饰字段是一个良好的实践。
- */
- finalprivateintid;
- Difficulty(finalintid){
- this.id=id;
- }
- /**
- *获得ID
- *@return
- */
- publicintgetId(){
- returnid;
- }
- }
public enum Difficulty { // 注意:枚举成员命名,请使用英文大写形式 NORMAL(1), MEDIUM(2), HARD(3); /** * final修饰字段是一个良好的实践。 */ final private int id; Difficulty(final int id){ this.id=id; } /** * 获得ID * @return */ public int getId() { return id; } }
通过调用getId 方法可以获取枚举成员的 ID 值。
2. 抽象设计
在不同的游戏难度级别中,不同任务的难度值不同(大多数情况是通过值来表示,而非枚举对象本身)。以Difficulty为例,定义一个抽象的方法,计算难度值。
- /**
- *获取不同任务的难度值
- *
- *@parammission
- *@return
- *@throwsIllegalArgumentException
- *如果<code>mission</code>为负数时。
- */
- publicabstractintgetValue(intmission)throwsIllegalArgumentException;
/** * 获取不同任务的难度值 * * @param mission * @return * @throws IllegalArgumentException * 如果<code>mission</code> 为负数时。 */ public abstract int getValue(int mission) throws IllegalArgumentException;
3. 多态设计
Difficulty枚举中定义抽象方法getValue,那么其子类必须实现这个方法。不过枚举不能被继承,也不能继承其他类,除了默认额java.lang.Enum<E>类以外。因此枚举是一个final的版本,不能实现抽象方法?
枚举的特殊在此,枚举虽然不能显示地利用extends关键字继承,不过它的每个成员都是自己的子类。那么以Difficulty为例,其类层次关系为:java.lang.Enum<E>->Difficulty->HARD。这么看来,每个枚举成员相当于定了一个final的类内置类。
回到Difficulty枚举,实现抽象方法如下:
- NORMAL(1){
- @Override
- publicintgetValue(intmission)
- throwsIllegalArgumentException{
- returnmission+this.getId();
- }
- },
- MEDIUM(2){
- @Override
- publicintgetValue(intmission)
- throwsIllegalArgumentException{
- returnmission*this.getId();
- }
- },
- HARD(3){
- @Override
- publicintgetValue(intmission)
- throwsIllegalArgumentException{
- returnmission<<this.getId();
- }
- };
NORMAL(1) { @Override public int getValue(int mission) throws IllegalArgumentException { return mission + this.getId(); } }, MEDIUM(2) { @Override public int getValue(int mission) throws IllegalArgumentException { return mission * this.getId(); } }, HARD(3) { @Override public int getValue(int mission) throws IllegalArgumentException { return mission << this.getId(); } };
4. 继承设计
在上述实现中,可以观察到一点,每个实现getValue 的方法都利用的 getId() 方法。那么再次说明了枚举类本身也是一个特殊基类,可以定义模板方法。
5. 串行化设计
在某些设计中,需要把枚举通过串行化。回到 Difficulty14 的示例中,三个成员变量都是常量,那么 static的变量是不可能被串行化的。如果去掉 static 修饰,那么语义将会被改变。而枚举不同,所有自定义枚举都是 java.lang.Enum<E> 的子类,因此所有的枚举都是可序列化的( java.lang.Enum<E> 实现了java.io.Serializable ) 。这也是枚举相对于常量的优势之一。
实现中可以提供类似这样的方法:
- privatevoidreadObject(ObjectInputStreamin)throwsIOException
- privatevoidreadObjectNoData()throwsObjectStreamException
private void readObject(ObjectInputStream in) throws IOException private void readObjectNoData() throws ObjectStreamException
总之,枚举也可以像类那样,实现面向对象的特点,不过值得一提的是, 枚举中应该保持尽量可能少的状态,职责单一的设计。
总结:Java中的枚举本质上也是类,只是类结构上比较特殊,Java5之前叫做实例受控类型安全的类型,Java5把这种类的设计语法化了,并且功能上更强大.
有一点是枚举类型并没有final修饰符修饰,如果有final修饰符修饰了,那么将禁止它在任何地方的子类化,不管是类外还是类内部,
枚举类型的构造方法受private修饰,这样可以在类内部子类化,以提供不同枚举值的行为上的差别.
相关推荐
5. **设计模式**:面向对象设计模式是解决特定问题的最佳实践,如单例模式、工厂模式、观察者模式等,这些模式在Java编程中广泛应用。 印旻的PPT将详细解释这些概念,并通过实例和练习帮助学习者理解和应用。通过...
通过这个电子课件,学习者不仅可以掌握Java语言的面向对象编程技术,还能了解到实际项目开发中的一些最佳实践。程细柱的讲解方式通常深入浅出,结合实例,有助于学习者快速理解和掌握知识要点。
15. 设计模式:设计模式是解决常见软件设计问题的最佳实践。如工厂模式、单例模式、观察者模式、装饰器模式等,都是面向对象编程中的重要工具。 以上就是Java面向对象的基础和高级知识点,通过深入学习和实践,...
15. 设计模式:设计模式是解决常见编程问题的最佳实践,例如工厂模式、单例模式、观察者模式等,它们在Java编程中广泛使用。 通过学习这些Java面向对象编程的核心概念,学习者可以更好地理解和应用Java语言,从而...
根据提供的文件内容,“Java面向对象程序设计语言课程设计指导书.pdf”这份文档可能是一份关于Java编程语言的实践课程设计教材。内容中提及了很多关于Java编程的重要知识点和概念,虽然内容显得有些混乱,部分文字...
Java面向对象编程是一种强大的软件开发方法,它基于“对象”的概念,使得代码更易于理解和维护。本指南将深入探讨Java中的面向对象编程(OOP)原理及其应用。 1. **对象与类** - **对象**是现实世界中的实体在程序...
【Java工程师成神之路】是一本专为Java工程师打造的技术成长指南,由...通过这本书,读者不仅能学习到Java语言本身,还能理解面向对象的设计思想,掌握实际开发中的最佳实践,从而在Java工程师的成长道路上不断迈进。
深入学习这些源代码,可以帮助读者掌握Java的高级特性和最佳实践,如模块系统、反射、注解、枚举、内省等。此外,还可以通过阅读和分析这些代码来提升对设计模式和面向对象设计原则的理解。总之,《JAVA核心技术-卷...
通过阅读和实践这些源码,开发者不仅可以巩固Java的基础知识,还能深入理解Java的高级特性和最佳实践,从而提高编程技能和解决问题的能力。书中的每个章节都对应着特定的代码示例,这些示例涵盖了各种实际场景,对于...
- **描述**:静态方法通常不是面向对象的最佳实践。 - **应用**:除非有充分理由,否则应避免在类中大量使用静态方法。 #### 原则二十一:考虑性能优化 - **描述**:在设计阶段就应考虑性能问题。 - **应用**:评估...
此外,书中还会深入探讨如何在Java中实现这些模式,包括如何利用Java的面向对象特性,如继承、封装和多态,以及如何利用接口、匿名内部类等特性来优化设计模式的应用。例如,Java的枚举类型可以方便地实现单例模式,...
《Java语言程序设计基础篇》是一本全面介绍Java编程基础知识的教材,由知名...通过学习这个教材,读者不仅可以掌握Java语言的基础知识,还能了解到实际开发中的常见技术和最佳实践,为今后的Java编程生涯打下坚实基础。
最后,第14章"Java高级应用拓展"可能涉及到一些进阶主题,如反射、动态代理、枚举、集合框架的高级特性和异常处理的最佳实践等。这些内容能够帮助开发者更高效地利用Java平台的功能。 总的来说,这个压缩包提供了...
5. **第8章 泛型-枚举-内部类**:泛型提供了类型安全,枚举用于定义常量集合,而内部类则是Java中实现复杂设计模式的重要工具。 6. **第9章 异常**:讲解了如何处理运行时错误和异常,包括try-catch-finally语句块...
多态性是Java面向对象的精髓,允许子类对象替换父类对象,实现不同子类对同一方法的不同实现。 3. **异常处理** - Java中的异常处理机制是通过try-catch-finally语句块来实现的,有助于编写健壮的代码,处理运行时...
在Java这样的面向对象编程语言中,设计模式更是不可或缺的工具,可以帮助开发者构建可扩展、可维护且高效的应用程序。《设计模式——Java语言版本》这本书深入探讨了如何在Java环境下应用这些模式。 在“设计模式--...
- **面向对象编程基础**:书中首先介绍了Java的基础知识,包括面向对象编程的基本概念。 - **语法结构**:详细解释了Java语言的语法结构,帮助读者快速掌握语言基础。 #### 2. **核心技术** - **核心类库**:...
总的来说,理解和掌握设计模式对于Java开发人员至关重要,它们提供了面向对象编程的最佳实践,使得软件开发更具有一致性、可读性和可维护性。《设计模式 - 可复用面向对象软件的基础》高清版是学习这些知识的宝贵...
1. 面向对象:Java支持类、对象、继承、封装和多态等面向对象的特性,使得代码更易于维护和扩展。 2. 自动内存管理:通过垃圾回收机制,Java自动处理内存分配和释放,避免了常见的内存泄漏问题。 3. 异常处理:Java...
这本书涵盖了Java语言的核心概念、语法、工具和最佳实践,旨在帮助读者快速掌握并深入理解Java编程。 1. **Java语言基础** - 类与对象:Java是面向对象的语言,以类为构造单元,通过对象来封装数据和行为。 - ...