Azimjon Abdurasulov

04. Builder va Prototype: Telescoping Constructor Fojiasi

🏛️ iOS Design Patterns — Mukammal Enterprise & Architecture Qo‘llanma

04. Creational: Builder va Prototype Pattern

"Agar sizning konstruktoringizda 12 ta parametr bo'lib, ulardan 8 tasi ixtiyoriy (optional) bo'lsa — siz 'Telescoping Constructor' balosiga duchor bo'lgansiz. Builder bu boshog'riqni chiroyli va o'qishli oqimga aylantiradi."


1. Builder Pattern Nima va U Qaysi Muammoni Hal Qiladi?

Builder — bu ko'p bosqichli va ko'p parametrli murakkab obyektlarni bosqichma-bosqich, zanjirband uslubda (Method Chaining / Fluent API) yaratish imkonini beruvchi pattern.

"Telescoping Constructor" Fojiasi:

// ❌ QO'RFINCHLI ANTI-PATTERN:
let request = URLRequest(
    url: url,
    cachePolicy: .useProtocolCachePolicy,
    timeoutInterval: 60,
    mainDocumentURL: nil,
    networkServiceType: .default,
    allowsCellularAccess: true,
    allowsExpensiveNetworkAccess: true,
    allowsConstrainedNetworkAccess: true,
    httpMethod: "POST",
    allHTTPHeaderFields: ["Authorization": "Bearer ..."],
    httpBody: bodyData
)

Bu kodni o'qish ham, yangi parametr qo'shish ham o'ta noqulay.

flowchart LR
    Start["Builder yaratish:\nURLRequestBuilder()"] --> SetURL[".setURL('/api/orders')"]
    SetURL --> SetMethod[".setMethod(.post)"]
    SetMethod --> SetAuth[".addBearerToken(token)"]
    SetAuth --> SetBody[".setJSONBody(payload)"]
    SetBody --> Build[".build() -> URLRequest"]

    style Start fill:#131720,stroke:#7da2ce,stroke-width:1px
    style Build fill:#131720,stroke:#bef264,stroke-width:2px

2. Real-World Swift 6 Kodi: Enterprise URLRequestBuilder

import Foundation

public enum HTTPMethod: String {
    case get = "GET"
    case post = "POST"
    case put = "PUT"
    case delete = "DELETE"
}

public final class URLRequestBuilder {
    private var baseURL: URL
    private var path: String = ""
    private var method: HTTPMethod = .get
    private var headers: [String: String] = [:]
    private var queryParams: [String: String] = [:]
    private var body: Data?
    private var timeout: TimeInterval = 30.0
    
    public init(baseURL: URL) {
        self.baseURL = baseURL
    }
    
    @discardableResult
    public func setPath(_ path: String) -> Self {
        self.path = path
        return self
    }
    
    @discardableResult
    public func setMethod(_ method: HTTPMethod) -> Self {
        self.method = method
        return self
    }
    
    @discardableResult
    public func addHeader(key: String, value: String) -> Self {
        self.headers[key] = value
        return self
    }
    
    @discardableResult
    public func addBearerToken(_ token: String) -> Self {
        self.headers["Authorization"] = "Bearer \(token)"
        return self
    }
    
    @discardableResult
    public func addQueryParam(key: String, value: String) -> Self {
        self.queryParams[key] = value
        return self
    }
    
    @discardableResult
    public func setJSONBody<T: Encodable>(_ object: T) throws -> Self {
        self.body = try JSONEncoder().encode(object)
        self.headers["Content-Type"] = "application/json"
        return self
    }
    
    public func build() throws -> URLRequest {
        var fullURL = baseURL.appendingPathComponent(path)
        
        if !queryParams.isEmpty {
            var components = URLComponents(url: fullURL, resolvingAgainstBaseURL: false)
            components?.queryItems = queryParams.map { URLQueryItem(name: $0.key, value: $0.value) }
            if let configuredURL = components?.url {
                fullURL = configuredURL
            }
        }
        
        var request = URLRequest(url: fullURL, timeoutInterval: timeout)
        request.httpMethod = method.rawValue
        request.httpBody = body
        for (key, value) in headers {
            request.addValue(value, forHTTPHeaderField: key)
        }
        return request
    }
}

Ishlatilishi (Toza va O'qishli):

let request = try URLRequestBuilder(baseURL: URL(string: "https://api.azimjondev.uz")!)
    .setPath("/v1/orders/create")
    .setMethod(.post)
    .addBearerToken("jwt_secret_token_123")
    .addQueryParam(key: "currency", value: "UZS")
    .setJSONBody(OrderPayload(itemsCount: 3))
    .build()

3. Swift'dagi @resultBuilder: SwiftUI Qanday Qilib Builderni Tilga Kiritdi?

SwiftUI dagi har bir VStack, HStack yoki List aslida Swift'ning rasmiy Result Builder imkoniyatiga asoslangan:

@resultBuilder
public struct ViewBuilder { ... }

Siz quyidagicha yozganingizda:

VStack {
    Text("Sarlavha")
    Button("Bosish") {}
}

Swift kompilyatori orqa fonda ViewBuilder.buildBlock(Text(...), Button(...)) chaqiruvini bajaradi. Bu Builder patternining zamonaviy deklarativ shaklidir!


4. Prototype Pattern: Nusxa Olish va Copy-on-Write (CoW)

Prototype — bu mavjud obyektning aniq nusxasini (klonini) yaratishga imkon beruvchi pattern.

Shallow Copy vs Deep Copy Fojiasi:

flowchart TD
    subgraph Shallow["❌ Shallow Copy (Sayoz Nusxa)"]
        ObjA["Obyekt A"] --> Ref["Bir Xil Xotira Manzili (Shared Reference)"]
        ObjB["Obyekt B"] --> Ref
        ObjB -.->|"B o'zgarsa"| Corrupt["💥 A ham kutilmaganda o'zgarib ketadi!"]
    end

    subgraph Deep["✅ Deep Copy (Chuqur Nusxa)"]
        Obj1["Obyekt A"] --> Ref1["RAM Manzil #1"]
        Obj2["Obyekt B (Klon)"] --> Ref2["Yangi RAM Manzil #2"]
        Obj2 -.->|"B o'zgarsa"| Safe["A xavfsiz va o'zgarmas qoladi!"]
    end

    style Shallow fill:#251014,stroke:#f87171,stroke-width:1px
    style Deep fill:#0d1f14,stroke:#4ade80,stroke-width:1px

Swift'da Prototype:

  • Structlar (Value Types): Swift structlari tabiiy ravishda Prototype patternini o'z ichiga oladi:
struct UserProfile { var name: String }
var user1 = UserProfile(name: "Ali")
var user2 = user1 // Avtomatik ravishda to'liq mustaqil klon! (Copy-on-Write)
user2.name = "Vali" // user1 "Ali" bo'lib qoladi
  • Classlar (Reference Types): Classlar bilan ishlaganda esa NSCopying protokolini amalga oshirish kerak bo'ladi:
final class ComplexReport: NSCopying {
    var title: String
    var metrics: [String: Int]
    
    init(title: String, metrics: [String: Int]) {
        self.title = title
        self.metrics = metrics
    }
    
    // Deep Copy implementatsiyasi
    func copy(with zone: NSZone? = nil) -> Any {
        return ComplexReport(title: self.title, metrics: self.metrics)
    }
}

5. Interaktiv Savol-Javob (Self-Check Quiz)

<details> <summary><b>1-Savol: Nega Swift'da Builder metodlarida '@discardableResult' atributi yoziladi?</b> (Yechimni ko'rish uchun bosing)</summary>

Tahlil va Yechim: Metod return self qaytargani sababli, agar chaqiruvchi ushbu qaytgan qiymatni alohida o'zgaruvchiga o'zlashtirmasa (masalan zanjir o'rtasida), Swift kompilyatori Result of call to '...' is unused degan ogohlantirish (warning) bermasligi uchun @discardableResult qo'yiladi. </details>

<details> <summary><b>2-Savol: Qachon oddiy Struct copy o'rniga Class uchun Prototype patternini maxsus yozish talab etiladi?</b> (Yechimni ko'rish uchun bosing)</summary>

Tahlil va Yechim: Agar obyekt juda og'ir bo'lsa (masalan, grafika chizish loyihalarida millionlab poligonlardan iborat 3D model yoki rasm tahrirlagichdagi ko'p qatlamli Layer) va uni noldan qayta hisoblab yaratish juda ko'p CPU talab qilsa. Bunday holatda xotiradagi tayyor obyektni klonlab, faqat o'zgargan bir nechta xususiyatini almashtirish 100x tezroq bo'ladi. </details>


6. Amaliy Topshiriq (Senior Challenge)

PushNotificationPayloadBuilder sinfini yarating:

  • .setTitle(_:), .setBody(_:), .setCategory(_:), .setBadgeCount(_:), .addCustomData(key:value:) metodlariga ega bo'lsin.
  • .build() metodi kamida title va body kiritilganini tekshirsin, agar kiritilmagan bo'lsa NotificationBuildError.missingRequiredFields xatosini tashlasin (throw qilsin)!

Markdown manba

iOS Lessons