Kali ini saya ingin berbicara sedikit tentang pola desain generatif lain dari geng arsenal Four - "Builder" . Ternyata dalam proses mendapatkan pengalaman saya (walaupun tidak terlalu luas), saya cukup sering melihat bahwa pola itu digunakan dalam kode "Java" secara umum dan dalam aplikasi "Android" pada khususnya. Dalam proyek "iOS" , apakah itu ditulis dalam "Swift" atau "Objective-C" , polanya cukup langka bagi saya. Namun demikian, dengan semua kesederhanaannya, dalam kasus yang cocok dapat berubah menjadi cukup nyaman dan, seperti yang biasa dikatakan, kuat.

Template digunakan untuk menggantikan proses inisialisasi yang kompleks dengan membangun objek yang diinginkan langkah demi langkah, dengan metode penyelesaian yang disebut di bagian akhir. Langkah-langkahnya mungkin opsional dan tidak boleh memiliki urutan panggilan yang ketat.

Contoh fondasi
Dalam kasus di mana "URL" yang diinginkan tidak diperbaiki, tetapi dibangun dari komponen (misalnya, alamat host dan jalur relatif ke sumber daya), Anda mungkin menggunakan mekanisme URLComponents
mudah URLComponents
dari perpustakaan Foundation .
URLComponents
, sebagian besar, hanyalah kelas yang menggabungkan banyak variabel yang menyimpan nilai berbagai komponen URL, serta properti url
, yang mengembalikan URL yang sesuai untuk sekumpulan komponen saat ini. Sebagai contoh:
var urlComponents = URLComponents() urlComponents.scheme = "https" urlComponents.user = "admin" urlComponents.password = "qwerty" urlComponents.host = "somehost.com" urlComponents.port = 80 urlComponents.path = "/some/path" urlComponents.queryItems = [URLQueryItem(name: "page", value: "0")] _ = urlComponents.url
Faktanya, use case di atas adalah implementasi dari pola Builder. Dalam hal ini, URLComponents
bertindak sebagai pembangun itu sendiri, menetapkan nilai ke berbagai propertinya ( scheme
, host
, dll.) Adalah inisialisasi objek masa depan dalam langkah-langkah, dan memanggil properti url
seperti metode penyelesaian.
Dalam komentar, pertempuran sengit terjadi tentang dokumen "RFC" yang menggambarkan "URL" dan "URI" , oleh karena itu, lebih tepatnya, saya mengusulkan misalnya untuk berasumsi bahwa kita hanya berbicara tentang "URL" sumber daya jarak jauh, dan tidak memperhitungkannya "URL" skema, seperti, katakanlah, "file".
Semuanya tampak baik-baik saja jika Anda jarang menggunakan kode ini dan mengetahui semua seluk-beluknya. Tetapi bagaimana jika Anda melupakan sesuatu? Misalnya, hal penting seperti alamat host? Menurut Anda apa hasil dari mengeksekusi kode berikut?
var urlComponents = URLComponents() urlComponents.scheme = "https" urlComponents.path = "/some/path" _ = urlComponents.url
Kami bekerja dengan properti, bukan metode, dan tidak ada kesalahan akan "dibuang" pasti. Properti url
"finalisasi" mengembalikan nilai opsional , jadi mungkin kita mendapatkan nil
? Tidak, kami mendapatkan objek jenis URL
dengan nilai yang tidak berarti - "https: / some / path". Oleh karena itu, terpikir oleh saya untuk berlatih menulis "pembangun" saya sendiri berdasarkan "API" yang dijelaskan di atas.
(Seharusnya ada "emoji" "sepeda", tetapi "Habr" tidak menampilkannya)
Meskipun demikian, saya menganggap URLComponents
"API" yang baik dan nyaman untuk merakit "URL" dari komponen dan, sebaliknya, "mem-parsing" komponen-komponen dari "URL" yang terkenal. Oleh karena itu, berdasarkan itu, kita sekarang menulis tipe kita sendiri yang mengumpulkan "URL" dari bagian-bagian dan memiliki (misalkan) "API" yang kita butuhkan saat ini.
Pertama, saya ingin menyingkirkan inisialisasi yang berbeda dengan memberikan nilai baru ke semua properti yang diperlukan. Sebagai gantinya, kami mengimplementasikan kemungkinan membuat turunan dari pembuat dan menetapkan nilai ke semua properti menggunakan metode yang disebut oleh rantai. Rantai diakhiri dengan metode finalisasi, hasil dari panggilan yang akan menjadi contoh URL
. Mungkin Anda telah menemukan sesuatu seperti " StringBuilder
di Jawa" dalam perjalanan hidup Anda - kami akan berjuang untuk "API" seperti itu sekarang.
Agar dapat memanggil metode-langkah di sepanjang rantai, masing-masing dari mereka harus mengembalikan turunan dari pembuat saat ini, di mana perubahan terkait akan disimpan. Untuk alasan ini, dan juga untuk menghilangkan penyalinan banyak objek dan dari menari di sekitar metode mutating
, terutama tanpa berpikir, kami akan mendeklarasikan pembangun kami sebagai kelas :
final class URLBuilder { }
Kami akan mendeklarasikan metode yang menentukan parameter "URL" di masa mendatang, dengan mempertimbangkan persyaratan di atas:
final class URLBuilder { private var scheme = "https" private var user: String? private var password: String? private var host: String? private var port: Int? private var path = "" private var queryItems: [String : String]? func with(scheme: String) -> URLBuilder { self.scheme = scheme return self } func with(user: String) -> URLBuilder { self.user = user return self } func with(password: String) -> URLBuilder { self.password = password return self } func with(host: String) -> URLBuilder { self.host = host return self } func with(port: Int) -> URLBuilder { self.port = port return self } func with(path: String) -> URLBuilder { self.path = path return self } func with(queryItems: [String : String]) -> URLBuilder { self.queryItems = queryItems return self } }
Kami menyimpan parameter yang ditentukan di properti pribadi kelas untuk digunakan di masa depan dengan metode penyelesaian.
Penghargaan lain untuk "API" di mana kita mendasarkan kelas kita adalah properti path
, yang, tidak seperti semua properti tetangga, tidak opsional, dan jika tidak ada jalur relatif, ia menyimpan string kosong sebagai nilainya.
Untuk menulis ini, pada kenyataannya, metode penyelesaian, Anda perlu memikirkan beberapa hal lagi. Pertama, "URL" memiliki beberapa bagian, yang tanpanya, seperti yang ditunjukkan di awal, tidak lagi masuk akal - ini adalah scheme
dan host
. Kami "memberikan" yang pertama dengan nilai default, karena itu, setelah melupakannya, kami masih akan menerima, kemungkinan besar, hasil yang diharapkan.
Dengan yang kedua, hal-hal sedikit lebih rumit: tidak dapat diberi nilai default. Dalam kasus ini, kami memiliki dua cara: dengan tidak adanya nilai untuk properti ini, baik mengembalikan nil
atau melempar kesalahan dan membiarkan kode klien memutuskan sendiri apa yang harus dilakukan dengannya. Opsi kedua lebih rumit, tetapi akan memungkinkan Anda untuk secara eksplisit menunjukkan kesalahan programmer tertentu. Mungkin, misalnya, kita akan menempuh jalan ini.
Poin menarik lainnya terkait dengan properti user
dan password
: mereka hanya masuk akal jika digunakan secara bersamaan. Tetapi bagaimana jika seorang programmer lupa untuk menetapkan salah satu dari dua nilai ini?
Dan, mungkin, hal terakhir yang perlu dipertimbangkan adalah bahwa sebagai hasil dari metode finalisasi kami ingin memiliki nilai properti url
dari URLComponents
, dan dalam hal ini, dalam hal ini, opsional tidak terlalu berguna. Meskipun, untuk setiap kombinasi nilai set properti nil
, kami tidak akan mendapatkannya. (Hanya instance URLComponents
kosong, baru saja dibuat, tidak akan memiliki nilai.) Untuk mengatasinya, Anda dapat menggunakan !
- operator "dipaksa membuka bungkus". Tetapi secara umum, saya tidak ingin mendorong penggunaannya, oleh karena itu, dalam contoh kami, kami secara singkat abstrak dari pengetahuan tentang seluk-beluk "Yayasan" dan menganggap situasi yang sedang dibahas sebagai kesalahan sistem, kejadian yang tidak tergantung pada kode kami.
Jadi:
extension URLBuilder { func build() throws -> URL { guard let host = host else { throw URLBuilderError.emptyHost } if user != nil { guard password != nil else { throw URLBuilderError.inconsistentCredentials } } if password != nil { guard user != nil else { throw URLBuilderError.inconsistentCredentials } } var urlComponents = URLComponents() urlComponents.scheme = scheme urlComponents.user = user urlComponents.password = password urlComponents.host = host urlComponents.port = port urlComponents.path = path urlComponents.queryItems = queryItems?.map { URLQueryItem(name: $0, value: $1) } guard let url = urlComponents.url else { throw URLBuilderError.systemError
Itu saja, mungkin! Sekarang pembuatan "URL" yang meledak dari contoh di awal mungkin terlihat seperti ini:
_ = try URLBuilder() .with(user: "admin") .with(password: "Qwerty") .with(host: "somehost.com") .with(port: 80) .with(path: "/some/path") .with(queryItems: ["page": "0"]) .build()
Tentu saja, menggunakan try
luar catch
do
- catch
atau tanpa operator ?
jika kesalahan terjadi, itu akan menyebabkan program crash. Tapi kami memberi "klien" peluang untuk menangani kesalahan yang menurutnya cocok.
Ya, dan fitur lain yang berguna dari konstruksi langkah demi langkah menggunakan template ini adalah kemampuan untuk menempatkan langkah-langkah di berbagai bagian kode. Bukan kasus yang paling sering, tapi tetap saja. Terima kasih akryukov untuk pengingatnya!
Kesimpulan
Templat ini sangat mudah dimengerti, dan segala sesuatu yang sederhana, seperti yang Anda ketahui, cerdik. Atau sebaliknya? Yah, sudahlah. Hal utama adalah bahwa saya, tanpa kedutan dari jiwa saya, saya dapat mengatakan bahwa itu (templat), telah terjadi, membantu saya dalam memecahkan masalah dalam menciptakan proses inisialisasi yang besar dan kompleks. Misalnya, proses mempersiapkan sesi komunikasi dengan server di perpustakaan, yang saya tulis untuk satu layanan hampir dua tahun lalu. Ngomong-ngomong, kodenya adalah "open source" dan, jika diinginkan, sangat mungkin untuk membiasakan diri dengannya. (Meskipun, tentu saja, sejak saat itu banyak air telah mengalir, dan programmer lain telah menerapkan kode ini.)
Posting saya yang lain tentang pola desain:
Dan ini adalah "Twitter" saya untuk memuaskan minat hipotetis dalam aktivitas publik-profesional saya.