Photo by Med Badr Chemmaoui / Unsplash

Builder Design Pattern

Swift Jun 25, 2022

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…

Tags

Furkan Ozoglu

iOS Developer & Geomatics Engineer