问题描述
考虑到滥用密封类,让我们看看下面的设计。
有两个模块:
-
parser
(Kotlin) - 负责从 String 创建实例 -
processor
(Java) - 将原始传入数据泵入强类型存储(即关系表)
- 字符串来自外部源到
processor
-
processor
将其认可委托给parser
-
parser
基于某些[Banana,Nail,Shoe]
生成不同类型的实例 -
processor
根据某些rules Y
将每个实例持久化到适当的表中
rules X
在 parser
中像这样使用密封类,然后在 processor
中根据每个实例的具体类型做出决定是否合适?
// parser module exposes Item and its subclasses
sealed interface Item {
class Banana(/*state 1*/) : Item
class Nail(/*state 2*/) : Item
class Shoe(/*state 3*/) : Item
}
fun parse(value: String,rule: ParseRule): Item {
return when (true) {
rule.canParseBanana(value) -> rule.makeBananaFrom(value)
rule.canParseNail(value) -> rule.makeNailFrom(value)
rule.canParseShoe(value) -> rule.makeShoeFrom(value)
else -> throw RuntimeException("cannot parse")
}
}
// processor module makes decisions based on class
void process(String value){
Item item = parser.parse(value);
if (item instance of Item.Banana){
persistBanana((Item.Banana) item)
} else if ( ... )
// etc
} else {
throw new RuntimeException("UnkNown subclass of Item : " + item.getClass())
}
}
我发现这种方法有问题,因为越来越多的 Item 子类可能会导致设计灾难,但无法确定是否存在与此完全不同的密封类的“规范”用例。
>密封类适用性的限制是什么,当系统设计者应该喜欢像下面这样的“少类型”的东西时:
class Item{
Object marker; // String or Enum
Map<String,Object> attributes;
}
// basically it is the same,but without dancing with types
void process(String value){
Item item = parser.parse(value);
if ("BANANA".equals(item.marker)){
persistBanana(item.attributes)
} else if (...){
// etc
}
}
解决方法
您可以使用访问者模式为 Java 提供 when
..is
风格的方法。
// Kotlin
abstract class ItemVisitor<OUT> {
operator fun invoke(item: Item) = when (item) {
is Banana -> visitBanana(item)
is Shoe -> visitShoe(item)
is Nail -> visitNail(item)
}
abstract fun visitBanana(item: Banana): OUT
abstract fun visitShoe(item: Shoe): OUT
abstract fun visitNail(item: Nail): OUT
}
因为 Item 是密封的,所以在 else
中您不需要 when
情况,然后 Java 代码可以创建访问者而不是自己做 {{1} } 使用 instanceof
进行检查,如果您添加了一个变体,则您添加了一个方法并得到提醒,您的访问者的 Java 实现需要新方法。
如果您选择使用密封类,则每当引入新的子类型时,您都会自行扩展层次结构。鉴于您有特定于子类型的规则和持久性,我不确定您可以做多少来避免这种情况。
话虽如此,在您已经确定了项目类型之后再做instanceOf
是一种耻辱。 下面的代码或多或少是访问者模式(这是有争议的,请参阅评论)。我认为需要将 Java 和 Kotlin 分离。
// Kotlin
sealed interface Item {
class Banana(/*state 1*/) : Item
class Nail(/*state 2*/) : Item
class Shoe(/*state 3*/) : Item
}
class Parser(val persistor: Persistor,val rule: ParseRule) {
fun parse(value: String): Item =
when (true) {
rule.canParseBanana(value) -> rule.makeBananaFrom(value).also { persistor.persist(it) }
rule.canParseNail(value) -> rule.makeNailFrom(value).also { persistor.persist(it) }
rule.canParseShoe(value) -> rule.makeShoeFrom(value).also { persistor.persist(it) }
else -> throw RuntimeException("cannot parse: $value")
}
}
class ParseRule {
fun canParseBanana(value: String): Boolean = ...
fun makeBananaFrom(value: String): Item.Banana = ...
...
}
// Java
public class Persistor {
void persist(Item.Banana item) {
System.out.println("persisting " + item);
}
void persist(Item.Shoe item) {
System.out.println("persisting " + item);
}
void persist(Item.Nail item) {
System.out.println("persisting " + item);
}
}
public class Processor {
private final Parser parser;
public Processor(Parser parser) {
this.parser = parser;
}
void process(String value) {
parser.parse(value);
}
public static void main(String[] args) {
Parser parser = new Parser(new Persistor(),new ParseRule());
Processor p = new Processor(parser);
p.process("banana");
p.process("nail");
p.process("zap");
}
}