2

考虑到误用密封类,我们来看看下面的设计。

有两个模块:

  • parser(Kotlin) - 负责从 String 创建实例
  • processor(Java) - 将原始传入数据泵入强类型存储(即关系表)
  1. 字符串来自外部源processor
  2. processor将其认可委托给parser
  3. parser[Banana, Nail, Shoe]根据一些创建不同类型的实例rules X
  4. processor根据某些情况将每个实例持久保存到适当的表中rules Y

在基于每个实例的具体类型做出决策时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
    }
}
4

2 回答 2

1

如果您选择使用密封类,则每当引入新的子类型时,您都会自行扩展层次结构。鉴于您有特定于子类型的规则和持久性,我不确定您可以做多少来避免这种情况。

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");
    }
}


于 2021-07-10T15:16:40.400 回答
1

您可以使用访问者模式为 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 代码可以创建访问者而不是使用 进行自己的instanceof检查else,如果你添加一个变体,你添加一个方法并提醒你访客需要新方法。

于 2021-07-10T13:20:47.187 回答