interface Drawable { fun draw(): String fun drawingKind(): String = "vector" } interface Scalable { fun scale(factor: Double) } abstract class Shape( val name: String, initialColor: String ) : Drawable, Scalable { var color: String = initialColor private set init { require(name.isNotBlank()) { "A shape needs a name" } require(initialColor.isNotBlank()) { "Color cannot be blank" } } abstract fun area(): Double abstract fun perimeter(): Double fun repaint(newColor: String) { require(newColor.isNotBlank()) { "Color cannot be blank" } color = newColor } protected fun requireValidScale(factor: Double) { require(factor > 0.0) { "Scale factor must be positive" } } open fun describe(): String = "$name ($color)" } class Circle(radius: Double, color: String = "sky") : Shape(name = "Circle", initialColor = color) { var radius: Double = radius private set init { require(radius > 0.0) { "Radius must be positive" } } override fun area() = Math.PI * radius * radius override fun perimeter() = 2 * Math.PI * radius override fun scale(factor: Double) { requireValidScale(factor) radius *= factor } override fun draw() = "Circle(radius=$radius, color=$color)" override fun describe() = "${super.describe()} | radius=$radius" } open class Rectangle protected constructor( width: Double, height: Double, color: String, shapeName: String ) : Shape(shapeName, color) { var width: Double = width private set var height: Double = height private set constructor(width: Double, height: Double, color: String = "amber") : this(width, height, color, "Rectangle") init { require(width > 0.0 && height > 0.0) { "Width and height must be positive" } } override fun area() = width * height override fun perimeter() = 2 * (width + height) override fun scale(factor: Double) { requireValidScale(factor) width *= factor height *= factor } open override fun draw() = "Rectangle(${width}x$height, color=$color)" override fun describe() = "${super.describe()} | $width x $height" } class Square(side: Double, color: String = "mint") : Rectangle(side, side, color, "Square") { val side: Double get() = width override fun draw() = "Square(side=$side, color=$color)" } class CanvasLabel(private val text: String) : Drawable { override fun draw() = "Aa $text" } private fun Double.pretty() = "%.2f".format(this) fun printMeasurements(shapes: List) { shapes.forEach { shape -> println( "${shape.name.padEnd(9)} " + "area=${shape.area().pretty()} " + "perimeter=${shape.perimeter().pretty()}" ) } } fun renderAll(items: List) { items.forEach { println("${it.drawingKind()}: ${it.draw()}") } } fun main() { val circle: Shape = Circle(radius = 2.0) val rectangle: Shape = Rectangle(width = 3.0, height = 4.0) val square: Shape = Square(side = 5.0) val shapes: List = listOf(circle, rectangle, square) println("MEASUREMENTS") printMeasurements(shapes) println("\nDRAWABLE GALLERY") val gallery: List = listOf( circle, rectangle, square, CanvasLabel("OOP Gallery") ) renderAll(gallery) square.scale(0.5) println("\nScaled square area=${square.area().pretty()}") }