본문 바로가기
언어/Kotlin

[Kotlin 문법 종합] - 인터페이스, 추상 클래스

by 젼젼39 2024. 3. 5.

 

1. 인터페이스

    : 부모 클래스와 별개로 다른 그룹과 겹치는 기능 구현 시 사용...

    - 근본적인 공통점을 상속받고, 추가적인 기능들은 인터페이스로 추가
    - 추상메소드 사용 권장 (구현 시 정의, 구현 필수)

//인터페이스는 다중구현 가능, 클래스는 다중상속 불가능
//인터페이스는 그 자체의 인스턴스 필드를 갖지 못함
//인터페이스 : 인터페이스 가능 

interface WaterBirdBehavior {
	//변수 값을 가질 수는 없음. 상수는 가질 수 있어도
    
    fun swim()
    //추상메소드 쓰는 게 좋음...
//    fun swim() {
//        println("수영 합니다")
//    }
}
fun main() {
    var bird = Bird("새")
    var chicken = Chicken("닭")
    var duck = Duck("오리")

    bird.fly()
    chicken.fly()
    duck.swim()
}

open class Bird(name:String) {
    var name: String = ""

    init {
        this.name = name
    }

    fun fly() {
        println("${name} 날아요~")
    }
}

class Chicken(name: String) : Bird(name) {}

class Duck(name: String) : Bird(name), WaterBirdBehavior {
    	//java와 다르게 클래스 옆에 implements ~~ 안해도 되나봄
        //구현할 때 정의 필수, 구현 필수
    override fun swim() {
        println("${name}가 수영해요~")
    }
}

//프로퍼티는 오직 추상 메서드로 선언(abstract 키워드 없어도 추상)
        단, val로 선언된 프로퍼티는 get() 통해 필요한 내용 구현 가능, 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() //기본 메서드
}

//Pet Category : small
//Feed the cat a tuna can
//Keep Patting
...
interface Pet{
    var category: String
    val msgTags: String
        get()= "I'm your pet"
    
    fun feeding()
    fun patting(){
        println("Keep patting")
    }
}
...
println("${obj.msgTags}")
...

    - msgTags 초기화는 불가능하지만 get() 통해 반환값을 지정할 수 있음(value를 저장하지는 못함)

//메서드는 추상, 일반(구현부 포함) 메서드 모두 가능

//인터페이스와 오버로딩

interface Pet{
    var category: String
    val msgTags: String
        get()= "I'm your pet"
        
    var species: String 	//종을 위한 프로퍼티
    
    fun feeding()
    fun patting(){
        println("Keep patting")
    }
}

open class Animal(val name: String)

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 snack")
    }
}

class Master{

    //species 한 뒤에는
    fun playWithPet(pet: Pet){ 	//인터페이스를 객체로 매개변수를 지정
        println("Enjoy with my ${pet.species}")
    }

    fun playWithPet(dog: Dog){ 		// 종류에 따라 오버로딩
        println("play with Dog")
    }
    fun playWithPet(cat: Cat){
        println("play with Cat")
    }
}

fun main(){
    val master = Master()
    val dog = Dog("탄이", "small")
    val cat = Cat("무", "big")
    master.playWithPet(dog)
    master.playWithPet(cat)
}

    - fun playWithPet(pet: Pet) 부분은 해당 객체의 실제 유형에 따라 실행되는 코드가 결정됨

 

//여러개의 인터페이스를 상속받았는데 이미 구현되어있는 동작을 override하려고 함,
    근데 이름이 인터페이스끼리 겹친다면

class 클래스명 : 인페1, 인페2{

    override fun jump(){
        super<구현된동작을 상속받아올 인터페이스명>.동작이름()
        println("do it")
    }
}

fun main(){
    val ex1 = 클래스명()
    ex1.jump()
}

 

    //by 를 통한 인터페이스의 위임

interface A {
    fun functionA()
}

interface B {
    fun functionB()
}

class C(val a: A, val b: B) {
    fun functionC() {
        a.functionA()
        b.functionB()
    }
}

    ^ 위는 functionA(), functionB() 에 접근하기 위해 a, b 변수(각각의 인터페이스를 구현한 객체)를 사용해야 함

class DelegatedC(a: A, b: B): A by a, B by b{
    fun functionC(){
        functionA()
        functionB()
    }
}

    ^ a, b를 각각 인터페이스 A, B에 위임해서 메서드 사용 시 . 없이도 사용할 수 있게 됨

//인터페이스를 구현한 클래스가 존재할 때, 다른 클래스에서 매개변수에 인터페이스를 위임해 와 사용 가능

interface Nameable{
    var name: String
    fun fly()
}

class StaffName : Nameable {
    override var name: String = "Sean"
    override fun fly(){
        println("flying")
    }
}

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) 	//여기서 StaffName 클래스의 name에 접근
    person.run() 		//여기서 Work 클래스의 run 접근
    person.fly()
}

//Sean
//work...
//flying

 

//커피메이커 예시

interface Heater{
    fun on()
    fun off()
    fun isHot() : Boolean
}
class ElectricHeater(var heating: Boolean = false) : Heater {
    override fun on() {
        println("[ElectricHeater] heating...")      //5
        heating = true
    }

    override fun off() {
        heating = false
        println("ElectricHeater 클래스의 off 함수")       //9
    }

    override fun isHot() : Boolean {
        println("ElectricHeater 클래스의 isHot 함수")         //6
        return heating
    }
}
interface Pump {
    fun pump()
}
class Thermosiphon(heater: Heater) : Pump, Heater by heater { // 위임의 사용
    override fun pump() {
        if (isHot()) {
            println("[Thermosiphon] pumping...");       //7
        }
    }
}
interface CoffeeModule {
    fun getThermosiphon() : Thermosiphon
}
class MyDripCoffeeModule : CoffeeModule {
    companion object {
        val electricHeater: ElectricHeater by lazy { // lazy를 이용한 지연 초기화 기법 사용
            println("MyDripCoffeeModule 클래스의 electricHeater 상수")        //4
            ElectricHeater()
        }
    }

    private val _thermosiphon : Thermosiphon by lazy {
        println("MyDripCoffeeModule 클래스의 private _thermosiphon 상수")         //3
        Thermosiphon(electricHeater)
    }

    override fun getThermosiphon() : Thermosiphon {
        println("MyDripCoffeeModule 클래스의 getThermosiphon 함수")       //2
        return _thermosiphon
    }
}
class CoffeeMaker(val coffeeModule: CoffeeModule) {
    fun brew() {
        val theSiphon: Thermosiphon = coffeeModule.getThermosiphon()
        theSiphon.on()
        theSiphon.pump()
        println("Coffee, here! Enjoy!~")        //8
        theSiphon.off()
    }
}

fun main() {
    val coffeeMaker = CoffeeMaker(MyDripCoffeeModule())     //여기선 공간만들기인듯
    println("순서확인")     //1
    coffeeMaker.brew()
}
순서확인
MyDripCoffeeModule 클래스의 getThermosiphon 함수
MyDripCoffeeModule 클래스의 private _thermosiphon 상수
MyDripCoffeeModule 클래스의 electricHeater 상수
[ElectricHeater] heating...
ElectricHeater 클래스의 isHot 함수
[Thermosiphon] pumping...
Coffee, here! Enjoy!~
ElectricHeater 클래스의 off 함수

 

 

2. 추상 클래스

    : 선언 등의 대략적인 설계 명세, 공통의 기능을 구현한 클래스

    - 하위 클래스에서 추상 클래스의 내용을 더 구체화해야 함
    - abstract 키워드 사용
    - 일반적인 객체를 생성하는 방법으로 인스턴스화할 수 없음
    - 해당 추상 클래스를 상속하는 하위 클래스가 어떻게 만들어져야 하는지를 나타내는 용도로 사용함

    - 클래스, 프로퍼티, 메서드 모두 abstract로 선언할 수 있음 (초기화 or 구현이 필요)

abstract class Printer{
    abstract fun print() 	//추상 메서드
}

val myPrinter = object: Printer(){ 	//객체 인스턴스
    override fun print(){ 	//추상 메서드 구현
        println("printing!")
    }
}

fun main(){
    myPrinter.print()
}