Untitled
kotlin.oop.basic 1. constructor 1.1. Constructor.kt /* Kotlin provides two kinds of constructors: primary and secondary. */ // Primary Constructor // Initialization code can be placed in initializer blocks, prefixed with init. class Person1(val name: String, var age: Int) class Person2(val name: String, var age: Int) { init { require(age > 0) { "Age must be positive" } println("First initializer block") } private val isAdult = age >= 18 init { println("Second initializer block with isAdult: $isAdult") } } // Secondary Constructor class Person3 { val name: String var age: Int constructor(name: String, age: Int) { this.name = name this.age = age } constructor(name: String): this(name, 0) { println("Secondary constructor called") } } // Kotlin allows for the definition of one primary constructor and one or more secondary constructors in a class. class Person4(val name: String) { var age: Int // Primary constructor init { age = 0 } // Secondary constructor constructor(name: String, age: Int): this(name) { this.age = age } } // Chu y neu khai bao ma khong co var hoac val nhu sau: // name chi la mot tham so cua constructor chu khong phai thuoc tinh cua lop -> khong the truy cap no tu ngoai constructor // hoac qua cac phuong thuc getter class MyClass(name: String) fun main() { val thieu = Person2("Thieu", 24) val hien = Person3("Hieu") } ####################################### 1.2. OverridingConstructor.kt /* in Kotlin (like in most other programming languages), constructors cannot be directly overridden. we can use the keywords open, final, and override to control inheritance and polymorphism. open: it allows subclasses to inherit or override functions and properties. final: it prevents subclasses from overriding functions or properties. override: it is used by the subclass to override functions or properties of the superclass. */ open class Person5(val name: String) // class Employee5(name: String, val id: Int) : Person5(name) // do not override it. open class Person6(open val name: String) class Employee6 (override val name: String, val id: String) : Person6(name) /* Even though constructors cannot be directly overridden in Kotlin, you can still use the constructors of the superclass when defining subclasses, which is an important element of inheritance in OOP. */ open class Person7(val name: String) { init { println("This is $name") } fun talk() { println("$name is talking") } } class Employee7(name: String, val id: Int) : Person7(name) { // the Employee class extends the Person class, using its constructor to set the name property. fun work() { println("$name is working with id $id") } } fun main() { val john = Person7("John") john.talk() val jane = Employee7("Jane", 123) jane.talk() jane.work() } ##################################### 1.3. PropertyAccessors.kt /* Every property in Kotlin has its own backing field, which contains a property value that can be accessed with the special keyword field. When initializing your property, the setter will not be called. This is also true for constructors since they initialize properties. */ fun main() { val client = Client("Han") println(client.name) println(client.age) println(client.info) client.name = "Lester" client.age = 20 println(client.info) client.age = -1 println(client.age) val smartOne = Smartphone("Ericsong") smartOne.price = -24 println(smartOne.price) // 32 val smartTwo = Smartphone("iNokhe") println(smartTwo.price) // 11 } class Client(name: String) { var name: String = name set(value) { field = value.lowercase() } private val defaultAge = 18 var age: Int = defaultAge set(value) { if (value < 0) { println("Age cannot be negative. Set to $defaultAge") defaultAge } else { value } field = value } val info: String get() { return "name = $name, age = $age" } } class Smartphone(val name: String) { var price: Int = -5 get() = name.length - field } class City(val name: String) { var population: Int = 0 set(value) { field = if (value < 0) { 0 } else if ( value > 50_000_000) { 50_000_000 } else { value } } var degrees: Int = 0 set(value) { field = if (value < -92 || value > 57) { when (name) { "Dubai" -> 30 "Moscow" -> 5 "Hanoi" -> 20 else -> 0 } } else { value } } } fun findMin() { val arr = intArrayOf(1, 2, 1) val min = arr.min() val countMin = arr.count {it == min} val index = arr.indexOf(min) println(if (countMin >= 2) { "neither" } else { when (index) { 0 -> "Dubai" 1 -> "Moscow" 2 -> "Hanoi" else -> "other" } } ) } ################################# 1.4. SecondaryConstructor.kt class Person() { var name: String = "Unknown" var age: Int = 0 constructor(_name: String, _age: Int): this() { name = _name this.age = _age } } class Size { var width: Int = 0 var height: Int = 0 constructor() {} constructor(_height: Int) { height = _height } constructor(_width: Int, _height: Int) { width = _width height = _height } constructor(_width: Int, _height: Double) { width = _width height = _height.toInt() } constructor(_height: Double, _width: Int) { width = _width height = _height.toInt() } } class otherSize { // Omitting default values val width: Int val height: Int val area: Int constructor(width: Int, height: Int) { this.width = width this.height = height this.area = width * height } } fun main() { val size1 = Size() val size2 = Size(3, 4) val fabric = Fabric("white", "dots", "black") println(fabric.color) } class Fabric(var color: String) { var pattern: String = "none" var patternColor: String = "none" init { println("$color fabric is created") } constructor(color: String, pattern: String, patternColor: String): this(color) { println("$patternColor $pattern pattern is printed on the fabric") } } class Kitty { // write here var color: String = "Unknown" var age: Int = 0 constructor() {} constructor(color: String, age: Int) { this.color = color this.age = age } constructor(color: String) { this.color = color } constructor(age: Int) { this.age = age } constructor(age: Int, color: String) { this.color = color this.age = age } } ############################## 2. visibility 2.1. ForClassesPackagesAndModules.kt /* 1. Defining class visibility - public: The class is accessible from any code. (Mac dinh trong kotlin) - internal: The class is accessible only within the module. - protected: The class is accessible only within the class and its subclasses. - private: The class is accessible only within the file it is declared in. */ public class PublicClass internal class InternalClass // protected class ProtectedClass // Compilation error, as protected classes cannot be top-level private class PrivateClass /* 2. Inheritance and visibility In Kotlin, if a class inherits another class, it can access its protected and internal members. However, private members of the parent class are not accessible to subclasses. */ open class Parent { public val publicValue = "Public" internal val internalValue = "Internal" protected val protectedValue = "Protected" private val privateValue = "Private" } class Child : Parent() { fun printValue() { println(publicValue) println(internalValue) println(protectedValue) // println(privateValue) // Compilation error, as privateValue is not accessible } } /* 3. Defining package visibility: Packages in Kotlin serve to group related classes, objects, and functions. Visibility within packages is determined by visibility modifiers, similar to those used for classes. */ public class MyClass internal class MyInternalClass /* 4. Modules and Internal visibility A module is a set of source files compiled together. In Kotlin, a module can be an IntelliJ IDEA project, a Gradle or Maven project, or another compilation unit. Module visibility is regulated by the internal modifier. The internal modifier is used to indicate that a certain code element is accessible only within a single module. This is useful when you want to restrict access to parts of the code that should only be available within the module and should not be visible outside of it. */ ########################################### 2.2. ForMembers.kt /* There are four access modifiers in Kotlin: private, protected, internal, and public. 1. Public member: By declaring an instance of a class as a public modifier, you can refer to any of its fields anywhere in the program where the object itself is available. 2. Private member: When using private, data will only be available within a specific class. These fields cannot be changed anywhere except in the methods of this class. However, you won't be able to get their value from the outside either 3. Protected member: Protected is the same as private, except it can be seen in subclasses. 4. Internal member: The internal modifier means that who sees the declaring class sees its internal members */ fun main() { val mark: Student2 = Student2("Mark", 1) // println("Name: ${mark.name} Id: ${mark.id}") //Cannot access 'id': it is private in 'Student' // val han: Student3 = Student3("han") // Cannot access '<init>': it is private in 'Student' val anna: Student3 = Student3("Anna", 23) } public class Student1 { public var name: String = "Unknown" // property is public and visible everywhere } class Student2(val name: String, private val id: Int) /* Visibility of constructors in a class: You can also specify a modifier for constructors: for example, make the class primary constructor private. Remember to add an explicit constructor keyword */ class Student3 private constructor(val name: String) { var age: Int = 0 constructor(name: String, _age: Int) : this(name) { age = _age } } /* Public and private functions: Private functions are used to hide the internal low-level logic implementation from the rest of the code and make public functions more brief and readable. */ class Student4( private val name: String, private val id: Int, private val age: Int ) { fun printInfo() { println("Id: $id Name: $name") } private fun getAge() { print("Age: $age ") } } ################################ 3. DeclaringClasses.kt class Emptiness { } // when a class has an empty body, curly braces can be omitted. So the same class can be defined in the following way: class Test class Patient { var name: String = "Unknown" var age: Int = 0 var height: Double = 0.0 } fun main() { class ClassInFunction // Object creation val empty = Emptiness() val patient = Patient() println("${patient.name}: ${patient.age} yrs, ${patient.height} cm") val john = Patient() john.name = "John" john.age = 30 john.height = 180.0 val alice = Patient() alice.name = "Alice" alice.age = 22 alice.height = 165.0 println("${john.name}: ${john.age} yrs, ${john.height} cm") println("${alice.name}: ${alice.age} yrs, ${alice.height} cm") } ################################# 4. FinalMembers.kt /* In Kotlin, all classes and methods are final by default. This means that if you declare a class in Kotlin, you will not be able to inherit it until you clearly make it open */ class MyFinalClass { fun myFinalMethod() { println("This method cannot be overridden!") } } //class MyChildClass : MyFinalClass() { // Error! Cannot inherit MyFinalClass // override fun myFinalMethod() { // Error! Cannot override myFinalMethod // println("I'm trying to override your method!") // } //} // If you want your class to inherit, or a method to be overridden, you need to use the keyword open. open class MyBaseClass { open fun myMethod() { println("Basic implementation") } } open class MyIntermediateClass : MyBaseClass() { // can use final for overridden methods or properties to prevent them from being further redefined. final override fun myMethod() { println("An overridden implementation that cannot be redefined further") } } class MyDerivedClass : MyIntermediateClass() { // override fun myMethod() { // Error! Cannot override myMethod // println("I'm trying to override your method!") // } } ################################### kotlin.oop.classhierarchy 1. abstraction 1.1. AbstractClass.kt /* An abstract class is like a blueprint that can be used to create other classes. */ // By default, abstract classes in Kotlin are open to being extended, and their abstract methods and properties are open to being overridden. abstract class Animal1(val id: Int) { // val name: String // We get here a compile-time error: property must be initialized or be abstract abstract val name: String abstract fun makeSound() fun isSleeping(): Boolean { return false } } // When a class extends an abstract class, it must provide implementations for all the abstract members declared in the abstract class. abstract class Animal2 { abstract fun move() abstract fun makeSound() fun eat(): Boolean = false fun sleep(): Boolean = false } class Cat2 : Animal2() { override fun move() { // Implementation specific to how the cat moves } override fun makeSound() { // Implementation specific to what sound the cat makes } } // An abstract class can also serve as a base class for other abstract classes. abstract class Animal3 { abstract fun makeSound() } abstract class Mammal3 : Animal3() { abstract fun eat() } class Cat3 : Mammal3() { override fun makeSound() { println("Meow!") } override fun eat() { println("The cat is eating.") } } // In Kotlin, it is also possible to make an abstract class inherit from an open class while overriding // a non-abstract open member with an abstract one using two keywords: abstract override. open class Polygon { open fun draw() { // Some default polygon drawing method } } abstract class WildShape : Polygon() { // Classes that inherit WildShape need to provide their own draw method instead of using the default on Polygon abstract override fun draw() } fun main() { // We cannot create objects of an abstract class directly, but we can create references of abstract class types and // assign objects of concrete subclasses to them. val cat: Animal2 = Cat2() cat.move() cat.makeSound() } ################################ 1.2. AbstractClassVsInterface.kt /* Abstract classes vs. interfaces: 1. Instantiation - Abstract classes: They cannot be instantiated directly. They are meant to serve as a base for subclasses to inherit from. - Interface: They cannot be instantiated directly. They define a contract of methods and properties that implementing classes must adhere to. 2. Constructor - Abstract classes: They can have constructors, including both primary and secondary constructors. Subclasses are responsible for invoking the appropriate superclass constructor. - Interface: They cannot have constructors. They only declare methods and properties without any implementation. 3. State - Abstract classes: They can have member variables and non-abstract methods with default implementations. They can also hold state and maintain internal data. - Interface: They cannot hold state or define member variables. They are purely focused on declaring behavior. 4. Inheritance - Abstract classes: Subclasses can extend only one abstract class. In Kotlin, class inheritance is limited to a single class, and abstract classes provide a way to establish an inheritance hierarchy. - Interface: Implementing classes can implement multiple interfaces. Kotlin supports multiple inheritance through interfaces, allowing classes to implement multiple interfaces at once. 5. Abstract and Non-Abstract Members - Abstract classes: They can have both abstract and non-abstract methods and properties. Subclasses must provide implementations for abstract members while inheriting non-abstract members. - Interface: They can declare abstract methods or methods that have default implementations. Both types of methods can be overridden by implementing classes. When deciding between abstract classes and interfaces, consider the following guidelines: - Use abstract classes when you want to provide a default implementation or when you need to maintain internal state within the base class. - Use interfaces when you want to define a contract of behavior that multiple unrelated classes can implement or when you need to achieve multiple inheritance. */ // Using abstract classes and interfaces together // Abstract classes enable you to encapsulate common behavior and state, while interfaces establish contracts for implementing classes. interface Shape { fun calculateArea(): Double fun calculatePerimeter(): Double } abstract class AbstractShape : Shape { // Common behavior or properties for shapes can be implemented here } class Rectangle(private val width: Double, private val height: Double) : AbstractShape() { override fun calculateArea(): Double { return width * height } override fun calculatePerimeter(): Double { return 2 * (width + height) } } class Circle(private val radius: Double) : AbstractShape() { override fun calculateArea(): Double { return Math.PI * radius * radius } override fun calculatePerimeter(): Double { return 2 * Math.PI * radius } } #################################### 2. encapsulation 2.1. Encapsulation.kt /* Encapsulation refers to bundling data with the methods operating with it while restricting direct access to some components. */ fun main() { } ###################################### 3. inheritance 3.1. Inheritance.kt // If your code looks like this, you're creating a final class. // It means that this class won't be available for inheritance in the future. class Book1(val pages: Int, val author: String) // So, if you're really sure that you need to extend your Book class (parent class), here's an easy way to do it: open class Book2(val pages: Int, val author: String, var cost: Float = 0F) { fun getFullInfo(): String { return "$pages pages, $author author, $$cost cost" } } // Extend it class Comics(pages: Int, author: String, cost: Float) : Book2(pages, author, cost) class Booklet(pages: Int, cost: Float) : Book2(pages, "", cost) { fun getUSDCost(): String { return "$$cost cost" } fun getEuroCost(): String { return "€$cost cost" } } // Reuse it // We can pass any child of the Book instance to the isBigBook() function. fun isBigBook(book: Book2): Boolean { return book.pages >= 50 } fun main() { val spiderManBook = Comics(60, "The Universe", 8.99F) println(spiderManBook.getFullInfo()) // output: 60 pages, The Universe author, $8.99 cost val centralBooklet = Booklet(5, 0.14F) print(centralBooklet.getUSDCost()) println(isBigBook(spiderManBook)) println(isBigBook(centralBooklet)) } ##################################### 4. interface 4.1. InterfaceComposition.kt // Resolving conflicts interface FirstInterface { fun f() { print("First") } fun g() { print("not g") } } interface SecondInterface { fun f() { print("Second") } fun g() { print("g") } } class ThirdClass : FirstInterface, SecondInterface { override fun f() { super<FirstInterface>.f() super<SecondInterface>.f() } override fun g() { super<SecondInterface>.g() } } // Idea of composition // As you can see, inheritance as a design pattern has its own flaws. // There's an alternative to it, which features same code reuse, and it's called composition. interface Level { fun getLevel(): Int } interface Enemy { fun isEnemy(): Boolean } interface Class { fun getClass(): String } class DangerousEnemyWarrior : Level, Enemy, Class { // For each new character, we'd need to manually set all these parameters. // How about we try to reuse the same parameters to optimize the process? override fun getLevel(): Int { return 10 } override fun isEnemy(): Boolean { return true } override fun getClass(): String { return "Warrior" } } // Instead of implementing a lot of interfaces and having to override all of their methods and fields, // we can create their implementations in advance and store them as separate entities (objects in the example below). // Then we can simply compose a needed object out of these "building blocks". // We only need to create all these objects once object Dangerous : Level { override fun getLevel(): Int { return 10 } } object NotDangerous : Level { override fun getLevel(): Int { return 1 } } object Foe : Enemy { override fun isEnemy(): Boolean { return true } } object Friend : Enemy { override fun isEnemy(): Boolean { return false } } object Warrior : Class { override fun getClass(): String { return "Warrior"}} object Wizard : Class { override fun getClass(): String { return "Wizard"}} // And then we're free to use them as many times as we'd like! class DangerousKotlinEnemyWarrior : Level by Dangerous, Enemy by Foe, Class by Warrior class NotDangerousFriendlyWizard : Level by NotDangerous, Enemy by Friend, Class by Wizard #################################### 4.2. InterfaceInheritance.kt // Implementation of derived interface interface Animal2 { val amountOfLimbs: Int fun move() fun communicate() } interface Bird2 : Animal2 { val canFly: Boolean val flyingSpeed: Int fun buildNest() } class Parrot2 : Bird2 { override val amountOfLimbs: Int = 2 override val canFly: Boolean = true override val flyingSpeed: Int = 20 override fun move() { // fly() } override fun communicate() { // speak() } override fun buildNest() { // collectMaterials() // findGoodPlace() // buildSmallNest() } } // Multiple Inheritance interface Animal3 { val numberOfLimbs: Int fun move() fun communicate() } interface Bird3 : Animal3 { fun buildNest() } interface Flying3 { val flyingSpeed: Int val flyingManeuverability: Int } class Owl3 : Bird3, Flying3 { // Flying interface override val flyingSpeed: Int = 100 override val flyingManeuverability: Int = 95 // Bird interface override fun buildNest() { // buildSmallNest() } // Animal Interface override val numberOfLimbs: Int = 2 override fun move() { // fly() } override fun communicate() { // coo() } } // Inheritance from multiple interfaces ############################## 4.3. Interfaces.kt /* An interface is a collection of methods that describes the behavior of an object. To implement the interface, an object should implement all the methods from it. */ // In Kotlin, interfaces are defined similarly to classes, only without constructors – interfaces cannot store states. // If they have a default implementation (which we will discuss in the next part), you don't need to override them. interface Animal { public val numberOfLimbs: Int public fun move() fun communicate(): String val age: Int get() = 10 fun printNumberOfLimbs() { print(numberOfLimbs) } } class Cat : Animal { override val numberOfLimbs: Int = 4 override fun move() { // run() } override fun communicate(): String { return "Meow" } } class Parrot : Animal { override val numberOfLimbs: Int = 2 override fun move() { // fly() } override fun communicate(): String { return "speak" } } // So far, it might seem like interfaces are a convenient way of making patterns for building classes. // However, that's not exactly the case because an interface is mostly used as a model of interaction with a certain object. // Interfaces might be compared to contracts because whatever is using this interface is guaranteed to possess the range of qualities defined in it. ################################ 5. polymorphism 5.1. Polymorphism.kt /* Polymorphism is the ability of an object or its methods to take on many forms depending on its type and the parameters of this or that method. We can use polymorphism to define how we want our objects to behave depending on their type or the parameters of their methods. We can: - overload methods with different parameters; - override methods in subclasses; - use duck typing (trong ngon ngu lap trinh dong nhu Python, Ruby) */
Leave a Comment