2

I'm trying to create a class that uses its own state to operate on the state of an external object that it holds a reference to. The external object can be of class A or B, which are similar, but not controlled by the author. So a sealed class is created to access their common attributes, per this earlier answer from @SimY4.

// *** DOES NOT COMPILE ***
class A {   // foreign class whose structure is not modifiable
  val prop get()= "some string made the Class-A way"
}
class B {   // foreign class whose structure is not modifiable
  val prop get()= "some string made the Class-B way"
}
data class ABTool (val obj:AB, val i:Int, val j:Int) {
  // class that manipulates i and j and uses them to do
  // things with AB's "common" attributes through the sealed class AB
  sealed class AB {   // substitute for a common interface
    abstract val prop: String
    abstract val addmagic: String
    data class BoxA(val o:A) : AB() {
      override val prop get()= o.prop
      override val addmagic get() = prop + this@???.magic  // HOW TO REFERENCE?
    }
    data class BoxB(val o:B) : AB() {
      override val prop get()= o.prop
      override val addmagic get() = this@???.magic + prop  // HOW TO REFERENCE?
    }
  }
  val magic get()= "magic: ${i*j}"
}

The problem now is that I've figured out I can't operate on the external object in the way I want, because a sealed class can't refer to its outer class members. Is there a better way to make this work, even if using a different approach (other than sealed class), while:

  • not changing foreign classes A or B;
  • respecting that A and B (and many others in the real case) are similar, so I'm trying to write one tool that calculates and adds magic to A and B with the same code base; and
  • noting that although the ABTool tools are the same, the way they are applied to add magic is slightly different in A vs. B, just as the to access the conceptually common elements of A and B may be different.

Any thoughts on this or a similar workaround? Maybe a more functional approach that I haven't conceived yet?

sirksel
  • 747
  • 6
  • 19

2 Answers2

2

If ABTool being a sealed class is something you can give up, then here's a solution:

  1. Replace sealed with inner abstract at the ABTool declaration;
  2. Mark BoxA and BoxB as inner as well;

data class ABTool(val obj: AB, val i: Int, val j: Int) {
    inner abstract class AB {
        abstract val prop: String
        abstract val addmagic: String

        inner class BoxA(val o: A) : AB() {
            override val prop get() = o.prop
            override val addmagic get() = prop + magic
        }

        inner class BoxB(val o: B) : AB() {
            override val prop get() = o.prop
            override val addmagic get() = magic + prop
        }
    }

    val magic get() = "magic: ${i * j}"
}

(alternatively, instead of marking AB as inner, move BoxA and BoxB out of it to the scope of ABTool)

hotkey
  • 140,743
  • 39
  • 371
  • 326
  • I'm confused as to how to instantiate. Given `val a=A()`, I've tried `ABTool.AB.BoxA(a)` . I think `BoxA` wants me to instantiate `AB`, maybe because `BoxA` is inner? `ABTool.AB().BoxA(a)` doesn't work, maybe because `AB` is abstract? `ABTool().AB().BoxA(a)` doesn't work, maybe because ABTool needs constructors?... But constructing it is what I'm trying to do in the first place. Am I missing that this needs to be in a companion class or something? **Can you help me understand how to instantiate it?** Thanks. – sirksel Nov 19 '17 at 15:04
  • 1
    Indeed, it's complicated. Do you need `val obj: AB` in the primary constructor of `ABTool`? When it's there, you need to create an `AB` (`BoxA` or `BoxB`) before you create `ABTool`, but since they are `inner`, they require an `ABTool` instance to exist at that time. – hotkey Nov 19 '17 at 17:28
  • Forgetting the outer data class for a moment, here's an even simpler part I can't grasp... If constructors of `BoxA` and `BoxB` (because they are `inner`) depend upon an *instance* of AB to be seen, and an object instance of `AB` type can only be created by making a `BoxA` or `BoxB` (since `AB` is abstract and `BoxA` and `BoxB` are the only classes that have `AB` as a superclass) ... can they ever be instantiated? (I keep thinking I'm missing a critical link in my understanding of inner classes...) – sirksel Nov 20 '17 at 03:21
  • Since inner class within abstract class might be something that could be a useful answer on its own, [I started a new question](https://stackoverflow.com/questions/47384785/inner-class-within-its-abstract-superclass-in-kotlin). – sirksel Nov 20 '17 at 03:48
2

An alternative would be to add an ABTool field to AB:

sealed class AB(val tool: ABTool) {
  abstract val prop: String
  abstract val addmagic: String
  data class BoxA(val o:A, tool: ABTool) : AB(tool) {
    override val prop get()= o.prop
    override val addmagic get() = prop + tool.magic
  }
  data class BoxB(val o:B, tool: ABTool) : AB(tool) {
    override val prop get()= o.prop
    override val addmagic get() = tool.magic + prop
  }
}

and pass this when creating it from ABTool. That's just what inner really does, after all.

In this specific case the field happens to be unused in AB itself and so you can remove it from there and make it val in BoxA and BoxB.

Alexey Romanov
  • 167,066
  • 35
  • 309
  • 487