Metode Pabrik dan Pabrik Abstrak di Swift dan iOS Universe

Kata "pabrik" sejauh ini merupakan salah satu yang paling sering digunakan oleh programmer ketika mendiskusikan program mereka (atau yang lain). Tetapi makna yang tertanam di dalamnya bisa sangat berbeda: itu bisa berupa kelas yang menghasilkan objek (polimorfik atau tidak); dan metode yang membuat instance dari jenis apa pun (statis atau tidak); itu terjadi, dan bahkan sembarang metode penghasil (termasuk konstruktor ).

Tentu saja, tidak semua yang menghasilkan contoh apa pun dapat disebut kata "pabrik." Selain itu, di bawah kata ini dua pola generatif yang berbeda dari gudang Geng Empat dapat disembunyikan - "metode pabrik" dan "pabrik abstrak" , rincian yang ingin saya selidiki sedikit, memberikan perhatian khusus pada pemahaman dan implementasi klasik mereka.

Dan saya terinspirasi untuk menulis esai ini oleh Joshua Kerivsky (kepala "Logika Industri" ), atau lebih tepatnya, bukunya "Refactoring to Patterns" , yang diterbitkan pada awal abad ini sebagai bagian dari serangkaian buku yang didirikan oleh Martin Fowler (penulis terkenal dari klasik pemrograman modern - buku " Refactoring " ). Jika seseorang belum pernah membaca atau bahkan mendengar yang pertama (dan saya tahu banyak dari mereka), pastikan untuk menambahkannya ke daftar bacaan Anda. Ini adalah sekuel yang layak untuk Refactoring dan buku yang bahkan lebih klasik, Teknik Desain Objective. Pola Desain . "

Buku itu, antara lain, berisi lusinan resep untuk menghilangkan berbagai "bau" dalam kode menggunakan pola desain . Termasuk tiga (setidaknya) "resep" pada topik yang dibahas.

Pabrik abstrak


Kerivsky dalam bukunya memberikan dua kasus di mana penggunaan templat ini akan bermanfaat.

Yang pertama adalah enkapsulasi pengetahuan tentang kelas-kelas tertentu yang dihubungkan oleh antarmuka umum. Dalam hal ini, hanya tipe pabrik yang akan memiliki pengetahuan ini. API publik pabrik akan terdiri dari serangkaian metode (statis atau tidak) yang mengembalikan instance dari jenis antarmuka umum dan memiliki beberapa nama "berbicara" (untuk memahami metode mana yang perlu dipanggil untuk tujuan tertentu).

Contoh kedua sangat mirip dengan yang pertama (dan, secara umum, semua skenario untuk menggunakan pola lebih atau kurang mirip satu sama lain). Ini adalah kasus ketika contoh dari satu atau lebih jenis dari kelompok yang sama dibuat di tempat yang berbeda dalam program. Dalam hal ini, pabrik sekali lagi merangkum pengetahuan tentang kode yang menciptakan mesin virtual, tetapi dengan motivasi yang sedikit berbeda. Sebagai contoh, ini terutama benar jika proses pembuatan instance dari tipe ini kompleks dan tidak terbatas pada memanggil konstruktor.

Untuk lebih dekat dengan topik pengembangan di bawah "iOS" , akan lebih mudah untuk mempraktikkan subkelas dari UIViewController . Memang, ini jelas merupakan salah satu jenis yang paling umum dalam pengembangan "iOS", hampir selalu "diwariskan" sebelum digunakan, dan subkelas tertentu seringkali bahkan tidak penting untuk kode klien.
Saya akan mencoba untuk menjaga contoh kode sedekat mungkin dengan implementasi klasik dari buku Gang of Four, tetapi dalam kehidupan nyata kode ini sering disederhanakan dengan satu atau lain cara. Dan hanya pemahaman yang cukup tentang template yang membuka pintu untuk penggunaannya yang lebih bebas.

Contoh terperinci


Misalkan kita memperdagangkan kendaraan dalam suatu aplikasi, dan pemetaan tergantung pada jenis kendaraan tertentu: kita akan menggunakan subkelas yang berbeda dari UIViewController untuk kendaraan yang berbeda. Selain itu, semua kendaraan berbeda dalam keadaan (baru dan bekas):

 enum VehicleCondition{ case new case used } final class BicycleViewController: UIViewController { private let condition: VehicleCondition init(condition: VehicleCondition) { self.condition = condition super.init(nibName: nil, bundle: nil) } required init?(coder aDecoder: NSCoder) { fatalError("BicycleViewController: init(coder:) has not been implemented.") } } final class ScooterViewController: UIViewController { private let condition: VehicleCondition init(condition: VehicleCondition) { self.condition = condition super.init(nibName: nil, bundle: nil) } required init?(coder aDecoder: NSCoder) { fatalError("ScooterViewController: init(coder:) has not been implemented.") } } 

Dengan demikian, kami memiliki kumpulan objek dari satu grup, contoh jenis yang dibuat di tempat yang sama tergantung pada beberapa kondisi (misalnya, pengguna mengklik produk dalam daftar, dan tergantung pada apakah itu skuter atau sepeda, kami buat pengontrol yang sesuai). Kontroler konstruktor memiliki beberapa parameter yang juga perlu diatur setiap kali. Apakah kedua argumen ini mendukung pembuatan "pabrik" yang sendirian akan memiliki pengetahuan tentang logika untuk membuat pengontrol yang tepat?

Tentu saja, contohnya cukup sederhana, dan dalam proyek nyata dalam kasus serupa, memperkenalkan "pabrik" akan secara eksplisit "overengineering" . Namun demikian, jika kita membayangkan bahwa kita tidak memiliki dua jenis kendaraan, dan perancang memiliki lebih dari satu parameter, maka keuntungan dari "pabrik" akan menjadi lebih jelas.

Jadi, mari deklarasikan antarmuka yang akan memainkan peran sebagai "pabrik abstrak":

 protocol VehicleViewControllerFactory { func makeBicycleViewController() -> UIViewController func makeScooterViewController() -> UIViewController } 

( "Pedoman" yang cukup singkat untuk mendesain "API" di Swift merekomendasikan pemanggilan metode pabrik yang dimulai dengan kata make.)

(Contoh dalam buku geng empat diberikan dalam "C ++" dan didasarkan pada fungsi warisan dan "virtual" . Menggunakan "Swift", tentu saja, kita lebih dekat dengan paradigma pemrograman berorientasi protokol.)

Antarmuka pabrik abstrak hanya berisi dua metode: untuk membuat pengontrol untuk menjual sepeda dan skuter. Metode mengembalikan instance bukan dari subclass spesifik, tetapi dari kelas dasar umum. Dengan demikian, ruang lingkup pengetahuan tentang jenis tertentu terbatas pada bidang di mana itu benar-benar diperlukan.

Sebagai "pabrik beton" kami akan menggunakan dua implementasi antarmuka pabrik abstrak:

 struct NewVehicleViewControllerFactory: VehicleViewControllerFactory { func makeBicycleViewController() -> UIViewController { return BicycleViewController(condition: .new) } func makeScooterViewController() -> UIViewController { return ScooterViewController(condition: .new) } } struct UsedVehicleViewControllerFactory: VehicleViewControllerFactory { func makeBicycleViewController() -> UIViewController { return BicycleViewController(condition: .used) } func makeScooterViewController() -> UIViewController { return ScooterViewController(condition: .used) } } 

Dalam hal ini, seperti dapat dilihat dari kode, pabrik tertentu bertanggung jawab untuk kendaraan dari kondisi yang berbeda (baru dan bekas).

Membuat pengontrol yang tepat sekarang akan terlihat seperti ini:

 let factory: VehicleViewControllerFactory = NewVehicleViewControllerFactory() let vc = factory.makeBicycleViewController() 

Kelas enkapsulasi pabrik


Sekarang secara singkat membahas kasus penggunaan yang ditawarkan Kerivsky dalam bukunya.

Kasus pertama terkait dengan enkapsulasi kelas tertentu . Misalnya, ambil pengontrol yang sama untuk menampilkan data tentang kendaraan:

 final class BicycleViewController: UIViewController { } final class ScooterViewController: UIViewController { } 

Misalkan kita berurusan dengan modul yang terpisah, misalnya, perpustakaan plug-in. Dalam hal ini, kelas yang dinyatakan di atas tetap (secara default) internal , dan pabrik internal sebagai "API" publik perpustakaan, yang dalam metodenya mengembalikan kelas dasar pengendali, sehingga meninggalkan pengetahuan tentang subclass spesifik di dalam perpustakaan:

 public struct VehicleViewControllerFactory { func makeBicycleViewController() -> UIViewController { return BicycleViewController() } func makeScooterViewController() -> UIViewController { return ScooterViewController() } } 

Memindahkan pengetahuan tentang membuat objek di dalam pabrik


"Kasus" kedua menjelaskan inisialisasi kompleks objek , dan Kerivsky, sebagai salah satu cara untuk menyederhanakan kode dan melindungi prinsip-prinsip enkapsulasi, menyarankan membatasi penyebaran pengetahuan tentang proses inisialisasi di luar pabrik.

Misalkan kita ingin menjual mobil secara bersamaan. Dan ini tidak diragukan lagi teknik yang lebih kompleks, dengan jumlah karakteristik yang lebih besar. Sebagai contoh, kami membatasi diri pada jenis bahan bakar yang digunakan, jenis transmisi dan ukuran pelek:

 enum Condition { case new case used } enum EngineType { case diesel case gas } struct Engine { let type: EngineType } enum TransmissionType { case automatic case manual } final class CarViewController: UIViewController { private let condition: Condition private let engine: Engine private let transmission: TransmissionType private let wheelDiameter: Int init(engine: Engine, transmission: TransmissionType, wheelDiameter: Int = 16, condition: Condition = .new) { self.engine = engine self.transmission = transmission self.wheelDiameter = wheelDiameter self.condition = condition super.init(nibName: nil, bundle: nil) } required init?(coder aDecoder: NSCoder) { fatalError("CarViewController: init(coder:) has not been implemented.") } } 

Contoh inisialisasi pengontrol yang sesuai:

 let engineType = EngineType.diesel let engine = Engine(type: engineType) let transmission = TransmissionType.automatic let wheelDiameter = 18 let vc = CarViewController(engine: engine, transmission: transmission, wheelDiameter: wheelDiameter) 

Kita dapat memikul tanggung jawab untuk semua "hal kecil" ini di "bahu" pabrik khusus:

 struct UsedCarViewControllerFactory { let engineType: EngineType let transmissionType: TransmissionType let wheelDiameter: Int func makeCarViewController() -> UIViewController { let engine = Engine(type: engineType) return CarViewController(engine: engine, transmission: transmissionType, wheelDiameter: wheelDiameter, condition: .used) } } 

Dan buat pengontrol dengan cara ini:

 let factory = UsedCarViewControllerFactory(engineType: .gas, transmissionType: .manual, wheelDiameter: 17) let vc = factory.makeCarViewController() 

Metode pabrik


Template "akar tunggal" kedua juga merangkum pengetahuan tentang tipe-tipe spesifik yang dihasilkan, tetapi tidak dengan menyembunyikan pengetahuan ini dalam kelas khusus, tetapi dengan polimorfisme. Kerivsky dalam bukunya memberi contoh di Jawa dan menyarankan menggunakan kelas-kelas abstrak , tetapi penghuni alam semesta Swift tidak terbiasa dengan konsep ini. Kami memiliki atmosfer kami sendiri di sini ... dan protokol.
Buku "Gangs of Four" melaporkan bahwa templat ini juga dikenal sebagai "konstruktor virtual", dan ini tidak sia-sia. Dalam "C ++", virtual adalah fungsi yang didefinisikan ulang dalam kelas turunan. Bahasa tidak memberi kesempatan pada perancang untuk mendeklarasikan virtual, dan ada kemungkinan bahwa itu merupakan upaya untuk meniru perilaku yang diinginkan yang mengarah pada penemuan pola ini.

Pembuatan objek polimorfik


Sebagai contoh klasik dari kegunaan templat, kami mempertimbangkan kasus ketika dalam hierarki jenis yang berbeda memiliki implementasi identik dari satu metode kecuali untuk objek yang dibuat dan digunakan dalam metode ini . Sebagai solusi, diusulkan untuk membuat objek ini dalam metode terpisah dan mengimplementasikannya secara terpisah, dan untuk meningkatkan metode umum yang lebih tinggi dalam hierarki. Dengan demikian, berbagai jenis akan menggunakan implementasi umum dari metode ini, dan objek yang diperlukan untuk metode ini akan dibuat secara polimorfik.

Misalnya, mari kembali ke pengontrol kami untuk menampilkan kendaraan:

 final class BicycleViewController: UIViewController { } final class ScooterViewController: UIViewController { } 

Dan misalkan entitas tertentu digunakan untuk menampilkannya, misalnya, koordinator , yang mewakili pengontrol ini secara modalnya dari pengontrol lain:

 protocol Coordinator { var presentingViewController: UIViewController? { get set } func start() } 

Metode start() selalu digunakan dengan cara yang sama, kecuali bahwa ia menciptakan pengontrol yang berbeda:

 final class BicycleCoordinator: Coordinator { weak var presentingViewController: UIViewController? func start() { let vc = BicycleViewController() presentingViewController?.present(vc, animated: true) } } final class ScooterCoordinator: Coordinator { weak var presentingViewController: UIViewController? func start() { let vc = ScooterViewController() presentingViewController?.present(vc, animated: true) } } 

Solusi yang diusulkan adalah membuat penciptaan objek yang digunakan dalam metode terpisah:

 protocol Coordinator { var presentingViewController: UIViewController? { get set } func start() func makeViewController() -> UIViewController } 

Dan metode utama adalah menyediakan implementasi dasar:

 extension Coordinator { func start() { let vc = makeViewController() presentingViewController?.present(vc, animated: true) } } 

Jenis khusus dalam hal ini akan berbentuk:

 final class BicycleCoordinator: Coordinator { weak var presentingViewController: UIViewController? func makeViewController() -> UIViewController { return BicycleViewController() } } final class ScooterCoordinator: Coordinator { weak var presentingViewController: UIViewController? func makeViewController() -> UIViewController { return ScooterViewController() } } 

Kesimpulan


Saya mencoba untuk membahas topik sederhana ini dengan menggabungkan tiga pendekatan:

  • deklarasi klasik tentang keberadaan resepsi, terinspirasi oleh buku "Geng Empat";
  • motivasi untuk digunakan, secara terbuka terinspirasi oleh buku Kerivsky;
  • aplikasi terapan sebagai contoh industri pemrograman yang dekat dengan saya.

Pada saat yang sama, saya berusaha sedekat mungkin dengan struktur buku teks templat, sejauh mungkin, tanpa menghancurkan prinsip-prinsip pendekatan modern untuk pengembangan untuk sistem iOS dan menggunakan kemampuan bahasa Swift (bukan C ++ dan Java yang lebih umum).

Ternyata, agak sulit untuk menemukan materi terperinci tentang topik yang berisi contoh yang diterapkan. Sebagian besar artikel dan manual yang ada hanya berisi ulasan dangkal dan contoh singkat, yang sudah cukup terpotong dibandingkan dengan versi implementasi buku teks.

Saya berharap bahwa setidaknya sebagian saya dapat mencapai tujuan saya, dan pembaca - setidaknya sebagian tertarik atau setidaknya penasaran untuk belajar atau menyegarkan pengetahuan saya tentang topik ini.

Materi saya yang lain tentang pola desain:


Dan ini adalah tautan ke "Twitter" saya, tempat saya menerbitkan tautan ke esai saya dan sedikit lebih dari itu.

Source: https://habr.com/ru/post/id451324/


All Articles