Builder Design Pattern
🔥 Flutter에서 Builder 패턴이 유용한 상황들
1. 복잡한 위젯 구성 (가장 흔함)
dart
// 복잡한 커스텀 버튼 위젯
class CustomButton {
final String text;
final Color? backgroundColor;
final Color? textColor;
final EdgeInsets? padding;
final BorderRadius? borderRadius;
final VoidCallback? onPressed;
final IconData? icon;
final double? elevation;
CustomButton._({
required this.text,
this.backgroundColor,
this.textColor,
this.padding,
this.borderRadius,
this.onPressed,
this.icon,
this.elevation,
});
// Builder 패턴 적용
static CustomButtonBuilder builder(String text) {
return CustomButtonBuilder._(text);
}
}
class CustomButtonBuilder {
final String _text;
Color? _backgroundColor;
Color? _textColor;
EdgeInsets? _padding;
BorderRadius? _borderRadius;
VoidCallback? _onPressed;
IconData? _icon;
double? _elevation;
CustomButtonBuilder._(this._text);
CustomButtonBuilder backgroundColor(Color color) {
_backgroundColor = color;
return this;
}
CustomButtonBuilder textColor(Color color) {
_textColor = color;
return this;
}
CustomButtonBuilder padding(EdgeInsets padding) {
_padding = padding;
return this;
}
CustomButtonBuilder rounded([double radius = 8.0]) {
_borderRadius = BorderRadius.circular(radius);
return this;
}
CustomButtonBuilder onPressed(VoidCallback callback) {
_onPressed = callback;
return this;
}
CustomButtonBuilder withIcon(IconData icon) {
_icon = icon;
return this;
}
CustomButtonBuilder elevation(double elevation) {
_elevation = elevation;
return this;
}
Widget build() {
return ElevatedButton.icon(
onPressed: _onPressed,
icon: _icon != null ? Icon(_icon) : SizedBox.shrink(),
label: Text(_text, style: TextStyle(color: _textColor)),
style: ElevatedButton.styleFrom(
backgroundColor: _backgroundColor,
padding: _padding,
shape: RoundedRectangleBorder(
borderRadius: _borderRadius ?? BorderRadius.zero,
),
elevation: _elevation,
),
);
}
}
// 사용법
Widget myButton = CustomButton.builder("확인")
.backgroundColor(Colors.blue)
.textColor(Colors.white)
.rounded(12.0)
.padding(EdgeInsets.symmetric(horizontal: 24, vertical: 12))
.withIcon(Icons.check)
.elevation(4.0)
.onPressed(() => print("클릭됨"))
.build();2. 네트워크 요청 설정
dart
class ApiRequest {
final String url;
final String method;
final Map<String, String>? headers;
final dynamic body;
final Duration? timeout;
final bool retryOnError;
final int maxRetries;
ApiRequest._({
required this.url,
required this.method,
this.headers,
this.body,
this.timeout,
this.retryOnError = false,
this.maxRetries = 3,
});
static ApiRequestBuilder get(String url) {
return ApiRequestBuilder._(url, 'GET');
}
static ApiRequestBuilder post(String url) {
return ApiRequestBuilder._(url, 'POST');
}
}
class ApiRequestBuilder {
final String _url;
final String _method;
Map<String, String> _headers = {};
dynamic _body;
Duration? _timeout;
bool _retryOnError = false;
int _maxRetries = 3;
ApiRequestBuilder._(this._url, this._method);
ApiRequestBuilder headers(Map<String, String> headers) {
_headers.addAll(headers);
return this;
}
ApiRequestBuilder addHeader(String key, String value) {
_headers[key] = value;
return this;
}
ApiRequestBuilder body(dynamic body) {
_body = body;
return this;
}
ApiRequestBuilder timeout(Duration timeout) {
_timeout = timeout;
return this;
}
ApiRequestBuilder enableRetry({int maxRetries = 3}) {
_retryOnError = true;
_maxRetries = maxRetries;
return this;
}
ApiRequest build() {
return ApiRequest._(
url: _url,
method: _method,
headers: _headers.isEmpty ? null : _headers,
body: _body,
timeout: _timeout,
retryOnError: _retryOnError,
maxRetries: _maxRetries,
);
}
}
// 사용법
ApiRequest loginRequest = ApiRequest.post('/api/login')
.addHeader('Content-Type', 'application/json')
.addHeader('Accept', 'application/json')
.body({'username': 'user', 'password': 'pass'})
.timeout(Duration(seconds: 10))
.enableRetry(maxRetries: 2)
.build();
ApiRequest getUserRequest = ApiRequest.get('/api/user/123')
.addHeader('Authorization', 'Bearer token')
.timeout(Duration(seconds: 5))
.build();3. 다이얼로그 Builder
dart
class CustomDialog {
final String? title;
final String? message;
final List<DialogAction> actions;
final Widget? customContent;
final bool dismissible;
final Color? backgroundColor;
CustomDialog._({
this.title,
this.message,
this.actions = const [],
this.customContent,
this.dismissible = true,
this.backgroundColor,
});
static CustomDialogBuilder builder() {
return CustomDialogBuilder._();
}
}
class DialogAction {
final String text;
final VoidCallback onPressed;
final bool isDefault;
DialogAction(this.text, this.onPressed, {this.isDefault = false});
}
class CustomDialogBuilder {
String? _title;
String? _message;
List<DialogAction> _actions = [];
Widget? _customContent;
bool _dismissible = true;
Color? _backgroundColor;
CustomDialogBuilder._();
CustomDialogBuilder title(String title) {
_title = title;
return this;
}
CustomDialogBuilder message(String message) {
_message = message;
return this;
}
CustomDialogBuilder addAction(String text, VoidCallback onPressed, {bool isDefault = false}) {
_actions.add(DialogAction(text, onPressed, isDefault: isDefault));
return this;
}
CustomDialogBuilder customContent(Widget content) {
_customContent = content;
return this;
}
CustomDialogBuilder dismissible(bool dismissible) {
_dismissible = dismissible;
return this;
}
CustomDialogBuilder backgroundColor(Color color) {
_backgroundColor = color;
return this;
}
Future<void> show(BuildContext context) {
return showDialog(
context: context,
barrierDismissible: _dismissible,
builder: (context) => AlertDialog(
backgroundColor: _backgroundColor,
title: _title != null ? Text(_title!) : null,
content: _customContent ?? (_message != null ? Text(_message!) : null),
actions: _actions.map((action) => TextButton(
onPressed: action.onPressed,
child: Text(action.text, style: TextStyle(
fontWeight: action.isDefault ? FontWeight.bold : FontWeight.normal,
)),
)).toList(),
),
);
}
}
// 사용법
// 간단한 확인 다이얼로그
CustomDialog.builder()
.title("삭제 확인")
.message("정말로 삭제하시겠습니까?")
.addAction("취소", () => Navigator.of(context).pop())
.addAction("삭제", () {
// 삭제 로직
Navigator.of(context).pop();
}, isDefault: true)
.show(context);
// 커스텀 컨텐츠 다이얼로그
CustomDialog.builder()
.title("설정")
.customContent(Column(
mainAxisSize: MainAxisSize.min,
children: [
SwitchListTile(
title: Text("알림 설정"),
value: true,
onChanged: (value) {},
),
SwitchListTile(
title: Text("자동 업데이트"),
value: false,
onChanged: (value) {},
),
],
))
.addAction("닫기", () => Navigator.of(context).pop())
.backgroundColor(Colors.grey[100])
.show(context);4. Director 패턴을 활용한 테마 Builder
dart
// Director 역할을 하는 클래스
class ThemeDirector {
final CustomButtonBuilder _builder;
ThemeDirector(this._builder);
// 기본 버튼 스타일 (Director가 지시)
Widget buildPrimaryButton(String text, VoidCallback onPressed) {
return _builder
.backgroundColor(Colors.blue)
.textColor(Colors.white)
.rounded(8.0)
.padding(EdgeInsets.symmetric(horizontal: 16, vertical: 12))
.elevation(2.0)
.onPressed(onPressed)
.build();
}
// 경고 버튼 스타일 (Director가 지시)
Widget buildWarningButton(String text, VoidCallback onPressed) {
return _builder
.backgroundColor(Colors.orange)
.textColor(Colors.white)
.rounded(12.0)
.padding(EdgeInsets.symmetric(horizontal: 20, vertical: 14))
.withIcon(Icons.warning)
.elevation(4.0)
.onPressed(onPressed)
.build();
}
// 성공 버튼 스타일 (Director가 지시)
Widget buildSuccessButton(String text, VoidCallback onPressed) {
return _builder
.backgroundColor(Colors.green)
.textColor(Colors.white)
.rounded(20.0)
.padding(EdgeInsets.symmetric(horizontal: 24, vertical: 16))
.withIcon(Icons.check_circle)
.elevation(6.0)
.onPressed(onPressed)
.build();
}
}
// 사용법
class MyWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
final director = ThemeDirector(CustomButton.builder(""));
return Column(
children: [
director.buildPrimaryButton("로그인", () {}),
SizedBox(height: 10),
director.buildWarningButton("주의", () {}),
SizedBox(height: 10),
director.buildSuccessButton("완료", () {}),
],
);
}
}🎯 Builder 패턴을 사용해야 하는 경우
✅ 사용하면 좋은 상황
생성자 매개변수가 5개 이상
선택적 매개변수가 많은 경우
불변 객체를 만들고 싶은 경우
복잡한 설정이 필요한 객체
같은 구조로 다양한 변형이 필요한 경우
❌ 사용하지 않는 것이 좋은 상황
간단한 객체 (매개변수 3개 이하)
자주 변경되지 않는 객체
성능이 매우 중요한 경우 (객체 생성 오버헤드)
💡 핵심 정리
Builder: 객체를 "어떻게" 만들지 정의
Director: 객체를 "무엇을", "어떤 순서로" 만들지 지시
Last updated