samedi 10 juillet 2021

How and when to use Kotlin sealed classes in Java?

Thinking about misusing sealed classes, let's look at the following design.

There are two modules:

  • parser (Kotlin) - is responsible for making instances from String
  • processor (Java) - pumps over raw incoming data into a strongly typed storage (i.e. relational tables)
  1. String comes from external source to processor
  2. processor delegates its recognition to parser
  3. parser makes instances of different types [Banana, Nail, Shoe] based on some rules X
  4. processor persists each instance into an appropriate table based on some rules Y

Is it appropriate to use sealed classes here like this in parser and after that in processor make decisions base on concrete type of each instance?

// 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())
  }
}

I see that something is wrong in this approach, because growing number of Item's subclasses could lead to a design catastrophe, but cannot figure out whether is there a "canonical" use case of sealed classes sufficiently different from this one.

What is the limit of sealed classes applicability, when system designer should prefer something "less typed" like the following:

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
    }
}

Aucun commentaire:

Enregistrer un commentaire