시리즈

[dart/flutter] extension method 가 뭔데?

20220401
class
dart
extension
extension method
flutter
language
method
programming
spec
syntax

 mixin 과 함께 자주 쓰면 코드를 단순하게 만들 수 있는 좋은 문법

0. extension method란?

만약에 Row 안에 있는 children의 개수를 반환하는 함수를 만들고 싶다고 하자.

그러면 Row.children.length 를 호출하는 함수를 만들 수 있다.

그런데 이게 Row 안에 메소드로 있다면 더 좋지 않을까?

하지만 NewRow나 ExtendedRow 같은 Row의 확장 클래스를 만드는 것은 좋지 않다.

Row는 매우 많이 쓰이는 문법이고 많은 사람들이 Row가 어떻게 동작할 지를 예측 가능하지만, NewRow를 보면 이게 뭔지 코드나 문서를 봐야 알 수 있을 것이다. 또한, 기존 코드의 Row를 모두 바꾸어 주어야 한다.

기존 코드를 건드리지 않고 새로운 기능(메소드) 를 추가하려면 extension method 를 사용하자.

이 글에서는 StringBuffer를 가지고 한 번 만들어 보겠다.

StringBuffer를 이용하면 문자열을 효율적으로 만들 수 있지만, write, writeln 같은 단순한 함수 뿐이다. 만약에 들여쓰기를 해서 write 해주는 메소드가 있으면 더 좋을 것 같다. 이걸 extension method로 만들어보자.

1. 선언하는 법

선언은 매우 간단하다.

새로운 다트 파일을 하나 만들어서 (필수)

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

이게 끝이다. 이제 사용해보자.

2. 사용하기

import '../../../lib/utility/extensions/stringbuffer_.dart'; // 필수

test('result page model', () {
    StringBuffer buffer = StringBuffer();
    buffer.writeWithIndent('"');
});

확장 메소드를 사용하려면 반드시 임포트를 해주어야 한다. 이것은 IDE가 자동으로 해주지 않는다.

기존에 있던 모든 클래스에 새로운 메소드가 추가되는 것을 막아준다.

임포트만 하면 그때부터는 메소드 자동완성 같은 것들이 지원된다.

3. 충돌 💥

똑같은 이름의 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가지가 있다.

  1. hide를 이용해서 한 쪽의 extension을 쓰지 않는다.

    import '../../../lib/utility/extensions/stringbuffer_.dart';
    import '../../../lib/utility/extensions/stringbuffer_b.dart'
        hide StringBufferTeamB;
    

    이 방법은 간단하지만 주의해야 한다. 왜냐면 확장 메소드를 hide하는게 아니라 extension 전체를 hide 해줘야 한다. 그러니 extension method를 만들 때 최대한 기능 별로 쪼개주는게 좋다.

  2. 명시적으로 사용할 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)];
}

References

https://dart.dev/guides/language/extension-methods#using-extension-methods

https://medium.com/dartlang/extension-methods-2d466cd8b308

.

piano (press key Q)

Categories

flutter ( 82 ) dart ( 34 ) android ( 32 ) kotlin ( 11 ) plugin ( 8 ) provider ( 8 ) vim ( 7 ) bloc ( 6 ) iOS ( 6 ) state management ( 6 ) 플러터 ( 6 ) PS ( 5 ) algorithm ( 5 ) architecture ( 5 ) async ( 5 ) getx ( 5 ) java ( 5 ) API ( 4 ) BOJ ( 4 ) class ( 4 ) daily ( 4 ) git ( 4 ) golang ( 4 ) memo ( 4 ) riverpod ( 4 ) state ( 4 ) stream ( 4 ) test ( 4 ) web ( 4 ) widget ( 4 ) windows ( 4 ) HTTP ( 3 ) androidX ( 3 ) app state ( 3 ) context ( 3 ) crash ( 3 ) db ( 3 ) editor ( 3 ) error ( 3 ) extension ( 3 ) github ( 3 ) hive ( 3 ) ide ( 3 ) package ( 3 ) pubspec ( 3 ) python ( 3 ) syntax ( 3 ) vscode ( 3 ) app icon ( 2 ) await ( 2 ) chocolatey ( 2 ) consumer ( 2 ) cp949 ( 2 ) deployment ( 2 ) dev ( 2 ) flavor ( 2 ) gesture ( 2 ) globalkey ( 2 ) go ( 2 ) google ( 2 ) hack ( 2 ) js ( 2 ) json ( 2 ) key ( 2 ) keystore ( 2 ) list ( 2 ) listview ( 2 ) lock ( 2 ) mac ( 2 ) map ( 2 ) navigation ( 2 ) nosql ( 2 ) project ( 2 ) pub ( 2 ) recyclerview ( 2 ) rxdart ( 2 ) sdk ( 2 ) selector ( 2 ) setting ( 2 ) size ( 2 ) soc ( 2 ) synchronized ( 2 ) tdd ( 2 ) tip ( 2 ) version ( 2 ) viewmodel ( 2 ) vundle ( 2 ) webview ( 2 ) xcode ( 2 ) yaml ( 2 ) ( 2 ) 플러터 단점 ( 2 ) 16.0 ( 1 ) 2.0 ( 1 ) 2023 ( 1 ) AATP2 ( 1 ) ChangeNotifierProvider ( 1 ) Example ( 1 ) Guava ( 1 ) ImageReader ( 1 ) Mo's algorithm ( 1 ) OAuth2 ( 1 ) OpenGL ( 1 ) Oreo ( 1 ) ProgressBar ( 1 ) REST API ( 1 ) Trie ( 1 ) activity ( 1 ) adaptive ( 1 ) android P ( 1 ) android context ( 1 ) android11 ( 1 ) apktool2 ( 1 ) app exit ( 1 ) append ( 1 ) appicon ( 1 ) arkit ( 1 ) array ( 1 ) asciidoc ( 1 ) async * ( 1 ) async* ( 1 ) audio ( 1 ) authorization ( 1 ) await for ( 1 ) behaviorsubject ( 1 ) beta ( 1 ) binary ( 1 ) binarysearch ( 1 ) blender ( 1 ) book ( 1 ) bottomsheet ( 1 ) break ( 1 ) broadcast ( 1 ) browser ( 1 ) bubbles ( 1 ) bug ( 1 ) build ( 1 ) buildcontext ( 1 ) buildnumber ( 1 ) bundle ( 1 ) button ( 1 ) bytecode ( 1 ) cache ( 1 ) camera2 ( 1 ) cameramanager ( 1 ) cd ( 1 ) chrome ( 1 ) ci ( 1 ) circle ( 1 ) clean ( 1 ) clean architecture ( 1 ) cli ( 1 ) clip ( 1 ) clipboard ( 1 ) cloud ide ( 1 ) cmdlet ( 1 ) code ( 1 ) coding test ( 1 ) command ( 1 ) comparator ( 1 ) complexity ( 1 ) concurrency ( 1 ) conditional ( 1 ) const ( 1 ) constraint ( 1 ) constraintlayout ( 1 ) controlc ( 1 ) controlv ( 1 ) converter ( 1 ) copy ( 1 ) copy project ( 1 ) coupling ( 1 ) coverage ( 1 ) cp ( 1 ) css ( 1 ) cupertino ( 1 ) cursor ( 1 ) cv ( 1 ) data class ( 1 ) data structure ( 1 ) dataBinding ( 1 ) database ( 1 ) debounce ( 1 ) decompile ( 1 ) delegate ( 1 ) deno ( 1 ) design pattern ( 1 ) development ( 1 ) device ( 1 ) di ( 1 ) dialog ( 1 ) dio ( 1 ) drawable ( 1 ) drug ( 1 ) emmet ( 1 ) encoding ( 1 ) english ( 1 ) entries ( 1 ) environment ( 1 ) equality ( 1 ) equatable ( 1 ) euc-kr ( 1 ) euckr ( 1 ) exit ( 1 ) expand ( 1 ) expanded ( 1 ) export ( 1 ) extension method ( 1 ) facade ( 1 ) fake ( 1 ) field ( 1 ) figma ( 1 ) final ( 1 ) fixed ( 1 ) flutter pub ( 1 ) flutter web ( 1 ) flutter_inappwebview ( 1 ) flutter_test ( 1 ) flutterflow ( 1 ) fold ( 1 ) fonts ( 1 ) form ( 1 ) frame ( 1 ) future ( 1 ) gestureDetector ( 1 ) gestureRecognizer ( 1 ) gesturearena ( 1 ) get-command ( 1 ) get_cli ( 1 ) getbuilder ( 1 ) getx단점 ( 1 ) gitignore ( 1 ) glut ( 1 ) google fonts ( 1 ) gopath ( 1 ) goto ( 1 ) gradient ( 1 ) graphics ( 1 ) gvim ( 1 ) hackaton ( 1 ) hash ( 1 ) hashmap ( 1 ) hot reload ( 1 ) how to ( 1 ) html ( 1 ) i18n ( 1 ) icon ( 1 ) id ( 1 ) impeller ( 1 ) implementation ( 1 ) import ( 1 ) indicator ( 1 ) inkwell ( 1 ) interrupt ( 1 ) intl ( 1 ) introduction ( 1 ) io ( 1 ) isar ( 1 ) iterable ( 1 ) iteration ( 1 ) javascript ( 1 ) julia ( 1 ) juno ( 1 ) jupyter ( 1 ) kakaomap ( 1 ) keytool ( 1 ) korean ( 1 ) kotlin syntax ( 1 ) l10n ( 1 ) lambda ( 1 ) language ( 1 ) layer ( 1 ) layout ( 1 ) lineageOS ( 1 ) localkey ( 1 ) localtoglobal ( 1 ) long list ( 1 ) ls ( 1 ) mac osx ( 1 ) markdown ( 1 ) markup ( 1 ) material ( 1 ) method ( 1 ) microtask ( 1 ) migrate ( 1 ) mintlify ( 1 ) mock ( 1 ) module ( 1 ) monitor ( 1 ) moor ( 1 ) mouse ( 1 ) mouseregion ( 1 ) multiplatform ( 1 ) multiset ( 1 ) multithread ( 1 ) mutable ( 1 ) mvvm ( 1 ) new ( 1 ) node ( 1 ) nodejs ( 1 ) nosuchmethod ( 1 ) null-safety ( 1 ) numberformat ( 1 ) nvim ( 1 ) object ( 1 ) objectbox ( 1 ) objectkey ( 1 ) obx ( 1 ) online ide ( 1 ) operator ( 1 ) orientation ( 1 ) parabeac ( 1 ) parse ( 1 ) paste ( 1 ) path ( 1 ) pattern ( 1 ) pitfall ( 1 ) play store ( 1 ) pod ( 1 ) podfile ( 1 ) pointer ( 1 ) pointers ( 1 ) powershell ( 1 ) private ( 1 ) programming ( 1 ) pull to refresh ( 1 ) puzzle ( 1 ) pycharm ( 1 ) realitykit ( 1 ) recursion ( 1 ) reduce ( 1 ) reference ( 1 ) regex ( 1 ) regular expression ( 1 ) release note ( 1 ) renderbox ( 1 ) renderobject ( 1 ) repl ( 1 ) repository ( 1 ) response ( 1 ) rm ( 1 ) rotue ( 1 ) round ( 1 ) run ( 1 ) scope ( 1 ) scroll ( 1 ) search ( 1 ) server ( 1 ) serverless ( 1 ) service ( 1 ) sharp ( 1 ) singlerepo ( 1 ) singleton ( 1 ) sketch ( 1 ) sliver ( 1 ) sliverlist ( 1 ) snippets ( 1 ) sogae ( 1 ) sorting ( 1 ) source ( 1 ) sparse ( 1 ) sparse array ( 1 ) spec ( 1 ) split ( 1 ) sqflite ( 1 ) sqlite ( 1 ) sqrt decomposition ( 1 ) stateful ( 1 ) statefulwidget ( 1 ) step ( 1 ) stepper ( 1 ) string ( 1 ) stringbuffer ( 1 ) stringbuilder ( 1 ) studio ( 1 ) study ( 1 ) sub-directory ( 1 ) svn ( 1 ) swiftui ( 1 ) swipe to refresh ( 1 ) system_alert_window ( 1 ) system_cache ( 1 ) systemnavigator ( 1 ) tail recursion ( 1 ) tailrec ( 1 ) tap test ( 1 ) text ( 1 ) texteditingcontroller ( 1 ) textfield ( 1 ) texttheme ( 1 ) themedata ( 1 ) then ( 1 ) thread ( 1 ) throttle ( 1 ) time ( 1 ) tool ( 1 ) tools ( 1 ) tooltip ( 1 ) ts ( 1 ) tutorial ( 1 ) typescript ( 1 ) ui ( 1 ) unittest ( 1 ) update ( 1 ) usb ( 1 ) utf8 ( 1 ) ux ( 1 ) valuekey ( 1 ) variable ( 1 ) vector ( 1 ) versioncode ( 1 ) very_good ( 1 ) view ( 1 ) vim plugin ( 1 ) vimrc ( 1 ) virtualenv ( 1 ) wasm ( 1 ) web app ( 1 ) webview_flutter ( 1 ) while ( 1 ) widget tree ( 1 ) window ( 1 ) wsl ( 1 ) yield ( 1 ) 강의 ( 1 ) 개발 ( 1 ) 개발 공부 ( 1 ) 공부법 ( 1 ) 그래픽스 ( 1 ) 꼬리재귀 ( 1 ) 꿀팁 ( 1 ) 데노 ( 1 ) 두줄 ( 1 ) 디노 ( 1 ) 번역 ( 1 ) 블록 ( 1 ) 상태관리 ( 1 ) 실험 ( 1 ) 안드로이드 ( 1 ) 안드로이드프로젝트 ( 1 ) 안드로이드프로젝트복사 ( 1 ) 어이없는 ( 1 ) 조건부 임포트 ( 1 ) 주절주절분노조절실패의식으흐름 ( 1 ) 패키지 ( 1 ) 프로젝트복사 ( 1 ) 플러그인 ( 1 )