인문주의 사피엔스

Flutter/Dart의 extends, implements, mixin 사용법 본문

프로그래밍/Flutter

Flutter/Dart의 extends, implements, mixin 사용법

인문주 2022. 4. 18. 17:38
반응형

Flutter의 프로그래밍 언어인 Dart의 문법은 C, C++, JAVA 등 이전의 프로그래밍 언어들과 비교할 때 효율적이면서도 훨씬 간결해졌습니다. 그런 만큼 기존에 볼 수 없었던 생소한 내용도 많이 있습니다. 그런 생소한 내용 가운데 하나가 클래스에 관한 문법입니다. 다음은 Dart 언어의 문법을 전체적으로 훑어보는 데 도움이 되는 공식 웹페이지입니다.

 

Dart basics | Dart

 

Introduction to Dart

A brief introduction to Dart programs and important concepts.

dart.dev

 

인터페이스

일반적으로 인터페이스는 클래스의 API 가운데 외부에 공개할 것들을 모아서 따로 관리하기 위한 목적으로 사용됩니다. 인터페이스를 정의할 때는 interface, protocol 등의 키워드를 사용하고 인터페이스의 내용을 구현할 때는 implements 등의 키워드를 사용합니다. 그런데 Dart에는 인터페이스를 ‘정의’할 때 사용하는 별도의 키워드가 없습니다. 인터페이스라는 개념을 사용하지 않는 걸까요? 아닙니다. 인터페이스를 ‘구현’할 때 사용하는 키워드인 implements는 그대로 살아있습니다. 이는 클래스와 인터페이스를 정의할 때는 구분하지 않고 구현할 때만 구분한다는 의미입니다. 구체적으로 말해 extends로 구현하면 상속이 되고 implements로 구현하면 인터페이스가 됩니다.

 

extends와 implements는 사용 방법에서도 차이가 있습니다. extends로 상속을 하는 경우에는 클래스의 모든 내용을 재정의할 필요가 없지만 implements로 인터페이스를 구현하는 경우에는 클래스의 모든 내용을 빠짐 없이 구현해야 합니다. 사람의 신상 정보와 행동을 정의하는 아래의 Person 클래스를 사용하여 예를 들어보겠습니다.

 

abstract class Person {
  Person(this.name);

  final String name;
  String sex();
  String age();
  String eat() => '$name eats nothing';
  String say() => '$name says nothing';
}

 

Person은 sex와 age를 추상 함수로 둔 추상 클래스입니다. Person 클래스를 볼 때 보통은 그것을 상속해 John, Jane 같은 자식 클래스를 구현하는 경우를 자연스럽게 연상하게 됩니다. Person의 형식이 interface나 protocol이 아니라 class이기 때문입니다. 하지만 Dart에서는 다릅니다. 다음와 같이 extends가 아닌 implements를 사용하면 Person은 John의 부모 클래스가 아닌 인터페이스의 역할을 하게 됩니다.

 

class John implements Person {
  @override
  String get name => 'John';

  @override
  String sex() => '$name is male';

  @override
  String age() => '$name is 20';

  @override
  String eat() => '$name eats pizza';

  @override
  String say() => '$name says something';
}

 

여기서 John은 Person이라는 인터페이스의 실제 내용을 구현한 클래스가 되었습니다. 앞에서 언급한 대로 implements가 사용되면 Person의 모든 변수와 함수는 의무적으로 재정의되어야 합니다.

 

상속

extends를 통해 상속을 하는 경우에는 모든 내용을 재정의할 필요가 없습니다. 의무적으로 재정의해야 하는 것은 추상 함수뿐입니다. 아래의 Jane 클래스는 extends를 사용함으로써 Person을 부모 클래스로 만들었습니다. Jane 클래스가 의무적으로 재정의해야 하는 함수는 sex와 age뿐입니다. 나머지 함수들은 필요한 경우만 선택적으로 재정의할 수 있습니다.

 

class Jane extends Person {
  Jane(String name) : super(name);

  @override
  String sex() => '$name is female';

  @override
  String age() => '$name is 21';
}

 

extends는 사용 목적에 있어서도 implements와 분명하게 구분이 됩니다. 여러 단계의 상속이 필요한 경우에는 당연히 extends를 사용해야 합니다. 다음은 Person, Male, Mike로 이어지는 다단계 상속을 가정한 예입니다.

 

abstract class Male extends Person {
  Male(String name) : super(name);

  @override
  String sex() => '$name is a male';
}

 

다음과 같이 Mike 클래스는 Male을 상속함으로써 자연스럽게 Person까지 상속받게 됩니다. age는 의무적으로 구현을 한 것이고 eat은 선택적으로 재정의한 것입니다.

 

class Mike extends Male {
  Mike(String name) : super(name);

  @override
  String age() => '$name is 22';

  @override
  String eat() => '$name eats burger';
}

 

다중 상속

Dart는 extends를 통한 다중 상속을 허용하지 않습니다. 다중 상속을 하려면 class 대신 mixin을 사용해야 합니다. mixin은 생성자가 없는 class로 생각하면 됩니다. 아래의 Hobby는 cook, read를 포함하는 mixin입니다.

 

mixin Hobby {
  String cook() => 'cooks';
  String read() => 'reads';
}

 

mixin은 사용 범위를 한정할 수도 있습니다. 다음 코드의 의미는 "Tenor는 반드시 Male을 상속받는 클래스에만 사용되어야 한다"고 선언하는 것입니다.

 

mixin Tenor on Male {
  String sing() => 'sings';
}

 

아래의 Paul 클래스는 Male을 상속하는 동시에 Hobby와 Tenor를 다중 상속하고 있습니다. 그리고 Hobby의 멤버 함수 중 하나인 cook을 선택적으로 재정의하고 있습니다.

 

class Paul extends Male with Hobby, Tenor {
  Paul(String name) : super(name);

  @override
  String age() => '$name is 23';

  @override
  String cook() => '$name ${super.cook()}';
}

 

다음은 전체 소스코드입니다.

 

 

반응형
Comments