회사에서 개발을 하다가 전략패턴을 적용해야 하는 상황이 나왔습니다.
총 6개의 케이스였는데, switch case 문을 사용하니 구분하기도 어렵고 4개의 케이스는 같은 api를 호출하지만 클래스 상속 관계가 명확하지 않아 파악하기 쉽지가 않았습니다.
그래서 팀원들과 상의 후 전략패턴을 적용해보기로 하였습니다.
플러터에서 전략패턴을 사용하려면 기본적으로는 전략패턴의 interface, context, service에 대한 이해가 필요합니다. 그리고 추가적으로 mixin, interface, abstract에 관한 클래스 정의 방법에 대한 이해도 필요합니다.
먼저 abstract과 interface 가장 큰 차이는 abstract로 만들 경우 메서드 구현을 할 수 있고 interface는 메서드 구현을 할 수 없습니다. mixin으로 클래스를 만들면 두 추상 클래스와 다른 점은 mixin에서 구현해놓은 메서드를 오버라이드 하지 않는다는 점입니다.
그렇다면 abstract로 작성된 클래스에 메서드 구현이 되어 있을 경우에는 implements를 해야할까요? 아니면 extends를 해야할까요?
정답은 extends로 해야 합니다. extends로 되어있다면 super에 구현된 메서드를 따로 구현하라는 lint 메세지도 나타나지 않습니다. 추가적으로 abstract로 작성된 클래스에 메서드 구현이 없다면 extends, implements 모두 상관 없지만 implements로 하는 것이 구현은 무조건 자식 클래스에서 해야한다는걸 알려주기 때문에 더 좋습니다. 그리고 구현이 없을 것이니 부모 클래스도 abstract interface로 만드는 것이 더 좋을 것입니다. abstract interface로 할 경우에 extends는 불가하고(오류 메세지 : The class '클래스명' can't be extended outside of its library because it's an interface class.), abstract인 경우에는 가능합니다.
interface : 위 개념과 같습니다. 자식들이 사용할 메서드 및 변수들을 선언합니다.
context : context 클래스를 사용하여 생성자로 들어온 타입에 따라 어떤 타입인지 구분하고 그 타입의 실제 구현되어 있는 함수를 호출합니다.
service : 구현 클래스입니다. 인터페이스에서 선언한 메서드들을 implements하여 구현합니다.
로봇의 종류는 총 4가지라고 가정합니다.
/// 전략 패턴을 이용해 관련 타입별로 다르게 호출합니다.
abstract interface class StrategyInterfaceRobot {
/// 로봇의 이름을 말하도록 해야하는 함수입니다.
String sayRobotName();
/// 로봇의 나이를 말하도록 해야하는 함수입니다.
String sayRobotAge();
/// 로봇의 가격을 말하도록 해야하는 함수입니다.
String sayRobotPrice();
}
class ContextRobot {
final StrategyInterfaceRobot _strategyInterfaceRobot;
ContextRobot(this._strategyInterfaceRobot);
String sayRobotName() {
return _strategyInterfaceRobot.sayRobotName();
}
String sayRobotAge() {
return _strategyInterfaceRobot.sayRobotAge();
}
String sayRobotPrice() {
return _strategyInterfaceRobot.sayRobotPrice();
}
}
class ContextMetalRobot {
final StrategyMetalRobot _strategyMetalRobot;
ContextMetalRobot(this._strategyMetalRobot);
String sayMetalRobotComponents() {
return _strategyMetalRobot.sayMetalRobotComponents();
}
}
mixin StrategyMetalRobotMixin implements StrategyInterfaceRobot {
String sayRobotName() {
return '제 이름은 전투에 특화된 초강력 메탈로봇 입니다.';
}
String sayRobotAge() {
return '저는 최첨단 메탈로봇이라서 나이가 존재하지 않죠.';
}
String sayRobotPrice() {
return '저는 6000만원 입니다.';
}
String sayMetalRobotComponents();
}
class StrategyRobotA implements StrategyInterfaceRobot {
String sayRobotName() {
return '제 이름은 A 입니다.';
}
String sayRobotAge() {
return '제 나이는 10살 입니다.';
}
String sayRobotPrice() {
return '제 가격은 50만원 입니다.';
}
}
class StrategyRobotB implements StrategyInterfaceRobot {
String sayRobotName() {
return '제 이름은 B 입니다.';
}
String sayRobotAge() {
return '제 나이는 12살 입니다.';
}
String sayRobotPrice() {
return '제 가격은 30만원 입니다.';
}
}
class StrategyRobotC with StrategyMetalRobotMixin {
String sayMetalRobotComponents() {
return '저는 탄소(C), 망간(Mn), 황(S), 인(P), 규소(Si)로 구성되어 있습니다.';
}
}
class StrategyRobotD with StrategyMetalRobotMixin {
String sayMetalRobotComponents() {
return '저는 스테인레스 스틸, 텅스텐, 디탄, 인코넬로 구성되어 있습니다.';
}
}
/// 전략 패턴을 이용해 관련 타입별로 다르게 호출합니다.
abstract interface class StrategyInterfaceRobot {
/// 로봇의 이름을 말하도록 해야하는 함수입니다.
String sayRobotName();
/// 로봇의 나이를 말하도록 해야하는 함수입니다.
String sayRobotAge();
/// 로봇의 가격을 말하도록 해야하는 함수입니다.
String sayRobotPrice();
}
class ContextRobot {
final StrategyInterfaceRobot _strategyInterfaceRobot;
ContextRobot(this._strategyInterfaceRobot);
String sayRobotName() {
return _strategyInterfaceRobot.sayRobotName();
}
String sayRobotAge() {
return _strategyInterfaceRobot.sayRobotAge();
}
String sayRobotPrice() {
return _strategyInterfaceRobot.sayRobotPrice();
}
}
class ContextMetalRobot {
final StrategyMetalRobotMixin _strategyMetalRobot;
ContextMetalRobot(this._strategyMetalRobot);
String sayRobotName() {
return _strategyMetalRobot.sayRobotName();
}
String sayRobotAge() {
return _strategyMetalRobot.sayRobotAge();
}
String sayRobotPrice() {
return _strategyMetalRobot.sayRobotPrice();
}
String sayMetalRobotComponents() {
return _strategyMetalRobot.sayMetalRobotComponents();
}
}
mixin StrategyMetalRobotMixin implements StrategyInterfaceRobot {
String sayRobotName() {
return '제 이름은 전투에 특화된 초강력 메탈로봇 입니다.';
}
String sayRobotAge() {
return '저는 최첨단 메탈로봇이라서 나이가 존재하지 않죠.';
}
String sayRobotPrice() {
return '저는 6000만원 입니다.';
}
String sayMetalRobotComponents();
}
class StrategyRobotA implements StrategyInterfaceRobot {
String sayRobotName() {
return '제 이름은 A 입니다.';
}
String sayRobotAge() {
return '제 나이는 10살 입니다.';
}
String sayRobotPrice() {
return '제 가격은 50만원 입니다.';
}
}
class StrategyRobotB implements StrategyInterfaceRobot {
String sayRobotName() {
return '제 이름은 B 입니다.';
}
String sayRobotAge() {
return '제 나이는 12살 입니다.';
}
String sayRobotPrice() {
return '제 가격은 30만원 입니다.';
}
}
class StrategyRobotC with StrategyMetalRobotMixin {
String sayMetalRobotComponents() {
return '저는 탄소(C), 망간(Mn), 황(S), 인(P), 규소(Si)로 구성되어 있습니다.';
}
}
class StrategyRobotD with StrategyMetalRobotMixin {
String sayMetalRobotComponents() {
return '저는 스테인레스 스틸, 텅스텐, 디탄, 인코넬로 구성되어 있습니다.';
}
}
void main() {
print(ContextRobot(StrategyRobotA()).sayRobotName());
print(ContextRobot(StrategyRobotA()).sayRobotAge());
print(ContextRobot(StrategyRobotA()).sayRobotPrice());
print('\n');
print(ContextRobot(StrategyRobotB()).sayRobotName());
print(ContextRobot(StrategyRobotB()).sayRobotAge());
print(ContextRobot(StrategyRobotB()).sayRobotPrice());
print('\n');
print(ContextRobot(StrategyRobotC()).sayRobotName());
print(ContextRobot(StrategyRobotC()).sayRobotAge());
print(ContextRobot(StrategyRobotC()).sayRobotPrice());
print('\n');
print(ContextRobot(StrategyRobotD()).sayRobotName());
print(ContextRobot(StrategyRobotD()).sayRobotAge());
print(ContextRobot(StrategyRobotD()).sayRobotPrice());
print('\n여기서부터 사용하는 context는 Metal context로\n');
print(ContextMetalRobot(StrategyRobotC()).sayRobotName());
print(ContextMetalRobot(StrategyRobotC()).sayRobotAge());
print(ContextMetalRobot(StrategyRobotC()).sayRobotPrice());
print(ContextMetalRobot(StrategyRobotC()).sayMetalRobotComponents());
print('\n');
print(ContextMetalRobot(StrategyRobotD()).sayRobotName());
print(ContextMetalRobot(StrategyRobotD()).sayRobotAge());
print(ContextMetalRobot(StrategyRobotD()).sayRobotPrice());
print(ContextMetalRobot(StrategyRobotD()).sayMetalRobotComponents());
}
여기서 더 나아가서 클래스를 하나 더 만들고 StrategyMetalRobotMixin에 on 키워드를 사용해 로봇 C,D가 해당 새로 만든 클래스를 상속받아 StrategyMetalRobotMixin 사용을 새로 만든 클래스 유형만 사용 할 수 있도록 제한하는 방법도 있습니다. 여러가지 방법으로 재사용 가능하게 하고 제한할 수도 있으니 앱의 코드 상황에 맞게 사용하시면 좋습니다.