zoukankan      html  css  js  c++  java
  • Java进阶知识点: 枚举值

    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、枚举可以用来实现状态机。

  • 相关阅读:
    C++类的内存结构
    emplace与insert的区别(C++11)
    C/C++宏的奇技淫巧
    编译器对内存的分配
    利用C++实现模块隐藏(R3层断链)
    PCB标准规范
    RTC 总结
    0.96寸OLED显示屏 IIC接口(SSD1306)
    串行通信简介(SPI, IIC, UART)
    人生感悟
  • 原文地址:https://www.cnblogs.com/wfq9330/p/9444338.html
Copyright © 2011-2022 走看看