7

I'm a beginner of Kotlin, I use Code A to define a complex class MDetail, and use Code B to create a object aMDetail1, it can work.

But the data construction is too bad to expand, if I include a new data class such as ScreenDef in MDetail just like Code C, all old code have to be rewriten.

Is there a good data construction for a complex class which include some classes ? I hope to the data construction can be expansion easily in future!

Code A

data class BluetoothDef(val Status:Boolean=false)
data class WiFiDef(val Name:String, val Status:Boolean=false)

data class MDetail (
        val _id: Long,
        val bluetooth: BluetoothDef,
        val wiFi:WiFiDef
)

Code B

var mBluetoothDef1= BluetoothDef()
var mWiFiDef1= WiFiHelper(this).getWiFiDefFromSystem()
var aMDetail1= MDetail(7L,mBluetoothDef1,mWiFiDef1)

Code C

data class BluetoothDef(val Status:Boolean=false)
data class WiFiDef(val Name:String, val Status:Boolean=false)
data class ScreenDef(val Name:String, val size:Long)
... 

data class MDetail (
        val _id: Long,
        val bluetooth: BluetoothDef,
        val wiFi:WiFiDef
        val aScreenDef:ScreenDef        
        ...
)

The following code is based what s1m0nw1 said, I think it's easy to extend for future. Thanks!

Is there other more better way?

Version 1 Code

interface DeviceDef

data class BluetoothDef(val Status: Boolean = false) : DeviceDef
data class WiFiDef(val Name: String, val Status: Boolean = false) : DeviceDef
data class ScreenDef(val Name: String, val size: Long) : DeviceDef

class MDetail(val _id: Long, val devices: MutableList<DeviceDef>) {
    inline fun <reified T> getDevice(): T {
        return devices.filterIsInstance(T::class.java).first()
    }
}

class UIMain : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.layout_main)

        val btD = BluetoothDef(true)
        val wfD = WiFiDef("MyWifi")
        val xSc = ScreenDef("MyScreen", 1)
        val m = MDetail(7L, mutableListOf(btD, wfD, xSc))


        handleBluetoothDef(m.getDevice<BluetoothDef>())
        handleWiFiDef(m.getDevice<WiFiDef>())
        handleScreenDef(m.getDevice<ScreenDef>())
    }

    fun handleBluetoothDef(mBluetoothDef:BluetoothDef){ }    
    fun handleWiFiDef(mWiFiDef:WiFiDef){ }    
    fun handleScreenDef(mScreenDef:ScreenDef){ }
}

Version 2 Code (Expansion)

interface DeviceDef

data class BluetoothDef(val Status: Boolean = false) : DeviceDef
data class WiFiDef(val Name: String, val Status: Boolean = false) : DeviceDef
data class ScreenDef(val Name: String, val size: Long) : DeviceDef

data class TimeLine(val Name: String): DeviceDef  //Extend

class MDetail(val _id: Long, val devices: MutableList<DeviceDef>) {
    inline fun <reified T> getDevice(): T {
        return devices.filterIsInstance(T::class.java).first()
    }
}

class UIMain : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.layout_main)

        val btD = BluetoothDef(true)
        val wfD = WiFiDef("MyWifi")
        val xSc = ScreenDef("MyScreen", 1)

        val aTe = TimeLine("MyTimeline")  //Extend

        val m = MDetail(7L, mutableListOf(btD, wfD, xSc,aTe)) //Modified


        handleBluetoothDef(m.getDevice<BluetoothDef>())
        handleWiFiDef(m.getDevice<WiFiDef>())
        handleScreenDef(m.getDevice<ScreenDef>())

        handleTimeLine(m.getDevice<TimeLine>()) //Extend
    }

    fun handleBluetoothDef(mBluetoothDef:BluetoothDef){}    
    fun handleWiFiDef(mWiFiDef:WiFiDef){ }    
    fun handleScreenDef(mScreenDef:ScreenDef){ }           
    fun handleTimeLine(mTimeLine:TimeLine){}  //Extend

Help

I have to replace interface with open class because I can't unserialize MDetail object from json string GSON.

but the fun inline fun <reified T> getDevice(): T{ } can't return correct result, how can I modify? Thanks!

open class DeviceDef

data class BluetoothDef(val status:Boolean=false):  DeviceDef()
data class WiFiDef(val name:String, val status:Boolean=false) : DeviceDef()

data class MDetail(val _id: Long, val deviceList: MutableList<DeviceDef>)
{
    inline fun <reified T> getDevice(): T {        
        return deviceList.filterIsInstance(T::class.java).first()
    }
}
s1m0nw1
  • 76,759
  • 17
  • 167
  • 196
HelloCW
  • 843
  • 22
  • 125
  • 310
  • If you want compile time checks, you have no choice but to modify the class to handle this kind of "expansion". But it's not clear what you mean by "all old code has to be rewritten". – Oliver Charlesworth Dec 30 '17 at 14:47
  • No need to change it to `open class`, `interface` works with following solution: https://stackoverflow.com/a/48051495/8073652 – s1m0nw1 Jan 01 '18 at 16:53

3 Answers3

4

I suggest to do the following: Your units (Wifi, Bluetooth etc.) should be abstracted by an interface (at least as a marker), which could be named DeviceDef.

interface DeviceDef
data class BluetoothDef(val Status: Boolean = false) : DeviceDef
data class WiFiDef(val Name: String, val Status: Boolean = false) : DeviceDef
data class ScreenDef(val Name: String, val size: Long) : DeviceDef 

The MDetail class can be instantiated with a variable list of these devices, so that no modifications are needed when new devices, such as ScreenDef, are added:

class MDetail(val _id: Long, val devices: List<DeviceDef>)

Inside MDetail, you can provide a method for filtering these devices:

class MDetail(val _id: Long, val devices: List<DeviceDef>) {

    inline fun <reified T> getDevice(): T {
        return devices.filterIsInstance(T::class.java).first()
    }
}

Now, it's pretty simple to work with the WifiDef for example:

fun main(args: Array<String>) {
    val btD = BluetoothDef()
    val wfD = WiFiDef("")
    val m = MDetail(7L, listOf(btD, wfD, ScreenDef("", 1)))
    println(m.getDevice<WiFiDef>())
}

I hope this helps. If not, it might be necessary that you provide more details on how MDetail is supposed to work.

s1m0nw1
  • 76,759
  • 17
  • 167
  • 196
  • 1
    Thanks! Do you mean that `interface DeviceDef` is blank in your way? – HelloCW Dec 26 '17 at 11:31
  • 1
    And more could you explain the `fun getDevice().. ` for me? – HelloCW Dec 26 '17 at 11:33
  • 1
    I don't know how you unbox the object of BluetoothDef, WiFiDef, ScreenDef from `val devices: MutableList`, could you give me a details code? Thanks! – HelloCW Dec 26 '17 at 11:40
  • As you can see in `getDevice`, the objects of BluetoothDef e.g. are searched in `devices` by class, in Java you would do this with `instanceof`, that’s what is happening in the method – s1m0nw1 Dec 26 '17 at 12:28
  • 1
    Thanks! Does it mean that `m.getDevice()' will return the instance of `class WiFiDef` ? – HelloCW Dec 27 '17 at 01:02
  • Exactly that’s what is happening – s1m0nw1 Dec 27 '17 at 03:30
  • 1
    If you want to remove duplicates use [`MutableSet`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/-mutable-set/index.html) instead of `MutableList` for devices. – Ircover Dec 28 '17 at 14:31
  • But I think it would be much better to validate incoming instances instead of just ignoring duplicates – s1m0nw1 Dec 30 '17 at 13:31
  • I modified the quesion, Would you please see Help section? I have to replace DeviceDef interface with open class because I can't unserialize MDetail object from json string using GSON. But inline fun getDevice(): T{ } can't return result, how can I modify? Thanks! – HelloCW Jan 01 '18 at 14:00
  • 1
    And more, could you have a look at https://stackoverflow.com/questions/48050487/how-to-register-an-instancecreator-with-gson-in-kotlin ? – HelloCW Jan 01 '18 at 14:31
  • I'll have a look, please note that I changed `MutableList` to `List`, this was not intended in the first place – s1m0nw1 Jan 01 '18 at 15:04
  • Thanks! Why do you want to change MutableList to List ? I can get the correct result when I use MutableList ! – HelloCW Jan 02 '18 at 03:23
  • Thank you very much. But I can't understand why you want to change MutableList to List . I can get the correct result when I use MutableList – HelloCW Jan 04 '18 at 00:22
  • Could you tell me why you want to change MutableList to List . I can get the correct result when I use MutableList ? Thanks! – HelloCW Jan 09 '18 at 00:19
3

I'm not a Kotlin expert but I would suggest adding default values in the constructor of MDetail

data class MDetail (
        val _id: Long,
        val bluetooth: BluetoothDef,
        val wiFi:WiFiDef
        val aScreenDef:ScreenDef? = null,
        val aGpsDef: GpsDef = GpsDef()
        ...
)

or if you don't want aScreenDef to be nullable add a default value for it like aGpsDef example. This way you can leave the already-existing constructor calls as is and add new values to constructor calls when needed.

In addition, you can also make use of the named arguments feature where you can specify the name of the parameter when calling the constructor. so if you want to add GpsDef to MDetail without adding ScreenDef you can do the following

val detail = MDetail(id, bluetooth, wifi, aGpsDef = GpsDef())

Note that if you are using this constructor from Java you might need to use @JvmOverloads annotation for the constructor which tells the compiler to generate multiple constructors based on the optional parameters that have default values.

Omar El Halabi
  • 2,118
  • 1
  • 18
  • 26
-1

Everytime you add a new property into MDetail , mark the new property nullable and set the default value to null. like this

data class MDetail (
        val _id: Long,
        val bluetooth: BluetoothDef,
        val wiFi: WiFiDef,
        val screen: ScreenDef? = null,
        ...
)

don't forget when creating a new instance, provide the specific property name, like this

var aMDetail1= MDetail(7L, mBluetoothDef1, mWiFiDef1, screen = mScreenDef1)
garywzh
  • 540
  • 3
  • 9