flutter – mixing, extension

Extension

Extension은 기존 클래스에 새로운 메서드나 속성을 추가하는 기능입니다. Dart 2.7에서 도입된 이 기능은 기존 클래스의 소스를 수정하지 않고도 그 클래스에 메서드나 속성을 추가할 수 있게 해줍니다. 이는 외부 라이브러리나 시스템 클래스를 확장해야 할 때 유용하며, 특히 라이브러리나 SDK의 코드를 직접 수정할 수 없는 상황에서 매우 효과적입니다.

Extension 정의 및 사용법

  • Extension은 extension 키워드를 사용하여 정의하며, 해당 클래스를 확장하는 형식을 가집니다.
  • 확장할 클래스 또는 타입을 on 키워드 뒤에 명시하여 그 타입에만 적용되는 메서드를 추가할 수 있습니다.
  • 다음은 List<int> 타입에 평균을 계산하는 메서드를 추가하는 예시입니다:
extension ListExtensions on List<int> {
  double get average => this.isEmpty ? 0 : this.reduce((a, b) => a + b) / this.length;
}

void main() {
  List<int> scores = [10, 20, 30];
  print(scores.average); // 출력: 20.0
}

Extension의 장점

  • 기존 코드 수정 없이 기능 추가: 클래스나 라이브러리를 확장할 수 있으며, 기존 코드를 변경하지 않고도 새로운 기능을 추가할 수 있습니다.
  • 가독성 향상: 확장된 메서드를 통해 코드가 더욱 직관적이고 명확하게 읽힐 수 있습니다.
  • 코드 재사용성 증가: 특정 타입에 특화된 유틸리티 메서드를 정의하여 재사용성을 극대화할 수 있습니다.

Extension의 제한점

  • 상태 유지 불가: Extension은 상태를 가질 수 없으며, 단지 메서드나 속성을 추가하는 데 그칩니다. 상태가 필요한 경우에는 Mixin이 더 적합할 수 있습니다.
  • 다중 확장에 대한 주의: 여러 Extension이 동일한 메서드 이름을 사용할 경우 충돌이 발생할 수 있으므로, 확장 메서드의 이름을 고유하게 지정하는 것이 중요합니다.

Mixin

Mixin은 여러 클래스 간에 공통된 기능을 재사용할 수 있도록 해주는 기능입니다. Dart에서 Mixin은 상속 대신 사용할 수 있는 방법으로, 서로 다른 클래스에 공통 기능을 포함시킬 때 매우 유용합니다. Mixin은 클래스 간의 다중 상속 문제를 해결할 수 있으며, 코드 중복을 줄이고 더 유연한 설계를 가능하게 합니다. Dart에서 Mixin을 정의하려면 mixin 키워드를 사용합니다.

Mixin 사용법

with 키워드를 사용하여 Mixin을 클래스에 적용할 수 있으며, 다중 Mixin을 사용할 수도 있습니다.

mixin Swimming {
  void swim() {
    print('Swimming...');
  }
}

mixin Flying {
  void fly() {
    print('Flying...');
  }
}

class Duck with Swimming, Flying {
  // Duck has access to swim() and fly() methods
}

class Fish with Swimming {
  // Fish has access to swim() method
}

void main() {
  Duck duck = Duck();
  duck.swim(); // Output: Swimming...
  duck.fly(); // Output: Flying...

  Fish fish = Fish();
  fish.swim(); // Output: Swimming...

Mixin 타입 제한

Mixin을 사용할 수 있는 타입을 제한할 수도 있습니다. 예를 들어, mixin이 정의하지 않은 메서드를 호출할 수 있는지에 따라 달라질 수 있습니다. 다음 예제처럼 on 키워드로 사용할 수 있는 부모 클래스를 제한함으로써 mixin의 사용을 제한할 수 있습니다.

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

Mixin의 제한점

  • 복잡성 증가: 여러 개의 Mixin을 사용할 경우 클래스의 구조가 복잡해질 수 있습니다. Mixin이 여러 개 쌓일수록 클래스 간의 관계를 이해하기 어려울 수 있습니다.
  • 상속과의 차이: 상속과 Mixin을 함께 사용할 경우 클래스의 계층 구조가 복잡해질 수 있으므로 설계를 신중하게 해야 합니다.

Abstract Mixin Class

  • 추상 Mixin 클래스는 상속받는 클래스 또는 Mixin을 사용하는 클래스에서 반드시 구현해야 하는 메서드를 포함할 수 있습니다.
  • 이 방식은 Mixin을 추상 클래스와 같이 사용할 수 있게 해주며, 다중 상속과 유사한 구조를 구현할 수 있습니다.
abstract mixin class Musician {
  // 사용하려면 추상 메서드를 반드시 구현해야 함
  void playInstrument(String instrumentName);

  void playPiano() {
    playInstrument('Piano');
  }
  void playFlute() {
    playInstrument('Flute');
  }
}

class Virtuoso with Musician { // Musician을 mixin으로 사용
  void playInstrument(String instrumentName) {
    print('Plays the $instrumentName beautifully');
  }  
} 

class Novice extends Musician { // Musician을 class로 사용
  void playInstrument(String instrumentName) {
    print('Plays the $instrumentName poorly');
  }  
}

Extension과 Mixin의 차이점

주요 차이점

  • Extension은 기존 클래스에 메서드나 속성을 추가합니다. 예를 들어, String 타입에 새로운 메서드를 추가하여 문자열 처리 로직을 개선할 때 유용합니다.
    Mixin은 여러 클래스에 공통 기능을 재사용하기 위해 사용됩니다. 예를 들어, 데이터 처리와 같은 기능을 여러 클래스에서 공유해야 하는 경우에 적합합니다.
  • Extension은 상태를 가질 수 없지만, Mixin은 상태를 가질 수 있습니다.

Extension 사용 예시

기본 타입 확장

예를 들어, 타입에 새 메서드를 추가하여 문자열이 숫자로만 구성되어 있는지 확인하는 메서드를 만들 수 있습니다:

extension StringValidator on String {
  bool get isNumeric {
    return RegExp(r'^-?[0-9]+$').hasMatch(this);
  }
}

void main() {
  String value = "12345";
  print(value.isNumeric); // true
}

이 예시는 String 타입의 모든 문자열에서 isNumeric 메서드를 사용할 수 있게 해줍니다.

외부 라이브러리 클래스 확장

외부 라이브러리의 클래스를 확장하여 유틸리티 메서드를 추가할 수도 있습니다. 예를 들어, 외부 라이브러리에서 제공하는 클래스에 새로운 메서드를 추가하여 더욱 직관적으로 사용할 수 있습니다.

import 'package:some_external_library/external_class.dart';

extension ExternalClassExtension on ExternalClass {
  void logData() {
    print('Logging data: ${this.data}');
  }
}

Mixin 사용 예시

상태 관리 기능 추가 Mixin은 상태를 관리할 수 있기 때문에, 여러 클래스에서 동일한 상태 관리 로직을 공유할 수 있습니다. 예를 들어, 동일한 상태 로직을 여러 UI 컴포넌트에 적용할 수 있습니다.

mixin CounterMixin {
  int _count = 0;

  void increment() {
    _count++;
  }

  void reset() {
    _count = 0;
  }

  int get count => _count;
}

class Counter with CounterMixin {
  void displayCount() {
    print('Current count: $count');
  }
}

void main() {
  Counter counter = Counter();
  counter.increment();
  counter.displayCount(); // Current count: 1
}

이 예시에서는 CounterMixin을 사용해 여러 클래스에서 동일한 카운터 상태를 관리할 수 있도록 합니다.