References: Do it! 코틀린 프로그래밍

코틀린에서의 인터페이스에 대해 알아봅니다.

 

 

1. 인터페이스(Interface)

 

코틀린의 인터페이스에는 추상 메서드나 일반 메서드가 포함될 수 있습니다. 

다른 객체지향 언어와는 달리 메서드에 구현 내용이 포함될 수 있습니다. 단, 추상 클래스처럼 프로퍼티를 통해 상태를 지정할 수는 없습니다.

 

추상 클래스는 기본적으로 클래스이기 때문에 상속을 통해 하위로 확장이 가능합니다. 

그런데 하위 클래스는 상속을 하나만 허용합니다. 또한 상위 클래스와 강한 관계가 생기며 상위 클래스의 영향을 받게 됩니다.

 

반면에 인터페이스는 기본적으로 클래스가 아닙니다. 따라서 상속으로 하위 클래스에 프로퍼티와 메서드를 전달하지 않습니다. 상속받은 하위 클래스라고 하기보단 구현 클래스라고 합니다.

구현 클래스는 인터페이스와 강한 관계를 갖지 않습니다. 

또한 원하는 인터페이스 만큼 구현 클래스에 붙여서 필요한 메서드를 구현할 수 있습니다.

 

 

2. 인터페이스 선언 및 구현 방법.

 

인터페이스는 interface라는 선언자를 통해 선언합니다.

구현 클래스에서는 override 라는 선언자를 통해 메서드들을 구현합니다.

 

다음 예시를 통해 확인하겠습니다.

 

interface Pet{
    var category: String
    fun feeding()
    fun patting(){ println("Keep patting") }
}
class Cat(override var category: String): Pet{
    override fun feeding() { println("Feed the cat a tuna can!") }
}
fun main() {
    val obj = Cat("small")
    println("Pet category: ${obj.category}")
    obj.feeding()
    obj.patting()
}

 

  • var category / fun feeding: 인터페이스에서는 별도의 선언자 없어도 기본적으로 abstract로 선언됩니다.
  • fun patting: 단, 이와 같이 메서드를 구현하면 일반 메서드가 됩니다.
  • override var category / override fun feeding: 추상 프로퍼티와 클래스를 구현합니다.
  • : Pet: Pet 인터페이스를 구현하는 클래스임을 알려줍니다.

 

위의 코드를 실행하면 다음과 같은 결과를 확인할 수 있습니다.

 

 

 

3. 인터페이스의 필요성.

 

다음 예시를 통해 인터페이스의 필요성에 대해 알아보겠습니다.

우선 애완동물과 주인의 관계를 표현하는 코드를 작성해 보겠습니다.

 

open class Animal(val name: String)
interface Pet{
    var category: String
    fun feeding()
    fun patting(){ println("Keep patting") }
}
class Dog(name: String, override var category: String): Animal(name), Pet{
    override fun feeding() { println("Feed the dog a bone.") }
}
class Cat(name: String, override var category: String): Animal(name), Pet{
    override fun feeding() { println("Feed the cat a tuna can.") }
}
class Master {
    fun playWithPet(dog: Dog){ println("Enjoy with my dog.") }
    fun playWithPet(cat: Cat){ println("Enjoy with my cat") }
}
fun main(){
    val master = Master()
    val dog = Dog("Toto", "small")
    val cat = Cat("Coco", "BigFat")
    master.playWithPet(dog)
    master.playWithPet(cat)
}

 

  • Dog / Cat: Animal클래스를 상속받고 Pet 인터페이스를 구현하는 클래스
  • playWithPet: 함수 인자에 따른 함수 오버로딩

위의 코드를 실행하면 다음과 같은 결과를 확인할 수 있습니다.

여기서 만약 애완동물 종류가 늘어나면 어떻게 될까요? 애완동물마다 playWithPet함수를 새로 오버로딩해야 하는 문제가 생깁니다. 

 

이러한 문제를 피하기 위해 코드를 다음과 같이 수정해 봅시다.

 

open class Animal(val name: String)
interface Pet{
    var category: String
    var species: String
    fun feeding()
    fun patting(){ println("Keep patting") }
}
class Dog(name: String, override var category: String): Animal(name), Pet{
    override var species: String = "dog"
    override fun feeding() { println("Feed the dog a bone.") }
}
class Cat(name: String, override var category: String): Animal(name), Pet{
    override var species: String = "cat"
    override fun feeding() { println("Feed the cat a tuna can.") }
}
class Master {
    fun playWithPet(pet: Pet){ println("Enjoy with my ${pet.species}.") }
}
fun main(){
    val master = Master()
    val dog = Dog("Toto", "small")
    val cat = Cat("Coco", "BigFat")
    master.playWithPet(dog)
    master.playWithPet(cat)
}

 

위와 같이 수정하면 더 이상 애완동물을 추가 할 때 마다 새로운 함수를 오버로딩할 필요가 없어집니다.

기존의 Master 클래스는 각각의 애완동물 클래스에 의존적이었으나 이젠 더이상 의존적인 클래스가 아니게 됩니다.

이런 식으로 클래스의 독립성을 유지하는데 인터페이스가 큰 역할을 합니다.

 

 

 

4. 여러 인터페이스를 구현하는 방법

 

클래스는 하나의 클래스만 상속이 가능합니다.

하지만 인터페이스를 이용하면 다중 상속과 같은 형태로 여러 인터페이스를 구현할 수 있습니다.

 

다음 예시를 통해 확인해 보겠습니다.

 

interface Bird{
    val wings: Int
    fun fly()
    fun jump(){ println("Bird jump!") }
}
interface Horse{
    val maxSpeed: Int
    fun run()
    fun jump(){ println("Jump!, max speed: $maxSpeed") }
}
class Pegasus: Bird, Horse{
    override val wings: Int = 2
    override val maxSpeed: Int = 100
    override fun fly(){ println("Fly") }
    override fun run(){ println("Run!") }
    override fun jump() {
        super<Horse>.jump()
        println("Pegasus jump!")
    }
}
fun main(){
    val pegasus = Pegasus()
    pegasus.fly()
    pegasus.run()
    pegasus.jump()
}

 

  • class Pegasus: Bird, Horse: 페가수스 클래스를 만들고 Bird, Horse 인터페이스를 구현하였습니다.
  • super<Horse>.jump(): 부모 인터페이스 중 Horse 인터페이스의 jump 메서드를 호출합니다.

 

해당 코드를 실행하면 다음과 같은 결과를 확인할 수 있습니다.

위와 같은 방법을 통해 한 클래스에 여러 인터페이스를 구현하게 할 수 있습니다.

 

 

5.  인터페이스 위임

 

인터페이스에서 by위임을 사용하는 방법을 예시를 통해 알아보겠습니다.

 

interface Nameable { var name: String }
class StaffName: Nameable { override var name: String = "Sean" }
class Work: Runnable { override fun run() { println("Work...") } }
class Person(name: Nameable, work: Runnable): Nameable by name, Runnable by work
fun main() {
    val person = Person(StaffName(), Work())
    println(person.name)
    person.run()
}

 

  • class Person: 각 매개변수에 인터페이스들을 위임합니다.
  • Person(StaffName(), Work()): 클래스의 생성자를 통해 객체를 전달합니다.

위의 코드를 실행한 결과는 다음과 같습니다.

이처럼 각 클래스의 위임된 멤버에 접근해 사용할 수 있습니다.

 

 

 

반응형

'Programming' 카테고리의 다른 글

[Kotlin] 33. 내부 클래스  (0) 2019.09.16
[Kotlin] 32. 데이터 클래스  (0) 2019.09.12
[Kotlin] 30. 추상 클래스  (0) 2019.09.05
[Kotlin] 29. 컴패니언 객체  (0) 2019.09.01
[Kotlin] 28. 위임(Delegation)  (0) 2019.09.01

References: Do it! 코틀린 프로그래밍

코틀린에서의 추상 클래스에 대해 알아봅니다.

 

 

1. 추상 클래스

 

추상 클래스는 abstract 키워드와 함께 전업됩니다.

추상 클래스는 일반적인 방법으로 인스턴스화 될 수 없습니다.

추상 클래스를 상속하는 하위 클래스가 어떻게 만들어져야 하는지 나타내는 용도로 사용됩니다.

 

추상 클래스의 멤버인 프로퍼티나 메서드 또한 abstract로 선언될 수 있으며 이를 추상 프로퍼티 혹은 추상 메서드라고 합니다. 추상 프로퍼티나 추상 메서드가 하나라도 존재한다면 그 클래스는 추상 클래스가 되어야 합니다.

추상 프로퍼티와 추상 메서드는 abstract로 선언 함으로써 미완성의 의미를 줄 수 있습니다.

 

다음 예시를 통해 추상 클래스에 대해 알아보겠습니다.

 

abstract class Vehicle(val name: String, val color: String, val weight: Double){
    abstract var maxSpeed: Double
    var year = "2019"
    abstract fun start()
    abstract fun stop()
    fun displaySpecs(){
        println("Name: $name, Color: $color, Weight: $weight, Year: $year, MaxSpeed: $maxSpeed")
    }
}

 

  • abstract class Vehicle : 추상 클래스 정의
  • abstract var maxSpeed : 추상 프로퍼티 정의
  • var year : 일반 프로퍼티 정의
  • abstract fun start / stop : 추상 메서드 정의
  • fun displaySpecs : 일반 메서드 정의

해당 클래스는 객체를 생성할 수 없고 설계의 역할을 합니다.

abstract선언자가 있는 모든 프로퍼티와 메서드는 반드시 자식 클래스에서 정의되어야 합니다.

이 추상 클래스를 상속하는 자식 클래스를 만들어 보겠습니다.

 

class Car (name: String, color: String, weight: Double, override var maxSpeed: Double): Vehicle(name, color, weight){
    override fun start() = println("Car started.")
    override fun stop() = println("Car stopped.")
}
class Motorcycle(name: String, color: String, weight: Double, override var maxSpeed: Double): Vehicle(name, color, weight){
    override fun start() = println("Motorcycle started.")
    override fun stop() = println("Motorcycle stopped.")
}

 

  • override : 추상 클래스에서 선언된 모든 추상 프로퍼티와 추상 메서드를 구현.

모든 추상 프로퍼티와 추상 메서드를 구현하지 않으면 컴파일러에서 오류를 반환하는 것을 확인할 수 있습니다.

모든 추상 클래스와 프로퍼티를 구현 후 다음 메인 함수를 실행시켜 봅시다.

 

fun main() {
    val car = Car("SuperMatiz", "Yellow", 1100.0, 270.0)
    var motor = Motorcycle("DreamBike", "Red", 173.0, 100.0)
    car.year = "2013"
    car.displaySpecs()
    car.start()
    motor.displaySpecs()
    motor.start()
}

 

실행결과는 다음과 같습니다.

추상 클래스는 이처럼 하위 클래스에서 반드시 가져야 하는 프로퍼티나 메서드를 제시할 수 있습니다. 또한 일반 프로퍼티나 메서드를 만들어 공통적으로 필요한 기능을 정의할 수도 있습니다.

 

만약 하위 클래스를 생성하지 않고 단일 인스턴스로 객체를 생성하려면 다음과 같이 수행하면 됩니다.

 

abstract class Printer{
    abstract fun print()
}
var myPrinter = object: Printer(){
    override fun print() = println("Print!")
}
fun main() {
    myPrinter.print()
}

 

  • abstract class Printer : 추상 클래스를 정의합니다.
  • abstract fun print() : 추상 메서드를 정의합니다.
  • var myPrinter = object: Printer() : 익명 객체를 생성하고 Printer() 추상 클래스를 상속받습니다.
  • override fun print() : 추상 메서드를 구현합니다.

실행 결과는 다음과 같습니다.

익명 객체를 지정하는 object 키워드를 사용해 추상 메서드를 인스턴스화 시킬 수 있습니다.

 

 

 

 

 

반응형

'Programming' 카테고리의 다른 글

[Kotlin] 32. 데이터 클래스  (0) 2019.09.12
[Kotlin] 31. 인터페이스  (0) 2019.09.12
[Kotlin] 29. 컴패니언 객체  (0) 2019.09.01
[Kotlin] 28. 위임(Delegation)  (0) 2019.09.01
[Kotlin] 27. 지연 초기화: lazy  (0) 2019.08.27

References: Do it! 코틀린 프로그래밍

코틀린에서의 컴패니언 객체에 대해 알아봅니다.

 

 

1. 컴패니언(companion)

 

코틀린은 static 키워드가 없는 대신 companion객체를 제공합니다.

 

컴패니언 객체는 프로그램 실행 시 독립적인 메모리 공간을 할당받습니다.

따로 인스턴스화 할 필요가 없으며 어떤 객체라도 동일한 참조 값을 갖고 있어 해당 클래스의 상태에 관계없이 접근할 수 있습니다.

따라서 모든 객체에 의해 공유되는 효과를 갖습니다.

 

다음 예시를 통해 컴패니언 객체를 사용하는 방법에 대해 알아봅시다

 

class Person{
    var id: Int = 0
    var name: String = "smoh"
    companion object {
        var language: String = "Korean"
        fun work(){ println("working ...") }
    }
}
fun main() {
    println(Person.language)
    Person.language = "English"
    println(Person.language)
    Person.work()
    //println(Person.name)
}

 

  • companion object : 컴패니언 객체를 선언합니다. 해당 객체의 내부에 선언된 내용은 프로그램 시작 시 초기화되어 바로 사용할 수 있습니다.
  • //println(Person.name) : 컴패니언 객체 외부의 프로퍼티를 사용하기 위해선 초기화가 먼저 필요합니다.

위 코드를 실행하면 다음과 같은 결과를 확인할 수 있습니다

 

컴패니언 객체는 주로 실제 객체의 싱글톤(Singleton)으로 정의됩니다.

이는 전역 변수를 사용하지 않고 객체를 하나만 생성하도록 하며 생성된 객체를 어디서든 참조하도록 하기 위함입니다.

이와 같이 하면 객체가 동일한 정보를 가질 때 하나의 메모리만 유지해 메모리를 절약할 수 있는 장점이 있습니다.

 

 

 

 

 

 

반응형

'Programming' 카테고리의 다른 글

[Kotlin] 31. 인터페이스  (0) 2019.09.12
[Kotlin] 30. 추상 클래스  (0) 2019.09.05
[Kotlin] 28. 위임(Delegation)  (0) 2019.09.01
[Kotlin] 27. 지연 초기화: lazy  (0) 2019.08.27
[Kotlin] 26. 지연초기화: lateinit  (0) 2019.08.27

References: Do it! 코틀린 프로그래밍

코틀린에서 by를 이용한 위임에 대해 알아봅니다.

 

1. 위임(Delegation)

 

현실에서의 위임(Delegation)이란 특정한 일을 대신하는 중간자의 역할을 말합니다.

코틀린에서도 by를 이용한 위임을 사용해 특정 클래스를 확장하거나 이용할 수 있습니다.

 

기본적으로 코틀린의 라이브러리는 open 되지 않은 채 제공됩니다. 따라서 상속이나 직접 클래스에 기능 확장이 어렵습니다.

따라서 필요한 경우에만 상속과 비슷하게 위임을 사용하여 해당 클래스의 모든 기능을 사용하면서 기능을 추가 확장 구현을 가능하게 합니다.

 

 

2. by에 의한 클래스 위임

 

by를 사용하면 하나의 클래스가 다른 클래스에 위임 하도록 선언할 수 있습니다.

위임이 선언되면 위임된 클래스가 가지는 멤버를 참조없이 호출할 수 있게 됩니다.

 

다음 예시를 통해 by를 이용한 클래스 위임에 대해 알아봅시다.

 

interface Car { fun go(): String }
class VanImpl(val power: String): Car{
    override fun go() = "은 짐을 적재하며 $power 을 가집니다."
}
class SportImpl(var power: String): Car{
    override fun go() = "은 경주용에 사용되며 $power 을 가집니다."
}
class CarModel(var model: String, impl: Car): Car by impl{
    fun carInfo(){ println("$model ${go()}") }
}
fun main(){
    var myDamas = CarModel("Damas", VanImpl("100마력"))
    var my350z = CarModel("350z", SportImpl("350마력"))
    myDamas.carInfo()    
    my350z.carInfo()
}

 

  • VanImpl / SportImpl : Car 인터페이스를 상속받아 구현합니다.
  • Car by impl인자 impl은 CarModel에 의해 위임됩니다. 타입이 Car이므로 특정 참조 없이 Car의 멤버를 사용할 수 있게 됩니다.

위 예시의 실행결과는 다음과 같습니다.

 

 

 

3. observable() 함수에 의한 위임

 

observable() 함수는 특정 프로퍼티를 감시하고 있다가 변화가 감지되면 호출되어 처리됩니다.

특정 변경 이벤트에 따라 호출되므로 콜백이라고 부르기도 합니다.

 

다음 예시를 통해 observable() 함수의 사용법에 대해 알아보겠습니다.

 

import kotlin.properties.Delegates
class User{
    var name: String by Delegates.observable("NONAME"){
        prop, old, new -> println("$old -> $new")
    }
}
fun main(){
    var user = User()
    user.name="smoh"
    user.name="Anonymous"
}

 

  • by Delegates.observable("NONAME"해당 프로퍼티에 obervable 함수로 위임합니다. 초깃값은 "NONAME"으로 설정합니다.
  • new -> println("$old -> $new") : 프로퍼티의 변경이 감지되면 new에 할당되어 있는 람다식이 실행됩니다.

 

위의 함수를 실행하면 다음과 같은 결과를 확인할 수 있습니다.

 

 

User클래스의 name이 변경될 때마다 지정해둔 람다식이 실행된 것을 알 수 있습니다.

 

 

4. vetoable() 함수에 의한 위임

 

obervable() 함수와 유사한 함수입니다. 프로퍼티 값이 변경되면 호출되며 콜백이 true값을 반환할 때 만 프로퍼티를 업데이트합니다.

 

다음 예시를 통해 알아보겠습니다.

 

import kotlin.properties.Delegates
fun main() {
    var max: Int by Delegates.vetoable(0){
        prop, old, new -> new > old
    }
    println(max)
    max = 10
    println(max)
    max = 5
    println(max)
}

 

  • by Delegates.vetoable(0) : 해당 프로퍼티에 vetoable() 함수로 위임합니다. 초깃값은 0으로 설정합니다.
  • new -> new > old : 콜백 함수입니다. 해당 조건이 true로 리턴되면 new값을 prop에 새로 저장합니다.

 

위 함수를 실행한 결과는 다음과 같습니다.

 

 

 

초깃값은 0으로 초기화됩니다. 이후 10은 0보다 크니까 프로퍼티 값에 저장합니다. 그다음 5는 10보다 작으므로 프로퍼티 값을 경신하지 않습니다.

 

 

 

반응형

'Programming' 카테고리의 다른 글

[Kotlin] 30. 추상 클래스  (0) 2019.09.05
[Kotlin] 29. 컴패니언 객체  (0) 2019.09.01
[Kotlin] 27. 지연 초기화: lazy  (0) 2019.08.27
[Kotlin] 26. 지연초기화: lateinit  (0) 2019.08.27
[Kotlin] 25. Custom Getter & Setter  (0) 2019.08.27

References: Do it! 코틀린 프로그래밍

코틀린에서 lazy을 이용한 지연 초기화에 대해 알아봅니다.

 

 

1. lazy

 

앞서 알아본 lateinit는 val을 허용하지 않습니다.

var은 언제든 값이 변경될 수 있다는 문제를 갖고 있습니다.

lazy를 사용하면 val로 선언해도 지연 초기화를 사용할 수 있습니다.

 

lazy는 다음과 같은 특징을 갖습니다.

  • val에서만 사용 가능하다.
  • 호출 시점에 by laze {...} 정의에 의해 초기화를 진행한다.
  • 람다식으로 구성되어 lazy인스턴스 반환 값을 갖는 함수다.

 

 

2. lazy를 통한 프로퍼티 지연 초기화

 

다음 예시를 통해 lazy를 통해 프로파티를 지연 초기화시키는 방법에 대해 알아봅시다.

 

class LazyTest{
    init{ println("Init block") }
    val subject by lazy {
        println("Initialized by lazy.")
        "Kotlin programming"
    }
    fun flow(){
        println("Not initialized")
        println("Subject one: $subject")
        println("Subject two: $subject")
    }
}
fun main() {
    val lazyTest = LazyTest()
    lazyTest.flow()
}

 

  • val subject by lazy { : by lazy를 통한 지연 초기화. 프로퍼티에 최초 접근될 때 블록 내부의 내용이 실행된다.
  • println("Subject one: $subject") : 프로퍼티에 최초 접근하는 코드. 이때 by lazy 블록의 코드가 실행된다.
  • println("Subject two: $subject") : 프로퍼티가 이미 초기화되어 by lazy 블록의 코드가 실행되지 않는다.

위의 코드를 실행하면 다음과 같은 결과를 확인할 수 있습니다.

 

 

 

3. lazy를 통한 객체 지연 초기화

 

다음 예시를 통해 lazy를 통해 객체를 지연 초기화시키는 방법에 대해 알아봅시다.

 

class Person(val name: String, val aget: Int)
fun main() {
    var isPersonInitialized: Boolean = false
    val person: Person by lazy{
        isPersonInitialized = true
        Person("smoh", 28)
    }
    val personDelegate = lazy{ Person("smoh", 28) }
    println("person Init: $isPersonInitialized")
    println("personDelegate Init: ${personDelegate.isInitialized()}")
    println("person.name: ${person.name}")
    println("personDelegate.value.name = ${personDelegate.value.name}")
    println("person Init: $isPersonInitialized")
    println("personDelegate Init: ${personDelegate.isInitialized()}")
}

 

  • by lazy: by lazy를 사용한 객체의 지연 초기화. 
  • lazy{ Person("smoh", 28) : 위임 변수를 사용한 객체의 초기화
  • ${person.name} : 객체 내 프로퍼티에 접근할 때 지연 초기화가 이루어집니다.
  • ${personDelegate.value.name} : 객체 내 프로퍼티에 접근할 때 지연 초기화가 이루어집니다.

 

 

4. lazy의 모드들

 

lazy에는 SYNCRONIZED, PUBLICATION, NONE의 세 가지 모드가 있습니다.

 

  • SYNCRONIZED : lock을 사용해 단일 스레드만 사용하는 걸 보장합니다. 기본값입니다.
  • PUBLICATION : 여러 군데에서 호출될 수 있으나 처음 초기화된 후 반환 값을 사용합니다.
  • NON : lock을 사용하지 않아 빠르지만 여러 스레드가 접근할 수 있어 값의 일관성을 보장할 수 없습니다.

다른 모드를 사용하고 싶으면 by lazy(모드 이름){ ... }과 같이 사용하면 됩니다.

 

 

 

 

반응형

References: Do it! 코틀린 프로그래밍

코틀린에서 lateinit을 이용한 지연 초기화에 대해 알아봅니다.

 

 

1. 지연 초기화

 

프로퍼티를 선언하면 기본적으로 모두 초기화를 해야 합니다.

만약 객체 정보가 나중에 나타나는 경우 객체 생성과 동시에 초기화가 힘들 수 있습니다.

이럴 경우에 지연 초기화를 사용합니다.

 

 

2. lateinit

 

lateinit키워드를 사용하면 프로퍼티에 값이 바로 할당되지 않아도 컴파일러에서 이를 허용합니다.

단 lateinit은 다음과 같은 제약사항이 있습니다.

 

  • var로 선언된 프로퍼티에만 사용할 수 있다.
  • 프로퍼티에 대한 게터와 세터를 사용할 수 없다.

다음 예시를 통해 확인해 보도록 하겠습니다.

 

class Person{
    lateinit var name: String
    fun test(){
        if(!::name.isInitialized){ println("Not initialized.") }
        else {{ println("Initialized.") }
    }
}
fun main() {
    var person = Person()
    person.test()
    person.name = "smoh"
    person.test()
}

 

  • lateinit var name: String: 지연된 초기화를 허용합니다.
  • ::name: 프로퍼티 참조를 위해 콜론을 두 개 사용함.
  • .isInitialized: 프로퍼티가 초기화되었는지 확인하는 코틀린 표준 함수 API

위의 코드를 실행하면 결과는 다음과 같습니다.

만약 초기화 없이 사용하면 어떻게 될까요?

코드를 다음과 같이 수정한 후 실행시켜 봅시다.

 

...
fun main() {
    var person = Person()
    println("Name: ${person.name})
}

 

위와 같이 lateinit 프로퍼티인 name이 초기화되지 않았다는 에러가 발생합니다.

 

 

이번에는 생성자를 통해 객체를 지연된 초기화하는 법을 알아보겠습니다.

 

data class Person(var name: String, var age: Int)
lateinit var person: Person
fun main() {
    person = Person("smoh", 28)
    println(person.name + " is "+ person.age.toString())
}

 

  • lateinit : 객체를 지연 초기화하도록 함.
  • person = Person("smoh", 28) : 생성자 호출 시점에서 객체가 초기화됨.

main함수 밖의 person객체는 생성자를 통해 선언되지 않았지만 lateinit에 의해 지연 초기화가 허용됩니다.

 

 

 

반응형

References: Do it! 코틀린 프로그래밍

코틀린에서 Getter와 Setter를 커스텀하는 방법에 대해 알아봅니다.

 

 

1. 사용자 정의 게터 및 세터

 

코틀린에서 게터와 세터는 별도의 작업없이 자동으로 생성되어 "." 를 통해 사용할 수 있습니다.

만약 게터와 세터에 추가적인 작업이 필요하다면 사용자가 직접 정의하여 사용할 수 도 있습니다.

다음 예시에서 확인해 보도록 하겠습니다.

 

class User(_id: Int, _name: String, _age: Int){
    val id: Int = _id
    var name: String = _name
        set(value){
            println("The name was changed.")
            field = value.toUpperCase()
        }
    var age: Int = _age
}

 

  • User(1, "test", 28): 주 생성자를 이용해 객체를 생성합니다. 세터는 사용되지 않았습니다.
  • user.name = "smoh": 세터를 사용해 name의 값을 변경합니다. 대문자로 저장됩니다.

 

위 예시에서 세터는 입력받은 값을 대문자로 변경하고 저장합니다.

결괏값은 다음과 같습니다.

 

 

2. 보조 프로퍼티의 사용

 

임시적으로 사용할 프로퍼티를 선언한 후에 게터나 세터에서 사용할 수 도 있습니다.

다음 예시로 확인해 보겠습니다.

 

class User(_id: Int, _name: String, _age: Int){
    val id: Int = _id
    private var tempName: String? = null
    var name: String = _name
    get(){
        if(tempName == null) { tempName = "NONAME" }
        return tempName ?: throw AssertionError("Asserted by others")
    }
    var age: Int = _age
}
fun main() {
    val user = User(1, "smoh", 28)
    user.name = ""
    println("user.name = ${user.name}")
}

 

  • private var tempName: String? = null: 이름이 null이 되는 경우를 처리하기 위한 보조 프로퍼티

위 예시의 실행 결과는 다음과 같습니다.

 

 

3. 프로퍼티 오버 라이딩

 

프로퍼티는 기본적으로 final로 선언되어 오버 라이딩이 불가합니다. 만약 오버 라이딩을 하고 싶다면 open키워드를 통해 프로퍼티를 선언해야 합니다.

다음 코드를 통해 확인해 봅시다.

 

open class First{
    open val a: Int = 0
        get(){
            println("First a")
            return field
        }
    val b: Int = 0
}
class Second: First() {
    override val a: Int = 0
        get(){
            println("Second a")
            return field +1
        }
}
fun main() {
    val sec = Second()
    println(sec.a)
    println(sec.b)
}

 

  • open val a: Int = 0: 프로퍼티 a를 오버라이드 가능한 상태로 선언합니다.
  • override val a: Int = 0: 프로퍼티 a를 오버라이드 합니다.

위 코드의 실행 결과는 다음과 같습니다.

 

b에는 open 키워드가 없어 오버 라이딩이 불가능합니다. 만약 val로 프로퍼티를 선언했다면 오버라이딩이 불가능합니다.

 

 

 

 

반응형

References: Do it! 코틀린 프로그래밍

코틀린에서 연관(Association)관계, 의존(Dependency)관계, 집합(Aggregation)관계, 구성(Composition)관계 에 대해 알아봅니다.

 

 

1. 클래스간의 관계

 

클래스간의 관계는 다음과 같이 표시할 수 있습니다.

 

또한 클래스간의 관계는 다음과 같이 판별할 수 있습니다.

 

 

  • 연관관계: 클래스간의 참조를 유지하는 경우
  • 의존관계: 클래스간의 참조를 유지하지 않는 경우
  • 집합관계: 연관관계에 있으면서 객체의 생명주기가 서로 유지되는 경우
  • 구성관계: 연관관계에 있으면서 객체의 생명주기가 서로 유지되지 않는 경우

 

 

2. 연관(Association)관계

 

연관관계란 두개의 서로 분리된 클래스가 연결을 갖고 있는것 입니다.

단방향 양방향 연결 모두를 포함하며 서로 다른 생명주기를 가집니다.

다음 예시를 통해 연관 관계에 대해 살펴보겠습니다.

 

class Doctor(val name: String){
    fun patientList(p: Patient){
        println("Doctor: $name, Patient: ${p.name}")
    }
}
class Patient(val name: String){
    fun doctorList(d: Doctor){
         println("Patient: $name, Doctor: ${d.name} ")
    }
}
fun main() {
    val doc = Doctor("DoctorA")
    val pat = Patient("PatientA")
    doc.patientList(pat)
    pat.doctorList(doc)
}

 

  • fun patientList(p: Patient){ / fun doctorList(d: Doctor){: 각 클래스에서 서로 인자를 사용해 참조합니다.
  • val doc = Doctor("DoctorA") / val pat = Patient("PatientA"): 객체는 따로 생성함을 보여줍니다.

위의 코드를 실행시키면 다음과 같은 결과를 확인할 수 있습니다.

 

Doctor와 Patient 클래스는 각각 따로 생성되 독립적인 생명주기를 갖게됩니다. 또한 클래스가 서로 참조하고 있으므로 양방향 참조를 가집니다.

서로 참조는 하고 있지만 객체의 생명주기에 영향을 주지않을 때 연관관계라고 합니다.

 

 

3. 의존(Dependency)관계

 

한 클래스가 다른 클래스에 의존되어 영향을 줄 때 의존관계라 합니다. 예를들어 Doctor 클래스를 생성하려고 할 때 먼저 Patient 클래스가 필요하다면 Doctor는 Patient에 의존하는 관계라고 합니다.

다음 예시를 통해 확인해 봅시다.

 

class Patient(val name: String, var id: Int){
    fun doctorList(d: Doctor){
        println("Patient: $name, Doctor: ${d.name} ")
    }
}
class Doctor(val name: String, val p: Patient){
    val customerId: Int = p.id
    fun patientList(){
        println("Doctor: $name, Patient: ${p.name}")
        println("PatientId: $customerId")
    }
}
fun main() {
    val pat = Patient("PatientA", 1)
    val doc = Doctor("DoctorA", pat)
    doc.patientList()
}

 

  • class Doctor(val name: String, val p: Patient): 이전과 달리 Patient 클래스를 주 생성자에서 받고 있습니다.
  • val doc = Doctor("DoctorA", pat): Doctor 클래스를 생성하기 위해선 이미 생성된 Patient 클래스가 필요합니다.

위 코드의 실행결과는 다음과 같습니다.

 

위의 설명과 같이 Doctor 클래스는 Patient 클래스에 의존하게 됩니다.

 

 

4. 집합(Aggregation)관계

 

집합관계는 연관관계와 유사하지만 한 개체가 특정 개체를 소유한 다는 개념이 추가된 것 입니다.

다음 예제를 통해 살펴보겠습니다.

 

class Pond(_name: String, _members: MutableList<Duck>){
    val name: String = _name
    val members: MutableList<Duck> = _members
    constructor(_name: String): this(_name, mutableListOf<Duck>())
}
class Duck(val name: String){ }
fun main(){
    val pond = Pond("myPond")
    val duck1 = Duck("Duck1")
    val duck2 = Duck("Duck2")

    pond.members.add(duck1)
    pond.members.add(duck2)
    for(duck in pond.members){ println(duck.name) }
}

 

  • _members: MutableList<Duck>: Pond 클래스는 여러 Duck 클래스를 가질 수 있습니다.
  • Pond / Duck: 두 클래스는 서로 생명주기에 영향을 미치지 않습니다.
  • add: Pond 클래스에 Duck를 추가합니다.

위 코드의 실행결과는 다음과 같습니다

연못에는 여러마리 오리가 존재할 수 있습니다. 한 오리는 한 연못에만 존재할 수 있습니다.

결국 Pond는 객체 Duck를 소유하게 됩니다.

 

 

5. 구성(Composition)관계

 

구성관계는 집합관계와 유사하지만 특정 클래스가 어느 한 클래스의 부분이 되는 것 입니다.

부분이 된 하위 클래스는 생명주기가 상위 클래스에 종속됩니다. 따라서 상위 클래스가 삭제되면 하위 클래스도 같이 삭제 되게 됩니다.

다음 예제를 통해 살펴보겠습니다.

 

class Engine(power:String){
    fun start() = println("Engine has ben started.")
    fun stop() = println("Engine has ben stopped.")
}
class Car(val name: String, val power: String){
    private var engine = Engine(power)
    fun startEngine() = engine.start();
    fun stopEngine() = engine.stop();
}
fun main() {
    var car = Car("Tico", "100hp")
    car.startEngine()
    car.stopEngine()
}

 

  •  private var engine = Engine(power): 클래스 내부에서 다른 클래스를 생성했습니다.

위의 코드를 실행한 결과는 다음과 같습니다.

 

위 처럼 프로그래밍을 하면 Engine 클래스는 Car클래스와 함께 생성되며 Car클래스에 생명주기도 종속됩니다. Car 클래스가 삭제되면 Engine 객체도 삭제됩니다.

 

이 관계를 구성 관계라고 합니다.

 

 

 

 

반응형

'Programming' 카테고리의 다른 글

[Kotlin] 26. 지연초기화: lateinit  (0) 2019.08.27
[Kotlin] 25. Custom Getter & Setter  (0) 2019.08.27
[Kotlin] 23. 다형성(Polymorphism)  (0) 2019.08.24
[Kotlin] 22. 상속(Inheritance)  (0) 2019.08.24
[Kotlin] 21. 생성자  (0) 2019.08.24

References: Do it! 코틀린 프로그래밍

코틀린에서의 오버로딩(Overloading)과 오버라이딩(Overriding)에 대해 알아봅니다.

 

 

1. 다형성(Polymorphism)

 

클래스를 상속하다보면 하위 클래스에서 상위 클래스와 똑같은 이름의 프로퍼티나 메서드를 지정할 일이 생깁니다.

하위 클래스에서 이름은 같지만 호출 매개변수가 다르거나 전혀 다른 동작의 메서드를 정의할 필요가 있습니다.

이렇게 이름은 같지만 매개변수가 다르거나 다른 동작을 하도록 하는 것을 다형성(Polymorphism)이라 합니다.

 

 

2. 오버로딩(Overloading)

 

오버로딩은 같은 이름의 메서드가 매개 변수만 달리하여 여러번 재정의 되는것을 말합니다.

덧셈에 대한 오버로딩을 다음 예시를 통해 알아봅시다.

 

class Calc{
    fun add(a: Int, b: Int): Int = a + b
    fun add(a: Double, b: Double): Double = a + b
    fun add(a: Int, b: Int, c: Int): Int = a + b + c
    fun add(a: String, b: String): String = a + b
}
fun main() {
    val calc = Calc()
    println(calc.add(1, 2))
    println(calc.add(1.1, 2.2))
    println(calc.add(1, 2, 3))
    println(calc.add("Hello", "Kotlin"))
}

 

Calc 클래스 내에 add라는 동일한 이름을 가진 메서드가 4개 있습니다. 이 메서드는 모두 다른 매개변수를 갖고 있으며 이를 오버로딩이라 합니다.

위 예시의 실행 결과는 다음과 같습니다.

 

3. 오버라이딩(Overriding)

 

오버라이드는 자식 클래스가 상속 받은 부모 클래스에 있는 메서드를 재정의 하는것을 말합니다.

상속을 하는 부모클래스에는 open 키워드가, 자식 클래스에서 오버라이드 하는 함수에는 override 키워드가 사용됩니다.

 

앞서 구현한 Bird 클래스를 사용하여 오버라이드를 사용해보도록 하겠습니다.

 

open class Bird(var name: String, var wing: Int, var beak: String, var color: String){
    fun fly() = println("Fly wing: $wing")
    open fun sing(vol: Int) = println("Sing vol: $vol")
}
class Parrot: Bird{
    var language: String
    constructor(name: String, wing: Int = 2, beak: String, color: String, language: String = "natural"): super(name, wing, beak, color){
    this.language = language
    }
    fun speak() = println("Speak! : $language")
    override fun sing(vol: Int){
        println("I'm a parrot!")
        speak()
    }
}
fun main() {
    val parrot = Parrot(name="myParrot", beak="short", color="multiple")
    parrot.language = "English"
    parrot.sing(5)
}

 

  • 부모 클래스에서 자식클래스에 override 될 함수임을 나타내기 위해 open 키워드를 입력합니다.
  • 자식 클래스에서 오버라이드 된 함수임을 나타내기 위해 override 키워드를 입력합니다.

Parrot는 Bird에서 상속 된 클래스이며 Parrot클래스 내의 sing은 Bird클래스의 sing을 오버라이드 한 메서드 입니다.

 

위 예시의 결과값은 다음과 같습니다.

 

 

 

반응형

'Programming' 카테고리의 다른 글

[Kotlin] 25. Custom Getter & Setter  (0) 2019.08.27
[Kotlin] 24. 클래스 혹은 객체간의 관계  (0) 2019.08.27
[Kotlin] 22. 상속(Inheritance)  (0) 2019.08.24
[Kotlin] 21. 생성자  (0) 2019.08.24
[Kotlin] 20. 예외 처리  (0) 2019.08.24

References: Do it! 코틀린 프로그래밍

코틀린에서 상속을 알아봅니다.

 

 

1. 상속

 

선언한 클래스가 너무 추상적이라 명확한 한 개체를 정의하기에 모호할 경우에 하위에 새로운 클래스를 추가 해 좀 더 명확한 클래스로 만들 수 있습니다.

이럴 때 상속을 사용하게 되며 추상적인 클래스를 좀 더 명확화 시키기 위해 클래스를 상속받아 좀 더 구체적인 클래스를 만듭니다.

 

아래의 예시를 통해 이전에 만든 Bird 클래스를 이용해 파생 클래스를 만들어 보도록 하겠습니다.

 

open class Bird(var name: String, var wing: Int, var beak: String, var color: String){
    fun fly() = println("Fly wing: $wing")
    fun sing(vol: Int) = println("Sing vol: $vol")
}
class Lark(name: String, wing: Int, beak: String, color: String): Bird(name, wing, beak, color){
    fun singHappy() = println("Happy song!")
}
class Parrot: Bird{
    val language: String
    constructor(name: String, wing: Int, beak: String, color: String, language: String): super(name, wing, beak, color){
        this.language = language
    }
    fun speak() = println("Speak! : $language")
}
fun main() {
    val coco = Bird("myBird", 2, "short", "blue")
    val lark = Lark("myLark", 2, "long", "brown")
    val parrot = Parrot("myParrot", 2, "short", "multiple", "Korean")
    lark.singHappy()
    parrot.speak()
    lark.fly()
}

 

  • 클래스를 상속할 수 있도록 open 키워드를 사용합니다.
  • 상속받은 값을 사용할 것이므로 var키워드는 생략할 수 있습니다. 
  • :Bird(프로퍼티)로 Bird 클래스를 상속받습니다. 자식 클래스의 프로퍼티가 부모 클래스와 동일하면 추가적인 초기화 작업은 필요없습니다.
  • :Bird로 Bird 클래스를 상속 받습니다. 프로퍼티를 별도로 명시하지 않았으므로 부 생성자를 통해 프로퍼티를 초기화 해줘야 합니다. language 프로퍼티가 추가되었으므로 부 생성자 내에서 초기화 시켜줍니다.
  • 클래스내의 메서드를 실행시킵니다. lark의 부모 클래스인 Bird에 fly가 정의되어 있으므로 사용할 수 있습니다.

위의 코드를 실행한 결과는 다음과 같습니다.

 

 

 

 

 

반응형

'Programming' 카테고리의 다른 글

[Kotlin] 24. 클래스 혹은 객체간의 관계  (0) 2019.08.27
[Kotlin] 23. 다형성(Polymorphism)  (0) 2019.08.24
[Kotlin] 21. 생성자  (0) 2019.08.24
[Kotlin] 20. 예외 처리  (0) 2019.08.24
[Kotlin] 19. 라벨  (0) 2019.08.23

+ Recent posts