빌터 패턴은 객체의 생성과 객체를 표현하는 속성들과 분리하여 생성 절차는 동일하지만 결과는 다르게 만드는 패턴이다
비슷한 역할을 하는 객체를 생성하기 위해서 매번 코드를 작성하는 것보다 속성만 바꿀 수 있고 동일한 생성을 할 수 있는 Builder를 만든다면 재사용성 높은 코드를 사용할 수 있다.
BuilderPattern 에서 중요한 3가지 역할은 아래와 같다
먼저 위에처럼 재사용성 있는 코드를 작성하기 위해서는 당연히 프로토콜이 필요하다!
재사용하려는 프로퍼티와 메서드를 아래와 같이 넣는다.
// Builder protocol
protocol Builder {
var label: UILabel { get }
func setText(with text: String) -> Builder
func setTextColor(with textColor: UIColor) -> Builder
func setFontSize(with textFontSize: CGFloat) -> Builder
}
다음으로 이 프로토콜을 채택해서 실제 어떤 속성을 지정해줘야하는지 정해야하는 클래스가 필요하다
// Builder 채택 후 상세 속성 지정하는 역할
class ConCreateBuilder: Builder {
var label: UILabel = UILabel()
func setText(with text: String) -> Builder {
label.text = text
return self
}
func setTextColor(with textColor: UIColor) -> Builder {
label.textColor = textColor
return self
}
func setFontSize(with textFontSize: CGFloat) -> Builder {
label.font = .systemFont(ofSize: textFontSize)
return self
}
}
이렇게 아직 존재하지 않는 객체지만, 만약 생성한다면 위에처럼 만들것 이라고 속성을 미리 정해둔다. 물론 값들은 실제 초기화시 받아야한다!
이제 이를 실제 사용하는 객체를 만들 것이다!
방법은 두가지가 있고 첫 번째는 메서드의 파라미터의 인자로 받아서 이를 활용한다.
class Director {
func makeLabel(builder: Builder) -> UILabel {
let build = builder
build.setText(with: "Eddy")
build.setFontSize(with: 40)
build.setTextColor(with: .red)
return build.label
}
}
그리고 이를 실제 사용할 때에는 ViewController
에서 인스턴스 생성 후 viewDidLoad()
에서 아래와 같이 적어줌으로써 뷰에서 보여지도록 한다.
class ViewController: UIViewController {
private let director: Director = Director()
override func viewDidLoad() {
super.viewDidLoad()
let label = director.makeLabel(builder: ConCreateBuilder())
self.view.addSubview(label)
label.frame = CGRect(x: 0, y: 0, width: 100, height: 100)
}
}
그리고 또 다른 방식으로는 chain를 활용해서도 생성할 수 있다
그 코드는 아래와 같다
private let tmpLabel: UILabel = ConCreateBuilder()
.setText(with: "eddy2")
.setTextColor(with: .blue)
.setFontSize(with: 30)
.label
이처럼 chain으로 값을 정해주고 마지막에 label
이라는 표시를 해준다! 그럼 어떤 타입인지 알려주게 되고
실제 이는 위에 label
처럼 addSubView
하고 위치를 잡아주면 된다.
self.view.addSubview(tmpLabel)
tmpLabel.frame = CGRect(x: 0, y: 200, width: 100, height: 100)
}
그럼 아래와 같이 시뮬레이터에서 들어가는 것을 볼 수 있다.
이것을 더 업그레이드 시켜서 실제 프로젝트에서 Alert
에 활용하는 것을 보여주려고한다!
기존의 빌더패턴 방식과 조금 다르지만, 사용해보며 차이점을 익혀보면 좋을 듯싶다.
제일 먼저 AlertAction
이라면 필요한 것들을 선언해준다.
이곳에서는 title
, alert style
, completion
3가지가 필요하다!
import UIKit
struct AlertAction {
let title: String
let style: UIAlertAction.Style
let completion: (() -> Void)?
}
그 다음에는 Builder Pattern
를 활용해서 Alert
의 템플릿?!을 만들면 된다.
먼저 ViewController
를 가지고 있어야 하며 alertAction
들을 담을 수 있는 배열을 생성한다.
그리고 Action
를 넣는 메서드를 통해 AlertAction
의 값들을 파라미터로 넣을 수 있도록 구현하고 이를 배열에 넣어준다.
그리고 show
메서드에서는 실제 alert
를 보여주는 방식에 대해 작성하면 된다.
UIAlertController
를 통해 alertController
를 만들어 준다.
그리고 아까 만든 alertActions
에 대한 배열을 반복문을 통해 alertAction
를 만들어준다!
그리고 이것을 alertController
의 액션으로 추가해서 넣은 다음 ViewController
에서 실행시켜주면 된다.
원래였다면 호출부에서 선언하던 것들을 이곳에서 템플릿처럼 만들어 둔것뿐이다!
import UIKit
final class AlertBuilder {
private weak var viewController: UIViewController?
private var alertActions: [AlertAction] = []
init(target: UIViewController) {
self.viewController = target
}
func addAction(_ title: String, style: UIAlertAction.Style, action: (() -> Void)? = nil) -> Self {
let alertAction = AlertAction(title: title, style: style, completion: action)
alertActions.append(alertAction)
return self
}
func show(_ title: String? = nil, message: String? = nil, style: UIAlertController.Style) {
let alertController = UIAlertController(title: title, message: message, preferredStyle: style)
alertActions.forEach { action in
let alertAction = UIAlertAction(title: action.title, style: action.style) { _ in
action.completion?()
}
alertController.addAction(alertAction)
}
viewController?.present(alertController, animated: true)
}
}
실제 사용처에서는 아래와 같이 사용하면 된다.
builder
를 호출하고, target
에는 ViewController
가 들어가므로 위치에 있는 self
를 넣어준다.
그리고 필요한 액션을 추가해주면 된다.
그다음으로 이곳에서는 diary
라는 모델을 사용하고 있는데 이는 이 행동을 통해 모델의 값들을 지우기 위해 만든 로직이다!
그래서 이 부분은 다르게 사용하고 싶다면 자신만의 방식으로 변경해도된다.
마지막에 보면 show
를 통해 유저에게 alert
창을 보여줄 것이다.
이렇게 간단하게 사용이 가능해지고 여러 곳에서도 공통으로 쓸 수 있는 템플릿이 만들어진 것이다.
func showDeleteAlert() {
AlertBuilder(target: self).addAction("취소", style: .default)
.addAction("삭제", style: .destructive) { [weak self] in
guard let diary = self?.diary else { return }
self?.delegate?.delete(diary)
self?.navigationController?.popViewController(animated: true)
}.show("진짜요?", message: "정말로 삭제하시겠어요?", style: .alert)
}
결론적으로, 재사용성을 높일 수 있는 패턴같다!
만약 비슷한 작업이 이루어지지만 내부 속성만 바꾸어야 한다면 빌더패턴을 활용해서 반복작업을 줄일 수 있을 것이다.
참고 사이트