헷갈리기 쉬운 다트의 추상화 관련 키워드 (abstract class, interface class, abstract interface class, mixin, abstract mixin class) 들의 차이를 알아보는 글
다트 언어도 OOP이기 때문에 추상화가 가능하며, 그 방식은 매우 다양합니다:
- abstract class
- interface class
- abstract interface class
- mixin
- abstract mixin class
등등...
종류가 매우 다양한 만큼 각 방식의 차이점과 use case들이 헷갈릴 수 있습니다.
제가 나름대로 공부하고 파악한 내용들 정리해보려합니다. 같은 내용으로 궁금증을 가지셨던 분들에게 도움이 됬으면 합니다.
본격적으로 내용을 다루기에 앞서 다트 공식문서에서 class modifier를 정리해둔 표를 공유드립니다.
1. class
다트의 클래스는 다른 언어에서의 클래스와 크게 다르지 않게 동작하지만, 가장 큰 차이는 다트에서 클래스는 암시적으로 interface로 사용될 수 있다는 점입니다. (implicit interface)
따라서 클래스 A는 클래스 B를 extends 할 수 있을 뿐만 아니라 implements할 수 있어집니다.
클래스를 인터페이스로서 사용할 때에는 다른 언어에서와 마찬가지로 클래스(여기서는 인터페이스)내에 구현된 모든 메소드들을 하위 클래서에서 오버라이드 해줘야합니다.
관련 내용 및 코드 예시는 다트 공식문서에서 확인할 수 있습니다.
2. interface class
다트에서는 클래스가 암시적으로 interface의 역할도 할 수 있다고 하였는데, 그렇다면 interface는 왜 필요한지 개인적으로 의문이 들었습니다.
이에 대한 의문은 위의 표를 통해 어느정도 해소될 수 있었습니다.
위의 표에서 class와 Interface class의 유일한 차이는 Extend? 컬럼의 값입니다.
이 값을 보면 interface class는 extend가 불가능합니다.
즉, 클래스이긴 한데 상속되어지면 안되고 Interface로만 사용될 수 있게 만들어져야할 때 interface class를 사용하면 될 듯 합니다.
Implicit하게 interface 역할을 같이 수행하던 클래스를 오직 interface로서만 쓰일 수 있게 바꿔주는것이 interface class 키워드인 것으로 이해됩니다.
하지만 interface class는 자바와 같은 다른 언어의 interface와는 다르게 여전히 클래스이기도 해서 메소드들의 구현부를 가지고 있어야합니다.
3. abstract interface class
interface class를 abstract로 정의해줌으로써 다음 변화사항이 생깁니다:
- interface class 인스턴스화 불가
- 메소드 구현시 구현부 없이 정의부만 작성 (abstract method)
이렇게 interface class를 abstract로 정의해줌으로써 비로소 다트에서도 interface를 자바와 같은 언어의 interface처럼 사용할 수 있게됩니다.
4. abstract class
다트에서 abstract class는 한마디로 "사용자가 마져 구현해야하는 덜 구현된 클래스"를 제공합니다.
이는 abstract class가 다음 특성들을 가지기 때문입니다.
- 인스턴스화 불가능
- 일반적인 멤버 (필드변수, 메소드)등 포함 가능
- abstract method 포함 가능
abstract method 이외에도 일반적인 변수와 메소드를 포함할 수 있다는 것이 dart의 abstract class가 java와 같은 언어의 abstract class의 가장 큰 차이로 보여집니다.
간단한 코드를 예시로 들자면:
abstract class Animal {
var food = 'fish';
void makeSound(); // abstract method -> 하위 클래스에서 구현 필요
void eat() {
print('Animal is eating $food');
}
}
class Dog extends Animal {
@override
void makeSound() {
print('I want to eat ${super.food}');
}
}
void main () {
var dog = Dog();
dog.makeSound();
dog.eat();
}
이때 abstract class에 abstract method가 아닌 일반메소드도 하위 클래스에서 오버라이드 하여 사용 가능합니다.
5. mixin
개인적으로는 아직까지 써본 적이 없는 기능이긴 한데, 공식문서는 다음과 같이 mixin을 정의합니다
"Mixins are a way of defining code that can be reused in multiple class hierarchies. They are intended to provide member implementations en masse."
상속관계(is-a) 관계없이 코드를 공유하게 해주는 장점인듯 합니다.
5.1. on 키워드
mixin 정의 시 on 키워드를 사용한다면 해당 mixin을 사용할 수 있는 클래스들을 제한할 수 있게됩니다.
class Musician{
var name = "brad";
void sayHi() {
print('hi, this is $name');
}
}
mixin MusicalPerformer on Musician {
void sayBye() {
print('say hi first, before saying bye');
sayHi();
}
}
class Pianist extends Musician with MusicalPerformer {}
void main() {
Pianist().sayBye();
}
위 코드에서 볼 수 있는 것처럼, on 키워드를 사용한다면
- MusicalPerformer라는 mixin은 이제 Musician 클래스와 그 하위클래스들에서만 사용가능하게 되고
- MusicalPerformer mixin에서 Musician 클래스에 구현된 메소드에 접근 가능하다는 것입니다.
5.2. mixin class
mixin class라는 것이 있습니다.
mixin으로도, class로도 사용가능 한 것인데, 둘다로 사용가능하다보니까 mixin과 class에 사용되는 제약을 모두 받습니다:
- mixin이기 때문에 extends / implements를 통해서 다른 클래스/인터페이스를 상속/구현할 수 없고
- class이기 때문에 정의부에 on 키워드 사용이 불가능합니다.
6. abstract mixin class
공식문서에서는 abstract mixin class의 사용법을 한가지만 소개합니다:
- "mixin을 class로 정의해버림으로써 사용할수 없게된 on 키워드의 기능을 대채하기 위함"
그래서 다음과 같은 예시를 제공합니다:
abstract mixin class Musician {
// No 'on' clause, but an abstract method that other types must define if
// they want to use (mix in or extend) Musician:
void playInstrument(String instrumentName);
void playPiano() {
playInstrument('Piano');
}
void playFlute() {
playInstrument('Flute');
}
}
class Virtuoso with Musician { // Use Musician as a mixin
void playInstrument(String instrumentName) {
print('Plays the $instrumentName beautifully');
}
}
class Novice extends Musician { // Use Musician as a class
void playInstrument(String instrumentName) {
print('Plays the $instrumentName poorly');
}
}
하지만 이것만으로는 abstract mixin class의 용도를 이해하기가 부족하기도 하고 abstract class로도 동일한 기능을 구현할 수 있는 것이 아닌가 하는 궁금증이 들어서 abstract mixin class가 직접쓰이는 곳들을 찾아보았습니다.
대표적으로 플러터의 WidgetsBindingObserver가 abstract mixin class로 정의되었습니다.
이 클래스는 다음과 같이 StatefulWidget을 구현하는 State클래스들에서 다음과 같이 with 키워드를 통해 쓰일 수 있습니다:
이를 통해 WidgetsBindingObserver가 왜 abstract class가 아닌 abstract mixin class로 구현되었는지 생각해보았는데:
- abstract mixin class 대신 abstract class를 사용하여 구현되었다면 AppState 클래스에서는 이 클래스를 사용하지 못했습니다. 다트에서도 다중상속은 불가능하기 때문입니다.
- 다중상속의 문제를 해결하기 위해서라면 Interface로 구현될수도 있지만 이렇게 되면 WidgetsBindingsObserver를 implement하는 클래스에서 WidgetsBindingsObserver의 모든 메소드를 오버라이딩 해야하므로 불필요한 코드가 증가하게 됩니다.
- 따라서 abstract mixin class로 구현함으로써
- WidgetsBindingObserver 자체의 기능을 활용해야하는 클래스들에서는 이를 mixin으로서 사용하면 되고
- WidgetsBindingObserver 를 상속하여 좀더 구체적인 구현체를 만들어야하는 경우에는 이를 클래스로 사용하면 되는 듯 합니다.
'mobile > dart & flutter' 카테고리의 다른 글
factory constructor를 쓰는 이유 (0) | 2024.04.28 |
---|