mixin 과 함께 자주 쓰면 코드를 단순하게 만들 수 있는 좋은 문법
만약에 Row 안에 있는 children의 개수를 반환하는 함수를 만들고 싶다고 하자.
그러면 Row.children.length 를 호출하는 함수를 만들 수 있다.
그런데 이게 Row 안에 메소드로 있다면 더 좋지 않을까?
하지만 NewRow나 ExtendedRow 같은 Row의 확장 클래스를 만드는 것은 좋지 않다.
Row는 매우 많이 쓰이는 문법이고 많은 사람들이 Row가 어떻게 동작할 지를 예측 가능하지만, NewRow를 보면 이게 뭔지 코드나 문서를 봐야 알 수 있을 것이다. 또한, 기존 코드의 Row를 모두 바꾸어 주어야 한다.
기존 코드를 건드리지 않고 새로운 기능(메소드) 를 추가하려면 extension method 를 사용하자.
이 글에서는 StringBuffer를 가지고 한 번 만들어 보겠다.
StringBuffer를 이용하면 문자열을 효율적으로 만들 수 있지만, write, writeln 같은 단순한 함수 뿐이다. 만약에 들여쓰기를 해서 write 해주는 메소드가 있으면 더 좋을 것 같다. 이걸 extension method로 만들어보자.
선언은 매우 간단하다.
새로운 다트 파일을 하나 만들어서 (필수)
extension을 선언하면 된다.
tabCount 만큼 앞에 tab을 넣어서 write 한다.
extension StringBufferIndent on StringBuffer {
void writeWithIndent(Object? object, {int tabCount = 0}) {
for (int i = 0; i < tabCount; i++) {
write('\\t');
}
write(object);
}
void writelnWithIndent(Object? object, {int tabCount = 0}) {
writeWithIndent(
object,
tabCount: tabCount,
);
writeln();
}
}
이게 끝이다. 이제 사용해보자.
import '../../../lib/utility/extensions/stringbuffer_.dart'; // 필수
test('result page model', () {
StringBuffer buffer = StringBuffer();
buffer.writeWithIndent('"');
});
확장 메소드를 사용하려면 반드시 임포트를 해주어야 한다. 이것은 IDE가 자동으로 해주지 않는다.
기존에 있던 모든 클래스에 새로운 메소드가 추가되는 것을 막아준다.
임포트만 하면 그때부터는 메소드 자동완성 같은 것들이 지원된다.
똑같은 이름의 extension method 가 겹치면 문제가 된다.
기존에 만든 StringBufferIndent 를 다른 팀원들도 사용하기 시작했고, 거기에 여러 유용한 함수를 추가해서 사용하고 있었다.
팀에 새로 온 개발자가 다른 팀에서 쓰던 extension 에 확장 메소드를 만들었다고 해보자.
extension StringBufferTeamB on StringBuffer {
void writeWithIndent(Object? object, {int sCount = 0}) {
for (int i = 0; i < sCount; i++) {
write(' ');
}
write(object);
}
void writelnWithIndent(Object? object, {int sCount = 0}) {
writeWithIndent(
object,
sCount: sCount,
);
writeln();
}
// Team B 에서 쓰던 많은 유용한 메소드
}
우연히 메소드의 이름이 겹쳐버렸고 테스트에서 에러가 떠 버린다.
import '../../../lib/utility/extensions/stringbuffer_.dart'; // 필수
import '../../../lib/utility/extensions/stringbuffer_b.dart'; // 필수
test('result page model', () {
StringBuffer buffer = StringBuffer();
buffer.writeWithIndent('"'); // ? 충돌
});
충돌
해결하는 방법은 3가지가 있다.
hide를 이용해서 한 쪽의 extension을 쓰지 않는다.
import '../../../lib/utility/extensions/stringbuffer_.dart';
import '../../../lib/utility/extensions/stringbuffer_b.dart'
hide StringBufferTeamB;
이 방법은 간단하지만 주의해야 한다. 왜냐면 확장 메소드를 hide하는게 아니라 extension 전체를 hide 해줘야 한다. 그러니 extension method를 만들 때 최대한 기능 별로 쪼개주는게 좋다.
명시적으로 사용할 extension을 지정해준다.
StringBufferTeamB(buffer).writeWithIndent('"');
마치 wrapper class 처럼 buffer 를 감싸줘서 이건 B팀의 extension의 메소드를 사용한다고 지정해준다.
만약 extension의 이름 자체가 똑같다면 이 방법을 쓸 수 없다. 만약 extension 이름까지 StringBufferIndent 로 똑같다면 아래 코드처럼 해야한다.
import '../../../lib/utility/extensions/stringbuffer_b.dart' as teamb;
...
teamb.StringBufferIndent(buffer).writeWithIndent('"');
as를 이용해서 prefix 를 넣어준다.
getter, setter, operator 등등을 확장 메소드로 만들 수 있다. field만 빼고!
generic 하게 만들 수 도 있는데 그냥 그런 게 있는 갑다 하면 된다.
extension MyFancyList<T> on List<T> {
int get doubleLength => length * 2;
List<T> operator -() => reversed.toList();
List<List<T>> split(int at) => [sublist(0, at), sublist(at)];
}
https://dart.dev/guides/language/extension-methods#using-extension-methods