Builder Design Pattern

Builder pattern'ini oldukça sık kullanılan Creational Design Pattern 'lerden birisidir. Class'larınızda çok fazla parametre varsa veya farklı parametreler ile farklı constructor'larınız varsa kullanabileceğiniz bir patern'dir. 3 bileşenden oluşmaktadır. Bunlar; Director, Builder ve Product'dır.

Builder design pattern 3 bileşeni

Aşağıda çok yaygın olarak kullanılan bir örnek paylaşıyorum.

Product: Oluşturulacak karmaşık nesnelerdir.

URLRequest and URLSessionDataTask mevcutta olduğu için yeniden oluşturmaya gerek yok sadece kullanılacak property'ler tanımlanmıştır.

enum Method: String {
    case GET
    case POST
}

struct RequestBaseUrls {
    static let gitHub = "https://api.github.com"
}

struct RequestEndpoints {
    static let searchRepositories = "/search/repositories"
}
Product

Builder: girdileri adım adım ekler ve ürünün oluşmasını yönetir. Genellikle sınıf tipindedir ve refrans olarak istenildiğinde yeniden kullanılabilir.

class RequestBuilder {
    private(set) var baseURL: URL!
    private(set) var endpoint: String = ""
    private(set) var method: Method = .GET
    private(set) var headers: [String: String] = [:]
    private(set) var parameters: [String: String] = [:]
    
    public func setBaseUrl(_ string: String) {
        baseURL = URL(string: string)!
    }
    public func setEndpoint(_ value: String) {
        endpoint = value
    }
    public func setMethod(_ value: Method) {
        method = value
    }
    public func addHeader(_ key: String, _ value: String) {
        headers[key] = value
    }
    public func addParameter(_ key: String, _ value: String) {
        parameters[key] = value
    }

    public func build() -> URLRequest {
        assert(baseURL != nil)
        let url = baseURL.appendingPathComponent(endpoint)
        var components = URLComponents(string: url.absoluteString)

        components?.queryItems = parameters.compactMap { 
        URLQueryItem(name: $0.key, value: $0.value) }

        var urlRequest = URLRequest(url: components!.url!)
        urlRequest.httpMethod = method.rawValue

        for header in headers {
            urlRequest.addValue(header.value, forHTTPHeaderField:
            header.key)
        }

        return urlRequest
    }
}
Request Builder

Task'ın gerçekleştirilmesi için Task Builder yazılmıştır. URLSessionDataTaskkullanılmıştır.

class TaskBuilder {
    private(set) var request: URLRequest!
    
    public func setRequest(_ request: URLRequest) {
        self.request = request
    }
    
    public func build() -> URLSessionDataTask {
        assert(request != nil)
        return URLSession.shared.dataTask(with: request) {
            (data, response, error) in
            
            guard let data = data,
                   let response = response as? HTTPURLResponse,
                   (200 ..< 300) ~= response.statusCode,
                   error == nil else {
                       return
               }
               
            if let responseObject = (try? 
            JSONSerialization.jsonObject(with: data)) as? [String: Any]
            {
               print(responseObject)
            }
        }
    }
}
Task Builder

Director: tanımlanan parametreler builder ile nesne olarak oluşturulacaktır. Örnekteki projenin Director'ü UIViewControllerdir.

class ViewController: UIViewController {
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        let requestBuilder = RequestBuilder()
        requestBuilder.setBaseUrl(RequestBaseUrls.gitHub)
        requestBuilder.setEndpoint(RequestEndpoints.searchRepositories)
        requestBuilder.setMethod(.GET)
        requestBuilder.addHeader("Content-Type", "application/json")
        requestBuilder.addParameter("q", "Builder Design Pattern")
        let request = requestBuilder.build()

        let taskBuilder = TaskBuilder()
        taskBuilder.setRequest(request)
        let task = taskBuilder.build()

        task.resume()
    }
}


//Playground'da test etmek için aşağıdaki kodu çağırabilirsiniz.

let viewController = ViewController()
PlaygroundPage.current.liveView = viewController
PlaygroundPage.current.needsIndefiniteExecution = true

//Builder Design Pattern ismindeki repolar döndürülecektir.

Bonus: Konunun daha iyi anlaşışması için bir örnek daha paylaşıyorum.

struct Theme {
    let textColor: UIColor?
    let backgroundColor: UIColor?
}

class ThemeBuilder {

    enum Style {
        case light
        case dark
    }

    func build(_ style: Style) -> Theme {
        switch style {
        case .light:
            return Theme(textColor: .black, backgroundColor: .white)
        case .dark:
            return Theme(textColor: .white, backgroundColor: .black)
        }
    }
}

let builder = ThemeBuilder()
let light = builder.build(.light)
let dark = builder.build(.dark)

Design Patterns by Tutorials, Chapter 9: Builder Pattern
The builder pattern allows the creation of complex objects step-by-step, instead of all at once, via an initializer. For example, you can use this pattern to implement a “hamburger builder”. The product could be a “hamburger” model, which has inputs such as meat selection, toppings and sauces. The d…