[코틀린 완전정복] 추상 클래스와 인터페이스

Dohyeon Ko·2021년 9월 8일
5

코틀린

목록 보기
1/7
post-thumbnail

"황영덕 저자"의 코틀린 프로그래밍을 읽고 정리한 글입니다.

클래스 를 더욱 구조화 하기 위해서는 특수한 목적을 가진 여러 클래스를 정의해야 한다. 오늘은 그런 목적을 가진 클래스를 살펴보려고 한다.

추상 클래스

추상 클래스 VS 인터페이스

추상 클래스대략적인 설계의 명세공통의 기능을 구현한 클래스이다. 즉, 구체적이지 않은 것이다. 추상 클래스 를 상속하는 하위 클래스 는 추상 클래스의 내용을 더 구체화 해야 한다.

오잉? 그럼 추상 클래스랑 인터페이스는 같은 거 아냐? 🤔

엄밀히 말하면 다르다. 인터페이스 역시 대략적인 설계 명세를 구현하고 인터페이스 를 상속하는 하위 클래스 에서 이를 구체화하는 것은 동일하다. 하지만 인터페이스에서는 프로퍼티의 상태 정보를 저장할 수 없다.

다시 말하면 인터페이스 에서는 프로퍼티의 초기화 가 불가능하다는 것이다.

interface Vehicle {
	val name : String
	val color : String
	val weight : Double
}

interface Pet {
	val name : String = "puppy" // 불가능!
}

Pet Interface 의 경우 정의와 동시에 초기화까지 해주고 있는데 인터페이스 에선 불가능하다.

추상 클래스 사용해보기

추상 클래스 는 위에서 말한 것처럼 구체화되지 않은 클래스이기 때문에 일반적인 객체를 생성하는 방법으로 인스턴스화될 수 없다. 일단 한번 정의해보자. 우선 추상 클래스 를 정의하기 위해선 abstract 라는 키워드를 사용해야 한다. 또한 클래스 내에서의 프로퍼티나 메소드도 abstract로 선언할 수 있다. 이를 상속받는 클래스에서 구체화하겠다는 의미가 된다. 하지만 이를 사용하기 위해선 해당 클래스가 추상 클래스가 되어야한다.

// abstract로 정의한 추상 클래스이다. 주생성자를 사용했다.
abstract class Vehicle(val name : String, val color : String, val weight : Double) {
	// abstract로 정의한 추상 프로퍼티이므로 하위 클래스에서 반드시 재정의해야한다.
	abstract var maxSpeed : Double 
	
	// 초기값을 갖는 일반 프로퍼티 (인터페이스에서는 불가능)	
	var year = "2021"

	// abstract로 정의한 추상 메소드이므로 하위 클래스에서 반드시 재정의해야한다.
	abstract fun start()
	abstract fun stop()
	
	fun displaySpecs() {
		println("Name : $name, Color : $color, Weight : $weight, Year : $year, MaxSpeed : $maxSpeed")
	}
}

Vehicle 클래스abstract로 정의한 추상 클래스이므로 기본적인 설계만 정의하고 있다. abstract 를 사용한 maxSpeed, start(), stop()은 반드시 하위 클래스에서 재정의 해줘야한다.

여기서 잠깐!
클래스를 상속하기 위해선 부모 클래스를 open 키워드로 정의해야 하는데 추상 클래스에서도 해줘야 하나요? 🤔

이미 코드를 통해 느꼈겠지만, 추상 클래스 에서는 open 키워드 를 사용하지 않아도 된다.
추상 프로퍼티추상 메소드 도 마찬가지다!

위에서 정의한 Vehicle 클래스를 상속해보자.

class Car(name : String, color : String, weight : Double, override var maxSpeed : Double) : Vehicle(name, color, weight) {
	override fun start() {
		// 재정의
		println("Car start")
	}

	override fun stop() {
		// 재정의
		println("Car Stop")
	}
}

class Bicycle(name : String, color : String, weight : Double, override var maxSpeed : Double) : Vehicle(name, color, weight) {
	override fun start() {
		// 재정의
		println("Bicycle start")
	}

	override fun stop() {
		// 재정의
		println("Bicycle Stop")
	}
}

fun main() {
	val car = Car("Matiz", "Yellow", 1000, 150)
	val bicycle = Bicycle("Bike", "Red", 150, 100)
	
	// 새로운 값 할당
	car.year = "2020"
	
	car.displaySpec()
	car.start()
	bicycle.displaySpec()
	bicycle.start()

displaySpec추상 클래스 가 갖고 있던 일반 메소드 이다. startstop추상클래스 를 상속받은 자식 클래스 에서 오버라이딩한 메소드 이다. 추상 클래스에서 abstract로 정의한 프로퍼티나 메소드들은 자식 클래스에서 반드시 재정의되어야한다.

그렇다면 혹시, 추상 클래스를 상속받는 하위 클래스를 정의하지 않고도 추상 클래스를 사용할 수 있을까? 🤔

사용할 수 있다! object 키워드를 사용하면 된다. (object 이 친구 만능이네...?)

abstract class Printer {
	abstract fun print()
}

val myPrinter = object : Printer() {
	override fun print() {
		println("print 메소드가 재정의되었습니다")
	}
}

fun main() {
	myPrinter.print()
}

인터페이스

코틀린 에서는 다른 언어와 다르게 메소드 에 구현 내용을 포함할 수 있다.

interface Pet {
	var category : String // 추상 프로퍼티
	fun feeding()  // 추상 메소드
	fun patting() { // 구현부를 포함할 수 있다. 구현부를 포함하면 일반 메소드
		println("Keep patting")
	}
}

추상클래스 와 다르게 abstract 키워드를 사용하지 않는다.
또한 앞서 말한 것처럼 상태를 저장할 수가 없으므로 프로퍼티에 기본 값을 가질 수가 없다.

인터페이스 사용하기

class Cat(override var category : String) : Pet { // 주생성자를 이용
	override fun feeding() {
		println("Feeding 메소드가 구현되었습니다.")
	}
}

fun main() {
	val obj = Cat("Small")
	obj.feeding() // 구현된 메소드
		obj.patting()// 일반 메소드
}

인터페이스에서 값 저장하기

아니 선생님, 인터페이스에서는 프로퍼티에 값을 저장할 수 없다고 하셨잖아요 😥

하지만 예외가 있다. val 로 선언한 프로퍼티게터 를 통해서 필요한 내용을 구현할 수 있다.

interface Pet {
	var category : String // 추상 프로퍼티
	val message : String // val로 선언하면 게터의 구현이 가능하다.
		get() = "I'm cutty"

	fun feeding()  // 추상 메소드
	fun patting() { // 구현부를 포함할 수 있다. 구현부를 포함하면 일반 메소드
		println("Keep patting")
	}
}

게터 가 사용 가능하지만 그렇다고 보조필드 가 사용가능한 것은 아니다.

인터페이스의 이점

인퍼페이스를 사용하면 뭐가 좋을까? 일단 코드의 재사용성이 올라간다. 한마디로 간결해진다는 점이다. 또 인터페이스를 이용해서 클래스 간의 의존성을 제거할 수 있다. 뿐만 아니라 다중 상속도 가능하다. 코틀린에서는 자바와 다르게 클래스에서 부모 클래스를 상속할 때는 1개의 클래스만 가능하다. 하지만 인터페이스를 활용하면 다중 상속이 가능해진다!

아래는 두 개의 인터페이스를 통해 다중 상속을 구현한 코드이다.

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("The Pegasus Fly!")
	}
	override fun run() {
		println("The Pegasus Run!")
	}
	override fun jump() {
		super<Horse>.jump() {
			println("The Pegasus jump")
		}
	}
}

구현부가 있는 메소드의 경우 필요에 따라 오버라이딩을 진행하면 된다. 만약 인터페이스에서 구현한 메소드의 이름이 같은 경우 super<인터페이스 이름>.메소드 명 을 통해 구분할 수 있다.

profile
티스토리로 옮겼어요! (https://codekodo.tistory.com)

0개의 댓글