관리 메뉴

나만을 위한 블로그

[Dart] mixin이란? 본문

Flutter

[Dart] mixin이란?

참깨빵위에참깨빵_ 2023. 4. 30. 20:23
728x90
반응형

Dart 언어를 공부하다가 mixin이란 키워드가 나왔다. 무슨 뜻인지 공식문서를 확인하니 아래와 같다.

 

https://dart.dev/language/mixins

 

Mixins

Learn how to add to features to a class in Dart.

dart.dev

믹스인은 여러 클래스 계층 구조에서 클래스 코드를 재사용하는 방법이다. 믹스인을 쓰려면 with 키워드와 하나 이상의 믹스인 이름을 사용하라. 아래 예제는 믹스인을 사용하는 두 클래스다
class Musician extends Performer with Musical {
  // ···
}

class Maestro extends Person with Musical, Aggressive, Demented {
  Maestro(String maestroName) {
    name = maestroName;
    canConduct = true;
  }
}
믹스인을 구현하려면 객체를 확장하고 생성자가 없는 클래스를 만든다. mixin을 일반 클래스로 쓰지 않으려면 class 대신 mixin 키워드를 사용하라
mixin Musical {
  bool canPlayPiano = false;
  bool canCompose = false;
  bool canConduct = false;

  void entertainMe() {
    if (canPlayPiano) {
      print('Playing piano');
    } else if (canConduct) {
      print('Waving hands');
    } else {
      print('Humming to self');
    }
  }
}

 

클래스 안의 코드를 재사용하는 방법이라고 하는 것 같은데 이것만으론 정확히 믹스인이 뭔지 모르겠다. 다른 글들을 확인했다.

 

https://medium.com/flutter-community/dart-what-are-mixins-3a72344011f3

 

Dart: What are mixins?

It’s a kind of magic ✨

medium.com

아래의 클래스 상속 다이어그램을 확인한다

3개의 하위 클래스(Mammal, Bird, Fish)가 있는 상위 클래스 Animal이 있다. 맨 밑에는 concrete class가 있다. 작은 사각형은 행동을 나타낸다. 파란 사각형은 이 동작이 있는 클래스의 인스턴스가 수영할 수 있음을 나타낸다. 일부 동물은 공통적인 행동을 한다. 고양이, 비둘기는 다 걸을 수 있지만 고양이는 날 수 없다. 이런 종류의 동작은 이 분류와 직교하므로 슈퍼클래스에서 이런 동작을 구현할 수 없다. 한 클래스가 하나 이상의 슈퍼클래스를 가질 수 있다면 간단할 것이다. Walker, Swimmer, Flyer의 3가지 다른 클래스를 구현할 수 있다. 그 후에는 Walker 클래스에서 Dove, Cat을 상속받기만 하면 된다. 그러나 Dart에선 Object를 제외한 모든 클래스에 정확히 하나의 슈퍼클래스가 있다. Walker 클래스에서 상속하는 대신 인터페이스처럼 구현할 수 있지만 여러 클래스에서 동작을 구현해야 해서 좋은 솔루션이 아니다. 여러 클래스 계층 구조에서 클래스 코드를 재사용하는 방법이 필요하다. 믹스인은 여러 클래스 계층 구조에서 클래스 코드를 재사용하는 방법이다...(중략)

 

정리하면 아래와 같다.

 

  • 일반적으로 클래스는 상속으로 코드를 재사용하지만 mixin은 상속 없이 클래스 안에서 코드를 재사용할 수 있게 하는 키워드다
  • mixin 클래스를 다른 클래스에서 사용하려면 그 클래스에 'with' 키워드를 써서 mixin 클래스를 선언해야 한다

 

다른 글에서 설명하는 내용도 거기서 거기기 때문에 예제 코드로 mixin을 확인한다.

메인 함수 밖에 필요한 클래스들을 선언한다.

 

mixin Logger {
  void log(String message) => print("[로그] : $message");
}

class Person with Logger {
  final String name;

  Person(this.name);

  void sayHello() => log("안녕! 내 이름은 $name");
}

 

Logger 클래스명 앞에 mixin 키워드를 써서 mixin 클래스를 만들고 log()를 만들었다. 그리고 Person 클래스를 만들고 앞서 만든 Logger mixin을 'with' 키워드와 함께 썼다. 화살표 함수를 써서 한 줄 함수로 만든 것 말고는 딱히 볼 게 없는 간단한 로그 작성 코드다. 이렇게 한 다음 메인 함수 안에서 사용한다.

 

void main() {
  Person("김철수")..sayHello(); // [로그] : 안녕! 내 이름은 김철수
}

 

캐스케이드 연산자를 통해 클래스의 인스턴스를 만든 다음 곧바로 함수를 호출했다. 실행하면 코드 옆 주석과 같은 문장이 출력될 것이다.

다른 예제도 확인해 본다. mixin을 써서 간단하게 게임의 캐릭터를 구현한다면 아래처럼 할 수 있을 것이다.

 

mixin Character {
  late String name = "";
  late int level = 1;
  late int health = 100;

  void attack(Character target) {
    print("$name이(가) ${target.name}을(를) 공격합니다!!");
    target.takeDamage(10 * level);
  }

  void takeDamage(int damage) {
    health -= damage;
    if (health <= 0) {
      print("$name이(가) 패배했습니다..");
    } else {
      print("$name이(가) $damage의 데미지를 받았습니다. 남은 체력 : $health");
    }
  }
}

class Player with Character {
  final String name;

  Player(this.name);
}

class Monster with Character {
  final String name;
  final int level;

  Monster(this.name, this.level) {
    health = 50 * level;
  }
}

 

텍스트 형태로 진행하는 게임을 머드 게임이라고 한다. Dart로 머드 게임을 만든다면 캐릭터가 몬스터를 공격할 수 있고, 몬스터도 캐릭터를 공격할 수 있을 것이다. 공격과 체력이 0일 경우와 0이 아닌 경우 어떤 문장을 출력하는지는 캐릭터, 몬스터의 공통적인 기능이기 때문에 mixin 안에 함수 형태로 구현했다. 그리고 Player, Monster라는 클래스를 각각 만들고 with 키워드로 Character mixin을 적용시킨다. 그리고 mixin 클래스 안에는 생성자가 없는 걸 확인할 수 있다.

이 처리 덕분에 캐릭터와 몬스터 클래스는 공격 기능과 데미지를 받았을 경우 처리하는 로직을 중복되지 않도록 갖게 되었고 이름만 받거나 이름, 레벨을 같이 받아서 인스턴스를 만드는 생성자만 갖게 됐다.

이후 메인 함수에서 아래와 같이 조립해 사용한다.

 

void main() {
  Player player = Player("김철수");
  Monster monster = Monster("슬라임", 3);

  player.attack(monster);
  monster.attack(player);
}

// 김철수이(가) 슬라임을(를) 공격합니다!!
// 슬라임이(가) 10의 데미지를 받았습니다. 남은 체력 : 140
// 슬라임이(가) 김철수을(를) 공격합니다!!
// 김철수이(가) 30의 데미지를 받았습니다. 남은 체력 : 70

 

player 객체를 통해 attack()을 호출했을 때, monster 객체를 통해 attack()을 호출했을 때 출력되는 문장과 남은 체력이 각각 다른 것을 볼 수 있다.

이렇듯 mixin을 사용하면 상속, 인터페이스를 사용하지 않고도 클래스의 코드를 재사용하는 트릭을 사용할 수 있다. 이로 인해 클래스 계층 구조가 단순화되고 코드 중복을 피해 가독성 좋은 코드를 작성할 수 있으며, 클래스 기능의 확장 또는 수정이 쉬워지기 때문에 코드 유연성도 좋아질 것을 기대할 수 있다.

반응형

'Flutter' 카테고리의 다른 글

[Dart] 캐스케이드 연산자(Cascade notation)란?  (0) 2023.08.05
[Dart] 함수(Function) 알아보기  (0) 2023.05.07
[Flutter] BuildContext란? Widget이란?  (0) 2023.01.24
[Dart] 자료형  (0) 2022.11.12
[Dart] Dart란?  (0) 2022.11.11
Comments