Uknow's Lab.
article thumbnail

 

추상적인게 뭘까.

 

관념적. 개념적.

구체적의 반댓말로도 볼 수 있겠네요.

그럼, 추상화는 뭘까요?

관념적이고, 개념적이게 만드는 것이죠.

즉, 사물이나 현상, 개념에서 공통적인 특성을 묶는 것을 의미합니다.

 

 

추상 클래스 (Abstract Class)

추상 클래스는 하나 이상의 추상 메소드를 갖고 있는 클래스입니다.

추상 메소드는 또 뭘까요?

추상 메소드(Abstract Method)는 구현부(몸체, 중괄호 안 내용)를 가지지 않는 미완성의 메소드입니다.

 

추상 클래스는 하나 이상의 추상 메소드를 가진 클래스로 이루어지며,

이러한 추상 메소드는 추상 클래스를 상속받은 자식 클래스에서 구체화 됩니다.

추상 클래스를 상속받은 자식 클래스는

모든 추상 메소드를 오버라이딩하여 구체화해야 합니다. 강제성이 부여되지요.

 

abstract class Animal {
    fun move() { println("이동!") }
    fun eat() { println("냠냠") }
    abstract fun talk() // 울음소리(짖는소리)는 동물마다 다르기 때문에 하위 클래스에서 재정의 함
}

class Bird : Animal() {
    override fun talk() {
        println("짹짹")
    }
}

class Dog: Animal() {
    override fun talk() {
        println("멍멍")
    }

}

class Cat : Animal() {
    override fun talk() {
        println("집사야 츄르 내놔")
    }
}

 

위와 같이 코틀린에서는 자바와 마찬가지로 abstract 키워드를 통해

추상 클래스와 추상 메소드를 정의할 수 있습니다.

당연하게도, 하위 클래스에서 재정의(구체화)가 필요하므로, private 키워드는 붙일 수 없습니다.

 

추상 클래스는 공통되는 개념을 모아둔, 일종의 미완성 설계도 입니다.

이동하기, 먹기 등은 모두 공통적인 특성이기에, 그냥 부모 클래스로부터 상속받아 공통적으로 사용합니다.

하지만, 울음소리 내기(짖기)는 어떨까요?

 

모두 공통적인 특성이지만, 동시에 모두 울음소리가 다릅니다.

따라서 하위 클래스에서 재정의가 필요한 것이지요.

 

 

추상 클래스는 미완성 설계도이다.

class cat {
	fun meow() { println("meow") }
}

class Bird {
	fun chatter() { println("chatter") }
}

class Dog {
	fun woof() { println("woof") }
}

 

동물들의 울음소리를 모두 다르게 한다면,

각각의 메소드의 일관성이 없어 유지보수나 사용하기가 힘들어 질 수 있습니다.

반면, 추상 메소드 talk()를 갖고있는 추상 클래스를 상속받는다면,

모두 talk()라는 통일된 이름으로 사용이 가능합니다.

 

 

 

인터페이스 (Interface)

인터페이스는 추상 메소드로 이루어진 집합입니다.

모든 메소드가 추상 메소드로 이루어져 완벽한 추상화를 제공… 했습니다만,

자바8 버전에 들어오면서 일반 메소드 구현이 가능해서 정체성이 모호해졌다는 말이 나오기도 합니다.

코틀린의 경우도 자바8 이상의 버전처럼 인터페이스 내 메소드 구현이 가능합니다.

 

하지만, ‘추상 메소드’를 하위 클래스에서 구현하게 한다는

인터페이스의 기본적인 쓰임새 만큼은 여전히 그대로입니다.

 

인터페이스의 경우, 하나만 상속이 가능하던 추상 클래스와 달리, 다중 상속이 가능합니다.

 

 

// 인터페이스 상속
interface Fly {
    fun fly()
}

class Bird: Fly {
    override fun fly() {
        println("날기")
    }
}

======================


// 클래스 + 인터페이스 상속
abstract class Animal {
    abstract fun talk()
}

interface Fly {
    fun fly()
}

class Bird : Animal(), Fly {
    override fun talk() {
        println("짹짹")
    }

    override fun fly() {
        
    }
}

 

인터페이스의 상속은 implements를 사용했던 자바와 달리,

클래스 상속과 마찬가지로 : 키워드를 사용해 상속할 수 있습니다.

 

이 때, 클래스는 소괄호() 가 붙으나, 인터페이스는 괄호가 붙지 않음으로써

클래스와 인터페이스를 구분할 수 있습니다.

 

 

abstract class Animal {
    abstract fun talk()
}

interface Fly {
    fun fly()
}

interface Beak {
    fun beak()
}

class Bird : Animal(), Fly, Beak {
    override fun fly() {

    }

    override fun beak() {
        
    }

    override fun talk() {
        
    }
}

 

한 개만 상속받을 수 있던 추상 클래스와 달리, 인터페이스는 두 개 이상의 다중 상속도 가능하며,

클래스 상속 + 복수개의 인터페이스 상속도 가능합니다.

 

 

부록. 추상 클래스 (Abstract Class) vs 인터페이스 (Interface)

객체지향의 추상 클래스와 인터페이스는 소프트웨어공학과 아주 밀접한 관련이 있습니다.

 

이 둘은 메소드를 하위 클래스에서 강제적으로 구현해야 한 다는 점에서 유사하나,

이 둘의 목적은 약간 다릅니다.

 

추상 클래스는 is a ~ “~이다”, 인터페이스는 has a ~, 즉 “~을 할 수 있는” 으로 볼 수 있습니다.

표현에 따라 is a kind of(~의 종류), be able to~(~를 할 수 있는)으로 말하기도 합니다.

 

상속파트에서, 상속은 사실 일상생활에서 이야기하는 ‘상속’이라는 의미보다는

‘확장’에 가깝다고 생각한다는 이야기를 했습니다.

 

부모클래스가 A, 자식클래스가 B라 한다면,

B는 A이다 라는 관계가 성립되어야 합니다.

 

할아버지로부터 유산을 물려받은 아버지. 하지만, 아버지는 할아버지다. 라는 표현은 뭔가 이질감이 듭니다.

하지만 확장으로 생각한다면?

동물 → 개

동물 → 새

동물 → 고양이

개는 동물이다. 새는 동물이다. 고양이는 동물이다. B는 A다. 라는 관계를 만족시킵니다.

자바의 상속 키워드가 extends 인 것을 생각하면, 매우 적절하지요.

 

 

동물

  • 척추 동물
    • 어류
      • 물에서 산다
      • 아가미로 호흡한다
      • ex> 고등어, 연어 등
    • 포유류
      • 새끼를 낳는다
      • ex> 개, 고양이 등
    • 조류
      • 알을 낳는다
      • 날 수 있다
      • ex> 참새, 제비 등
    • 기타 등등
  • 무척추 동물
    • 강장동물
    • 연체동물
    • 기타 등등

 

중고등학교때 생명과학 시간의 기억을 더듬더듬 꺼내봤습니다.

일반적으로 생각했을 때, 동물들의 분류는 상속과 확장에 해당합니다.

동물은 척추동물, 무척추동물로 나누고,

척추동물은 조류, 양서류, 파충류, 포유류, 어류 또 나뉩니다.

그 안에서도 목, 괴, 종, 속, 종으로 또다시 나뉘지요.

 

이들간의 관계는 언뜻 보면 상속(확장)으로 생각할 수 있고,

포유류는 새끼를 낳고, 조류는 알을 낳고, 날기 속성을 추가하고,

하나씩 상속(확장)해가며 하나씩 기능을 추가하면 될 것 같습니다.

 

 

포유류는 새끼를 낳으니까! 동물 클래스를 상속해서 새끼낳기 추상 메소드를 추가하자!

abstract class 동물 {
    abstract fun 먹다()
    abstract fun 움직이다()
}

// 포유류는 새끼를 낳으니, 동물 클래스를 상속하고, 새끼낳기 속성을 추가한 새로운 추상 클래스를 만들자.
abstract class 포유류 : 동물() {
    abstract fun 새끼를낳다()
}



// 개 : 먹고, 움직이고, 새끼를 낳는다.
class 개 : 포유류() {
    override fun 새끼를낳다() { println("새끼를 낳다") }
    override fun 먹다() { println("우걱우걱") }
    override fun 움직이다() { println("산책시켜줘") }
}

// 고양이 : 먹고, 움직이고, 새끼를 낳는다.
class 고양이 : 포유류() {
    override fun 새끼를낳다() { println("새끼를 낳다") }
    override fun 먹다() { println("집사야 츄르 줘") }
    override fun 움직이다() { println("(책상 위 컵을 떨어뜨린다)") }
}

// 오리 너구리 : 먹고, 움직이고, 새끼를... 엥 전 포유류인데 알 낳는데요
class 오리너구리 : 포유류() {
    override fun 새끼를낳다() { println("엥 전 알 낳는데여") } // 못하는데 상위 클래스에서 추상 메소드 있으므로 정의 해야함...
    override fun 먹다() { println("우걱우걱") }
    override fun 움직이다() { println("이동") }
}

 

그런데, 문제가 생겼습니다.

 

 

얘 대체 뭐임?

 

 

신이 생명체를 만들 때, 술먹고 스파게티 코드로 짠거 아니냐는 우스갯소리가 있는 오리너구리.

특이하게도 포유류지만, 새끼가 아니라 알을 낳습니다.

 

추상 클래스란, 일반적으로 '절대적' 특성입니다.

하지만 포유류니까 새끼를 낳는다는건 절대적이지 않습니다.

 

그럼에도, 새끼를 낳다 속성을 가진 추상메소드 '포유류'를 상속했기에,

하위 클래스인 오리너구리에서 강제적으로 구체화해야 합니다.

 

 

박쥐는 포유류인데 날 수 있어요. 펭귄은 조류인데 못날아요.

abstract class 동물 {
    abstract fun 먹다()
    abstract fun 움직이다()
}

abstract class 조류: 동물() {
    abstract fun 날다()
}

class 박쥐 : 포유류() {
    override fun 새끼를낳다() { }
    override fun 먹다() { }
    override fun 움직이다() { }
    // 박쥐도 날 수 있는데 날기 속성이 조류에만 있어요.
}

class 펭귄 : 조류() {
    override fun 먹다() { println("정어리 냠냠") }
    override fun 움직이다() { println("뒤뚱뒤뚱") }
    override fun 날다() { println("에 저 못날아여") }
}

 

날 수 있는 건, 조류만의 절대적인 특성이니까 조류에 날기 속성을 추가하자!

-> 박쥐는 조류인데 날 수 있어요

-> 펭귄은 조류인데 못날아요 T.T

 

'새끼/알을 낳는다', '날다', '수영하다'와 같은 속성은,

특정 분류의 공통적인 속성이지만, 어디까지나 일반적으로 그렇다는 것이지,

먹다/움직이다와 같이 절대적인 공통 속성은 아닙니다.

 

하지만, 추상 클래스는 단 한개의 클래스만 상속이 가능하니, 복수개의 클래스를 상속받을 수도 없습니다.

 

 

그럼, 선택적으로 복수개의 특성을 가지려면?

abstract class 동물 {
    abstract fun 먹다()
    abstract fun 움직이다()
}

abstract class 포유류:동물()
abstract class 조류:동물()

// 생물의 특성
interface 날기 { fun 날기() }
interface 새끼낳기 { fun 새끼낳기() }
interface 알_낳기 { fun 알_낳기() }


class 박쥐 : 포유류(), 새끼낳기, 날기 {
    override fun 먹다() { }
    override fun 움직이다() { }
    override fun 새끼낳기() {  }
    override fun 날기() { println("저도 이제 날 수 있어요.") }
}

class 펭귄 : 조류(), 알_낳기 {
    override fun 먹다() { }
    override fun 움직이다() { }
    override fun 알_낳기() { }
		// 조류지만 날 수 없으므로 날기 인터페이스를 상속받지 않았어요.
}

class 오리너구리: 포유류(), 알_낳기 {
    override fun 먹다() { }
    override fun 움직이다() { }
    override fun 알_낳기() { println("저는 포유류지만 알을 낳아요") }
}


/*
	괄호가 있으면 클래스, 없으면 인터페이스 입니다.

	class 오리너구리: 포유류(), 알_낳기
	포유류() -> (추상)클래스
	알_낳기 -> 인터페이스
*/

 

추상클래스 ‘동물’을 상속받아 먹기/움직이기 특성을 가져오면서,

인터페이스를 사용해 각 생물의 특성을 선택적으로, 단수 혹은 복수개의 특성을 선택적으로 상속받았습니다.

이와 같이, 추상 클래스와 인터페이스는 하위 클래스에서 강제로 추상 메소드를 구현해야 한다는 점에서 같으나, 사용하는 목적에 있어 차이가 있습니다.

 

추상 클래스 -> is a ~, is a kind of "~이다, ~의 종류"
인터페이스 -> has a ~, be able to ~ "~을 할 수 있는"

 

추상 클래스는 같은 부모의 클래스를 상속하면서, 공통되는 기능을 구현할 때,

인터페이스는 다른 부모의 클래스를 상속하면서, 단수/복수개의 공통되는 기능을 구현할 때 사용됩니다.

profile

Uknow's Lab.

@유노 Uknow

인생은 Byte와 Double 사이 Char다. 아무말이나 해봤습니다.