반응형
모든 내용은 Do it! 코틀린 프로그래밍을 바탕으로 정리한 것입니다.
추상 클래스와 인터페이스
추상 : 구체적이지 않은 것
▶ 추상 클래스와 인터페이스 모두 대략적인 설계 명세를 가지고 몇 가지 기본적인 부분은 구현할 수 있으나, 하위에서 더 자세히 구현해야 함.
추상 클래스
- abstract 키워드와 함께 선언
- 일반적인 객체를 생성하는 방법으로 인스턴스화 X
- 프로퍼티에 상태 정보 저장 가능
abstract class Vehicle(val name: String, val color: String, val weight: Double) {
abstract var maxSpeed: Double // 추상 프로퍼티
var year = "2018" // 일반 프로퍼티
// 추상 메서드
abstract fun start()
abstract fun stop()
// 일반 메서드
fun displaySpecs() {
println("Name: $name, Color: $color, Weight: $weight, Year: $year, Max Spped: $maxSpeed")
}
}
- abstract 키워드 자체가 상속과 오버라이딩을 허용하기 때문에 open 키워드를 사용할 필요가 없음
- 추상 클래스의 프로퍼티나 메서드도 abstract로 선언될 수 있음 : 추상 프로퍼티, 추상 메서드
- 클래스에 추상 프로퍼티나 메서드가 하나라도 있다면 해당 클래스는 추상 클래스
- name, color, weight, year는 일반 프로퍼티 / maxSpeed는 추상 프로퍼티
- displaySpecs()는 일반 메서드 / start(), stop()은 추상 메서드
abstract class Printer {
abstract fun print()
}
val myPrinter = object: Printer() {
override fun print() {
println("출력합니다")
}
}
fun main() {
myPrinter.print()
}
- 추상 클래스로부터 하위 클래스를 생성하지 않고 단일 인스턴스로 객체 생성 가능
- object 키워드 : 익명 객체를 지정함
- 콜론(:) 오른쪽에 생성자 이름을 사용하고 블록에서 관련 메서드를 오버라이딩해 구현해야 함
추상 클래스의 한계
- 하위 클래스는 상속을 하나만 허용함 → 2개 이상의 클래스로부터 프로퍼티나 메서드를 상속받을 수 없음
- 상위 클래스와 하위 클래스에 강한 연관이 생기면서 하위 클래스는 상위 클래스에 영향을 받음
인터페이스
- interface 키워드와 함께 선언
- abstract로 정의된 추상 메서드나 일반 메서드가 포함됨
- 다른 객체 지향 언어와는 다르게 메서드에 구현 내용이 포함될 수 있음
- 프로퍼티를 통해 상태를 저장할 수는 없음
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 cate a tuna can!")
}
}
fun main() {
val obj = Cat("small")
println("Pet Category: ${obj.category}")
obj.feeding()
obj.patting()
}
- 추상 클래스와 달리 abstract를 붙이지 않아도 기본적으로 추상 프로퍼티와 추상 메서드로 지정됨
- Cat 클래스는 Pet 인터페이스를 구현한 클래스
- 인터페이스의 구현은 클래스의 상속과 동일하게 콜론(:)을 사용
- 추상 프로퍼티와 추상 메서드를 override 키워드를 통해 구현
게터를 구현한 프로퍼티
인터페이스는 프로퍼티에 값을 지정할 수 없음.
그렇다면 val로 선언된 프로퍼티의 값은? 초기화 할 수는 없지만 getter를 통해 필요한 내용을 구현
interface Pet {
var category: String
val msgTags: String
get() = "I'm your lovely pet!"
...
}
fun main() {
...
println("Pet MsgTags: ${obj.msgTags}")
...
}
인터페이스 구현의 필요성
open class Animal(val name: String)
class Dog(name: String, override var category: String) : Animal(name),Pet {
override fun feeding() {
println("Feed the dog a bone")
}
}
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)
}
- playWithPet() : 놀고자 하는 동물에 따라 매개변수를 다르게 정한 오버로딩되 메서드
- dog, cat 클래스에 대해 필요한 playWithPet()
- 애완동물의 종류가 늘어난다면 많은 수의 오버로딩 된 메서드가 필요함
→ 이때 필요한 것이 인터페이스
interface Pet {
var category: String // 추상 프로퍼티
val msgTags: String
get() = "I'm your lovely pet!"
var species: String
...
}
class Cat(name: String, override var category: String) : Pet {
override var species: String = "cat"
override fun feeding() {
println("Feed the cate a tuna can!")
}
}
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 Master {
fun playWithPet(pet: Pet) {
println("Enjoy with my ${pet.species}")
}
}
- 인터페이스에 종을 위한 프로퍼티를 선언하고 이것을 이용해 어떤 애완동물과 놀게 될지 알 수 있음
- Master 클래스의 playWithPet()은 각 애완동물에 따라 오버로딩을 하지 않아도 됨
- 기존의 Master 클래스가 Cat/Dog 클래스에 의존적이었으나 인터페이스를 통해 의존성을 제거함
여러 인터페이스의 구현
interface Bird {
val wings: Int
fun fly()
fun jump() {
println("bird jump!")
}
}
interface Horse {
val maxSpeed: Int
fun run()
fun jump() {
println("jump!, max spped: $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()
}
- Bird, Horse라는 2개의 인터페이스로부터 Pegasus 클래스를 정의함
- 기본 구현되어 있는 메서드는 필요에 따라서만 오버라이딩할 수 있음
- 이름이 동일한 경우 : super<인터페이스 이름>.메서드()
인터페이스의 위임
인터페이스에서도 by 위임자를 사용할 수 있음
interface A {
fun functionA() { }
}
interface B {
fun functionB() { }
}
class C(val a: A, val b: B) {
fun functionC() {
a.funcitonA()
b.funcitonB()
}
}
현재 코드에서 클래스 C는 funcitonA()와 functionB()를 접근하기 위해 a, b 변수를 사용했음
class C(val a: A, val b: B): A by a, B by b {
fun functionC() {
functionA()
functionB()
}
}
by 위임자를 사용하여 a, b를 인터페이스 A, B에 위임함으로서 해당 메서드를 사용할 때 점(.) 표기법 접근 없이 사용할 수 있음
위임을 이용한 멤버 접근
Person 클래스가 상속과 같은 형태로 위임을 사용하고 있음
interface Nameable {
var name: String
}
class StaffName : Nameable {
override var name: String = "Jun"
}
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()
}
반응형
'Kotlin > Kotlin 프로그래밍' 카테고리의 다른 글
[Kotlin] 배열(Array) (0) | 2021.05.18 |
---|---|
[Kotlin] 제네릭(Generic) (0) | 2021.05.18 |
[Kotlin] 다양한 클래스와 인터페이스 (3) (0) | 2021.05.13 |
[Kotlin] 다양한 클래스와 인터페이스 (2) (0) | 2021.05.10 |
[Kotlin] 프로퍼티와 초기화 (0) | 2021.04.20 |
[Kotlin] 클래스와 객체 (2) (0) | 2021.04.19 |
[Kotlin] 클래스와 객체 (1) (0) | 2021.04.16 |
[Kotlin] 프로그램의 흐름 제어 : 조건문, 반복문, 예외처리 (0) | 2021.04.14 |
댓글