통조림

[Dart] 클래스 Modifier 본문

Software/Flutter

[Dart] 클래스 Modifier

고랭지참치 2024. 4. 2. 20:49

코드 깃허브
https://github.com/KoreanTuna/Dart-Class-Modifier

참고
https://dart.dev/language/class-modifiers

extends와 implements

  • Dart는 Java와 다르게, 일반 클래스도 implements할 수 있다.
  • extends도 메소드 override가능, implements도 override가능.

그렇다면 Dart에서 2개의 차이는?

  • extends는 부모의 변수와 메소드에 접근 가능. 구현없이도 메소드 사용 가능
  • implements는 모든 변수와 메소드를 override해서 구현 해야한다, 다중상속 가능

Class Modifier

💡 Class modifiers control how a class or mixin can be used, both from within its own library, and from outside of the library where it's defined.

Class Modifier는 라이브러리 내부 클래스 혹은 외부에서 해당 클래스를 사용할 수 있는 방법을 제한하는데 사용된다.

  • final class
  • mixin class (그냥 mixin과 다름)
  • base class
  • abstract class
  • sealed class
  • interface class

base class

  • base 클래스를 구현하거나 확장하는 모든 클래스는base,final,sealed 로 표시해야 한다.
  • 외부 라이브러리에서는 implements할 수 없다.
    • == 다중상속 받는 with에 base class를 추가할 수 없다.

So, 외부 클래스에서 base 클래스가 보장하는 것들을 위반하는 것을 방지할 수 있다.

ex) 내부 라이브러리에서 제작한 기능 그대로만 사용하고, 기능을 변경시키거나 수정하지 못하도록 하기 위해.

// Library a
base class Vehicle {
	...
}

// Error: base class는 base, final ,sealed만 상속 가능
class ProtoVehicle extends Vehicle{
	...
}

// No Error : 내부 라이브러리 안에서는 base도 구현 가능
base class PostVegicle implements Vehicle{
	...
}
// Library b

// 문제없음
base class Car extends Vehicle {
	...
}

// Error : 외부 라이브러리에서 구현 불가능
base class Car implements Vegicle{
	...
}

base class가 필요한 반례

class A {
  void _privateMethod() {
    print('I inherited from A');
  }
}

void callPrivateMethod(A a) {
  a._privateMethod();
}
import 'a.dart';

class B implements A {
  // No implementation of _privateMethod()!
}

main() {
  callPrivateMethod(B()); // Runtime exception!
}

final class

  • base 클래스를 구현하거나 확장하는 모든 클래스는base,final,sealed 로 표시해야 한다.
  • final class는 외부 라이브러리에서는 상속할 수 없다.
  • final은 base의 기능을 포함한다.
// Library a
final class Vehicle {
	...
}

// 내부 라이브러리에서 확장이라 문제없음
final class ProtoVehicle extends Vehicle{

}
// Library b

// Error : 외부 라이브러리에서 확장 불가능
final class Car extends Vehicle {
	...
}

// Error : 외부 라이브러리에서 구현 불가능
final class Car implements Vehicle {
	...
}

abstract Interface class

  • Java에서 사용되는 interface와 거의 동일하게 사용할 수 있도록 하는 제어자.
  • 외부 라이브러리에서는 확장은 불가능하고 구현만 가능.
  • 생성 불가능
// Library a
abstract interface class Fish {
  void makeSound();
  abstract int size;
}
// Library b

// Error 발생
class Bass extends Fish{
}

class Shark implements Fish {
  Shark(this.size);

  @override
  void makeSound() {
    print('Shark makes sound');
  }

  @override
  int size;
}

sealed class

  • 암묵적으로 abstract 기능을 포함한다.
    • 자식 클래스들은 abstract하지 않다.
  • 외부 라이브러리에서 생성되거나 확장되거나 구현하지 못한다.
  • 생성 불가능
  • enum처럼 사용할 수 있다.
    • exhausitive한 속성을 적극적으로 활용하는 것을 권장하고 있음
sealed class Vehicle {}

class Car extends Vehicle {}

class Truck implements Vehicle {}

class Bicycle extends Vehicle {}

// ERROR: Cannot be instantiated
Vehicle myVehicle = Vehicle();

// Subclasses can be instantiated
Vehicle myCar = Car();

String getVehicleSoundWithError(Vehicle vehicle) {
  // ERROR: The switch is missing the Bicycle subtype or a default case.
  return switch (vehicle) {
    Car() => 'vroom',
    Truck() => 'VROOOOMM',
  };
}

String getVehicleSound(Vehicle vehicle) {
  // No Error
  return switch (vehicle) {
    Car() => 'vroom',
    Truck() => 'VROOOOMM',
    Bicycle() => 'ring ring',
  };
}

.
.

sealed class 사용해보기

깃허브에는 package 형태로 class modifier를 사용해서 API를 호출하는 단에서 어떻게 활용할 수 있는지 고민해봤다.

라이브러리 단에서 sealed class로 부모 클래스를 선언한다.

sealed class BettherCalendarCase {
  final Color backgroundColor;

  Future<void> showCalendar(
    BuildContext context, {
    required BettherCalendarConfig config,
  });
  BettherCalendarCase(this.backgroundColor);
}

.
.
.
.

자식 클래스들을 선언한다.

class RedCalendar implements BettherCalendarCase {
	Future<void> showCalendar(BuildContext context, {required BettherCalendarConfig config}){
  		...
  	}
}

class YellowCalendar implements BettherCalendarCase {
	Future<void> showCalendar(BuildContext context, {required BettherCalendarConfig config}){
  		...
  	}
}

class GreenCalendar implements BettherCalendarCase {
	Future<void> showCalendar(BuildContext context, {required BettherCalendarConfig config}){
  		...
  	}
}

.
.
.
.

라이브러리 호출 코드

sealed class는 Exhaustiveness checking를 보장하기 때문에, Switch문에서 빠지는 자식 클래스 없이 케이스 검사가 가능하다. (빠져있는 자식클래스가 있다면 error가 발생한다)

class HomeScreen extends StatelessWidget {
  const HomeScreen({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: SizedBox(
        width: double.infinity,
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          crossAxisAlignment: CrossAxisAlignment.center,
          children: [
            ElevatedButton(
              onPressed: () async {
                await getColorCanlendar(BlueCalendar(), context);
              },
              child: const Text('Show Calendar'),
            ),
            const Text('Hello, world!'),
          ],
        ),
      ),
    );
  }

  Future<void> getColorCanlendar(
      BettherCalendarCase calendarCase, BuildContext context) async {
    return switch (calendarCase) {
      RedCalendar() => RedCalendar().showCalendar(context),
      BlueCalendar() => BlueCalendar().showCalendar(context),
      GreenCalendar() => GreenCalendar().showCalendar(context),
      YellowCalendar() => YellowCalendar().showCalendar(context),
    };
  }
}