BUILDER
designpattern
BUILDER
Builder is a creational design pattern that lets you construct complex objects step by step.
Design Principles
Structure:
- Separate the construction of a complex object from its representation so that the same construction process can create different representations.
DataSource Examples
예를 들어 커넥션 설정 하는 경우를 생각해보자. 이 경우, user, password, url, hostName, databaseName, port 등 다양한 속성을 생성자의 파라미터로 받거나 setter 를 제공하여 객체를 생성할 수 있다.
이 경우 다음과 같은 문제가 존재한다.
- 특정 속성이 설정되기 위해서 다른 속성에 의존하는 경우 (즉, 순서에 의존하는 경우)
- 객체지향 프로그래밍에서 캡슐화 특성에 대한 정의에 따르면, 내부 데이터는 접근 권한 제어를 통해 숨겨져야 하며, 외부에서는 클래스에서 제공하는 제한된 인터페이스를 통해서만 내부 데이터에 접근할 수 있어야 한다. 따라서 노출해서는 안 되는 setter 메서드를 노출하는 것은 객체지향 프로그래밍의 캡슐화 특성을 명백하게 위반하고 있는 것이다. 이러한 데이터는 어떤 코드도 마음대로 수정할 수 있으며 결국 절차적 프로그래밍 스타일로 퇴화된다.
data class ConnectionConfig private constructor(
val user: String,
val password: String,
val url: String?,
val hostName: String?,
val databaseName: String?,
val port: Int?
) {
class Builder {
private var user: String? = null
private var password: String? = null
private var url: String? = null
private var hostName: String? = null
private var databaseName: String? = null
private var port: Int? = null
fun user(user: String) = apply { this.user = user }
fun password(password: String) = apply { this.password = password }
fun url(url: String) = apply {
this.url = url
this.hostName = null // url이 설정되면 hostName을 비활성화
}
fun hostName(hostName: String) = apply {
if (this.url != null) {
throw IllegalStateException("URL이 설정되면 hostName을 설정할 수 없습니다.")
}
this.hostName = hostName
}
fun databaseName(databaseName: String) = apply { this.databaseName = databaseName }
fun port(port: Int) = apply {
require(port in 1024..65535) { "포트 번호는 1024~65535 사이여야 합니다." }
this.port = port
}
fun build(): ConnectionConfig {
// 필수 필드 검증
requireNotNull(user) { "user는 필수 값입니다." }
requireNotNull(password) { "password는 필수 값입니다." }
require(url != null || hostName != null) { "url 또는 hostName 중 하나는 필수입니다." }
return ConnectionConfig(user!!, password!!, url, hostName, databaseName, port)
}
}
}
빌더 클래스에서는 필수 항목, 의존성, 제약 조건에 대한 검증을 수행할 수 있다. 이 부분이 빌더 패턴을 사용해야하는 판단을 내릴 때 중요한 근거가 된다. 커넥션을 위한 객체(e.g 캐시를 위한 객체) 등 특정 객체를 구성함에 있어서 객체를 구성할 때 필요한 매개변수의 유효성 검사 를 위해서 빌더 패턴을 사용할 수 있다. 생성자와 Setter 를 사용하는 경우에는 생성자에서는 필수 유효성을 검사할 수 있지만, setter 를 추가 제공해야 한다.
Client:
fun main() {
// URL 을 이용한 커넥션 설정
val connection1 = ConnectionConfig.Builder()
.user("admin")
.password("securePassword")
.url("jdbc:mysql://example.com:3306/dbname")
.build()
// HostName 과 Port 를 이용한 커넥션 설정
val connection2 = ConnectionConfig.Builder()
.user("admin")
.password("securePassword")
.hostName("example.com")
.databaseName("myDB")
.port(3306)
.build()
}
FACTORY 와의 차이점은, 팩터리는 인터페이스를 상속하는 연관된 비슷한 유형의 객체를 만들때 사용하며 BUILDER 는 동일 유형의 복잡한 객체를 생성할 때 사용한다.
References
- Gangs of Four Design Patterns
- 设计模式之美 / 王争