如何创建具有不同行为和关联值的灵活枚举?
Kotlin密封班的力量
枚举非常适合将具有相似行为的对象组合在一起。 它们也是有效的,因为将仅创建其中一个实例。 但是,很难为行为稍有不同的类实现枚举。 让我举例说明。
示例:统计计算器
假设我们要编写一个统计计算器,以计算数学统计信息,例如用于值列表。 SUM
, COUNT
, AVG
, QUANTILES
。 首先定义一个接口。
现在,我们可以轻松地将不同的统计信息表示为枚举,如下所示:
我们可以这样使用它:
足够容易,但是一旦要支持QUANTILE
stat变得困难,该QUANTILE
需要存储百分位以在枚举实例中进行计算。 例如。 QUANTILE(90).calculate(values)
应该计算值的百分之九十 。
实现具有不同行为的枚举的问题
- 由于Java枚举必须具有相同的字段,因此我们必须向所有枚举添加百分位数字段,尽管它与
AVG
,SUM
和COUNT
无关。 - 我们不知道提前静态创建Quantile实例的百分比值。 因此,它们不再是枚举。
- 一旦将它们转换为常规类,我们将失去它的默认单例行为。
我们基本上希望将简单的统计数据AVG
, SUM
, COUNT
作为单例,但是将QUANTILE
作为给定百分位数值的动态创建的类。 我们必须编写很多样板代码来支持这种行为上的差异,他是做到这一点的一种方法。
太多的工作。
科特林的救援营
Kotlin的强大的密封类可轻松解决此类用例。
首先让我们了解什么是密封类:
密封类用于表示受限类层次结构。 从某种意义上讲,它们是枚举类的扩展。
密封的类可以具有子类,但是所有子类必须在同一文件中声明。 密封类的子类可以具有可以包含状态的多个实例。
您可以在密封类内部或外部声明子类,但始终必须在同一文件中声明子类。
密封类本身是抽象的,不能直接实例化,可以有抽象成员。
密封的类不允许具有非私有的构造函数(默认情况下,它们的构造函数是私有的)。
现在,我们了解了密封的类,让我们看看如何使用密封的类来实现StatsCalculator
让我们为每个Stats这样创建一个密封的Stats类和子类。
Kotlin支持对象声明以在单行中创建Singleton。 语言本身支持定义我们想要的:
- 使用对象声明将简单的统计数据
AVG
,COUNT
,SUM
设为单例。 -
QUANTILE
被定义为用于存储百分位值的常规类。 - 所有这些类在密封的基类
Stats
分组在一起。
现在,可以以简洁明了的方式实现计算方法。
该代码不仅简洁,表达的时候帮助的美丽我们避免在运行时的潜力错误。
因为当使用表达式直接返回值时,如果我们添加一个新的统计信息,例如说MAX
扩展了Stats
却忘记了更新计算方法,编译器将抛出错误。
“何时”表达式必须详尽无遗,请添加必要的“最大”分支或“其他”分支。
对于密封类,这是可能的,因为所有子类都在同一个文件中声明,因此编译器将知道所有可能的值。
Sealed类什么时候有用?
密封类的想法不是新的。 密封的类使我们可以轻松地处理代数数据类型 。 其他语言也提供类似功能,例如
因此,我们可以使用Kotlin的密封类来解决那些需要代数数据类型的问题。
如果您不习惯使用Kotlin,则可以查看Spotify的数据枚举 ,以普通的旧Java进行。
2018年12月3日更新:
代码示例已更新为使用Doculet 。