🐦 Flutter

[Dart] mixin

ji-hyun 2023. 4. 9. 10:58

mixin 은 여러 클래스 계층 구조에서 클래스 코드를 재사용하는 방법입니다.

무슨 말인지 이해 안되실 수 있습니다. (괜찮습니다! 저도 그랬으니까요)

천천히 mixin 이 왜 등장하였는지, 다른 클래스와는 어떻게 다른지 저와 같이 한 번 정리해보며 알아보겠습니다!!

 

 

 

 

1. mixin 등장 배경

 

class Player {
  void play() {
    print("경기하는 중...");
  }
}

class BasketBallPlayer extends Player{
 @override
  void play() {
    super.play();
    print("농구경기 하는 중...");
  }
}

 

위와 같이 Player 부모 클래스를 상속받는 BasketBallPlayer 자식 클래스가 있습니다.

여기서 BasketBallPlayer 클래스에게 다른 클래스도 상속해주고 싶습니다.

하지만 그럴 순 없습니다. 왜냐하면 OOP 에선 다중 상속이 대체로 불가능하기 때문입니다.

 

 

 

👀 왜 OOP 에서 다중 상속이 안되는 건가요?

👉 그 이유는 죽음의 다이아몬드 문제와 관련 있습니다

 

 

 

 

 

1 -1. 죽음의 다이아몬드 문제란?

 

다중 상속을 지원하게 되면 하나의 클래스가 여러 상위 클래스를 상속받을 수 있습니다.

이런 특징 때문에 발생하는 문제가 바로 다이아몬드 문제입니다

 

 

 

위의 클래스 다이어그램과 같은 상속 구조에서 발생되는 문제가 다이아몬드 문제입니다. 마치 모양이 다이아몬드 모양이라서 붙여진 이름 같습니다.

 

 

 

예를들어 GrandFather이라는 클래스가 myMethod() 라는 이름의 메소드를 가지고 있다고 가정해봅시다. 그리고 FatherA와 FatherB가 각각 오버라이딩하여 구현하였다면, FatherA와 FatherB를 모두 상속받은 Son 클래스 입장에서는 어떤 부모의 myMethod()를 사용해야 할까요? 이로 인하여 충돌이 생기게 됩니다.

 

 

 

코드로 나타내면 아래와 같은 모습입니다

 

class GrandFather {
    void myMethod(){
        System.out.println("GrandFather");
    }
}

class FatherA extends GrandFather {
    @Override
    void myMethod(){
        System.out.println("FatherA");
    }
}

class FatherB extends GrandFather {
    @Override
    void myMethod(){
        System.out.println("FatherB");
    }
}

class Son extends FatherA, FatherB{
    @Override
    void myMethod() {
        super.myMethod(); //FatherA를 출력해야 할까? FatherB를 출력해야 할까?
    }
}

 

Son클래스 입장에서는 같은 이름의 myMethod가 두개의 상위 클래스에 모두 정의되어 있기 때문에, 어떤 메소드를 실행해야 될지 알 수가 없습니다. (그리고 위의 코드는 당연히 컴파일 되지 않습니다.)

 

 

 

이와 같은 이유로 자바는 내부적으로 구현이 불가하도록 막아두었습니다. 

이 이유로 인해 Dart 또한 다중상속이 되지 않도록 막은 것이 아닐까 생각이 듭니다. 실제로 Dart 에서 다중 상속을 구현하면 다음과 같은 에러가 뜹니다.

 

 

👇

 

class OrderProductAndCountModel extends OrderModel, RatingModel // Error!

 

Each class definition can have at most one extends clause.
Try choosing one superclass and define your class to implement (or mix in) the others.

 

 

 

💬  에러 메시지가 친절하게 알려주네요

 

- 최대 한개의 class 만 extends 할 수 있다고 합니다.

- 하나의 슈퍼클래스만 선택해야하거나 다른 클래스를 implement(= 인터페이스)  및 mixin 을 이용하라고 합니다.

 

 

 

 

 

 

 

1 -2.
다이아몬드 문제로 인해 다중상속이 불가능한건 알았습니다.
근데 인터페이스는 왜 다중상속이 가능한걸까요..?

 

 

한가지 의아한 점이 생길 수 있습니다. 저런 문제가 있음에도 불구하고 자바에서 인터페이스는 다중상속이 가능하기 때문입니다.(다트 또한 마찬가지입니다) 이는 실제로 구현해보면 전혀 문제를 발생시키지 않는다는 것을 알 수 있습니다.

 

 

 

 

[Java]

 

interface GrandFather {
    void myMethod();
}

interface FatherA extends GrandFather {
    @Override
    void myMethod();
}

interface FatherB extends GrandFather {
    @Override
    void myMethod();
}

interface Son extends FatherA, FatherB{
    @Override
    void myMethod();//상위 인터페이스에서 구현된 것이 없기 때문에 충돌이 발생하지 않는다.
}

 

 

인터페이스는 기능에 대한 선언만 해두면 되기 때문에, 다이아몬드 상속이 되더라도 충돌할 여지가 전혀 없습니다. 그러므로 인터페이스에 경우는 다중상속을 통한 문제가 발생하지 않습니다.

 

 

 

 

 

[Dart]

Dart 의 인터페이스도 아래 예시처럼 구현해봤습니다.

 

class RatingModel implements IModelWithId, IModelWithTestId {
  @override
  final String id;
  final UserModel user;
  final int rating;
  final String content;
  @JsonKey(
    fromJson: DataUtils.listPathsToUrls,
  )
  
  
  
abstract class IModelWithId {
  final String id;

  IModelWithId({
    required this.id,
  });
}


abstract class IModelWithTestId {
  final String id;
  IModelWithTestId({
    required this.id,
  });
}

 

<Java 와 Dart 의 문법 차이>

참고로, Java 와의 문법적 차이점에 대해 설명해보자면 

Java 는 인터페이스를 정의하기 위해서는 interface, protocol 등의 키워드를 사용하고 구현할 때는 implements 라는 키워드를 사용합니다.

 

 

그런데 Dart 는 인터페이스를 정의하기 위한 별도의 키워드가 없습니다

하지만 인터페이스를 구현할 때 사용하는 키워드인 implements 는 있습니다.

 

 

💡

이는 클래스와 인터페이스를 정의할 때는 구분하지 않고, 구현할 때만 구분한다는 의미입니다.

구체적으로 말해, extends 로 구현하면 상속이 되고, implements 로 구현하면 인터페이스가 됩니다

 

 

 

 

 

✏️ .. extends 와 interface 의 문법 차이점

참고로 구현의 키워드인 extends 와 implements 는 사용방법에 대해서도 차이가 있습니다

extends 로 상속을 하는 경우에는 클래스의 모든 내용을 재정의할 필요가 없지만 implements 로 인터페이스를 구현하는 경우에는 클래스의 모든 내용을 빠짐 없이 구현해야 합니다

 

 

 

 

 

 

 

 

1 -3. 다중상속은 안되지만 인터페이스의 경우는 다중 상속이 가능한 점을 알았습니다..
그럼 일반 클래스는 다중 상속이 정말 불가능한거군요...

아니요!!!!!!!! 이래서 Mixin 을 이용하는거예요!!!!

 

Dart 는 extends 를 통한 다중상속을 허용하지 않지만 mixin 을 사용하면 다중상속을 가능하게 할 수 있습니다

mixin 은 다른 것을 생각할 필요 없고 그냥 쉽게 다중상속이 가능한 "생성자가 없는 클래스" 라고 생각하시면 됩니다

 

 

 

 

 

 

 

 

2. Mixin 사용 방법

Mixin 을 사용하려면 with 키워드 다음으로 하나 이상의 믹스인을 사용하면 됩니다

 

class Musician extends Performer with Musical {
  // ···
}

class Maestro extends Person with Musical, Aggressive, Demented { // 다중상속 가능!
  Maestro(String maestroName) {
    name = maestroName;
    canConduct = true; // 주목!
  }
}

 

위와 같이 다중 상속이 가능한 것을 볼 수 있습니다

여기서 canConduct 에도 주목을 해볼게요.

 

 

 

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');
    }
  }
}

 

생성자를 선언하지 않는 클래스인 것을 확인할 수 있습니다

아까 위의 canConduct 변수는 Musical 에서 왔던 것이었군요. 이렇게 mixin 을 with 로 사용하면 변수나 메서드를 그대로 갖다 쓸 수 있습니다!

 

 

 

 

 

여기서 mixin 을 사용하는 타입을 제한할 수도 있습니다

 

 

 

다음 예제와 같이 필요한 슈퍼 클래스를 지정하는 키워드 on 를 사용하여 Mixin 사용을 제한할 수 있습니다

 

 

class Musician {
  // ...
}
mixin MusicalPerformer on Musician {
  // ...
}
class SingerDancer extends Musician with MusicalPerformer {
  // ...
}

 

이 코드를 보면

오직 Musician class 를 extend 하거나 implement 하는 클래스들만 mixin MusicalPerformer 를 사용할 수 있습니다.

 

SingerDancer 는 Musician 을 extends 하기 때문에 SingerDancer 는 MusicalPerformer 를 mix 할 수 있습니다

 

 

 

 

 

 

 

 

 

 

출처

https://siyoon210.tistory.com/125

 

자바는 왜 다중상속을 지원하지 않을까? (다이아몬드 문제)

다이아몬드 문제 다중 상속을 지원하게 되면 하나의 클래스가 여러 상위 클래스를 상속 받을 수 있습니다. 이런 특징 때문에 발생하게 되는 문제가 있는데, 바로 '다이아몬드 문제' 입니다. 위의

siyoon210.tistory.com

 

 

https://theoryof0.tistory.com/117

 

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

Flutter의 프로그래밍 언어인 Dart의 문법은 C, C++, JAVA 등 이전의 프로그래밍 언어들과 비교할 때 효율적이면서도 훨씬 간결해졌습니다. 그런 만큼 기존에 볼 수 없었던 생소한 내용도 많이 있습니

theoryof0.tistory.com

 

https://dart.dev/language/mixins

 

Mixins

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

dart.dev