kang9366
Repository
kang9366
글쓰기
설정
전체 방문자
오늘
어제
  • 분류 전체보기 (143)
    • Programming (70)
      • Java (1)
      • C++ (11)
      • Kotlin (12)
      • Keras (2)
      • Android (34)
      • Jetpack Compose (1)
      • Error Solution (7)
    • CS (36)
      • 자료구조 (13)
      • 운영체제 (1)
      • 알고리즘 (4)
      • 컴퓨터 보안 (8)
      • 기타 (10)
    • Data Science (28)
      • 데이터분석 (7)
      • 머신러닝 (14)
      • 딥러닝 (7)

인기 글

최근 글

최근 댓글

Github · Instagram · Facebook
kang9366

Repository

Programming/Kotlin

[Kotlin] 추상클래스와 인터페이스

2022. 12. 16. 00:09

추상 클래스

추상 클래스 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 은 추상 클래스 가 갖고 있던 일반 메소드 이다. start 와 stop 은 추상클래스 를 상속받은 자식 클래스 에서 오버라이딩한 메소드 이다. 추상 클래스에서 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<인터페이스 이름>.메소드 명 을 통해 구분할 수 있다.

저작자표시 비영리 변경금지 (새창열림)

'Programming > Kotlin' 카테고리의 다른 글

[Kotlin] Data Class  (0) 2022.11.25
[Kotlin] open  (0) 2022.11.22
[Kotlin] 스코프함수  (0) 2022.11.17
[Kotlin] 빈 문자열 확인(isEmpty, isBlank)  (0) 2022.07.02
[Kotlin] 늦은초기화 (lateinit, by lazy)  (0) 2022.07.02
    'Programming/Kotlin' 카테고리의 다른 글
    • [Kotlin] Data Class
    • [Kotlin] open
    • [Kotlin] 스코프함수
    • [Kotlin] 빈 문자열 확인(isEmpty, isBlank)
    kang9366
    kang9366

    티스토리툴바