//抽象类可以包含属性、方法、构造器、初始化块、嵌套类5种成员
//抽象类体现的就是一种模板模式的设计，抽象类作为多个子类的通用模板，子类在抽象类的基础上进行扩展、改造，但子类总体上会大致保留抽象类的行为方式
abstract class Shape {
    init {
        println("执行Shape的初始化块......")
    }

    var color = ""
    abstract fun calPerimeter(): Double    //抽象方法，没有实现

    abstract val type: String              //抽象属性，没有初值

    constructor() {}                       //构造器主要用于被其子类调用，不能生成实例

    constructor(color: String) {
        println("执行Shape的构造器...")
        this.color = color
    }
}

//抽象类的实现1
class Triangle(
    color: String, var a: Double,
    var b: Double, var c: Double
) : Shape(color) {
    fun setSides(a: Double, b: Double, c: Double) {
        if (a >= b + c || b >= a + c || c >= a + b) {
            println("三角形两边之和必须大于第三边")
            return
        }
        this.a = a
        this.b = b
        this.c = c
    }

    //重写Shape类的计算周长的抽象方法
    override fun calPerimeter(): Double {
        return a + b + c
    }

    //重写Shape类的代表形状的抽象属性
    override val type: String = "三角形"
}
//抽象类的实现2
class Circle(color: String, var radius: Double) : Shape(color) {
    //必须重写抽象类中所有abstract方法和属性
    override fun calPerimeter(): Double = 2 * Math.PI * radius
    override val type: String = "圆形"
}

//密封类是一种特殊的抽象类，专门用于派生子类，密封类的直接子类必须与密封类本身在同一个文件中，在其他文件中则不能为密封类派生子类（间接子类则无需在同一个文件中。）
//密封类的子类是固定的，可以清楚地知道密封类只可能有固定数量的子类
sealed class SealedClass{
    class SealedClass1():SealedClass()
    class SealedClass2():SealedClass()
    fun hello(){
        println("Hello World ... ")
    }
}

sealed class Mathematics(){
    data class Dou(val number: Double) : Mathematics()
    data class Sub(val e1: Mathematics, val e2: Mathematics) : Mathematics()
    object NotANumber : Mathematics()

    fun eval(m: Mathematics): Double = when(m) {   //m:子类
        is Dou -> {
            m.number
        }
        is Sub -> eval(m.e1) - eval(m.e2)
        NotANumber -> Double.NaN
    }
}

fun main() {
    var s1: Shape = Triangle("黑色", 3.0, 4.0, 5.5)
    var s2: Shape = Circle("黄色", 4.0)
    println(s1.type)
    println(s2.type)

    println(s1.calPerimeter())
    println(s2.calPerimeter())

        var sc:SealedClass = SealedClass.SealedClass1()   //只能通过密封类内部的子类实例化对象，这时就可以执行里面的方法了
        sc.hello()

    var ec1:Mathematics = Mathematics.Dou(5.0)
    var d1 = ec1.eval(ec1)
    println(d1)

    var ec2:Mathematics = Mathematics.Sub(ec1, Mathematics.Dou(3.0))
    var d2 = ec2.eval(ec2)
    println(d2)

    var ec3:Mathematics = Mathematics.NotANumber
    var d3 = ec3.eval(ec3)
    println(d3)
}
