Arsitektur untuk pemula atau mengapa Anda tidak perlu memasukkan bendera ke pedang pria



Anotasi:

  1. Contoh implementasi fungsi baru di kelas dengan menambahkan "bendera".
  2. Efek.
  3. Pendekatan alternatif dan perbandingan hasil.
  4. Bagaimana menghindari situasi: "Arsitektur berlebihan"?
  5. Momen ketika saatnya tiba untuk mengubah segalanya.

Sebelum Anda mulai, beberapa catatan:

  • Ini adalah kisah tentang arsitektur perangkat lunak - dalam arti yang digunakan Paman Bob. Ya itu.
  • Semua karakter, nama dan kode mereka dalam artikel itu fiktif, semua kebetulan dengan kenyataan adalah acak.


Misalkan saya adalah seorang programmer-programmer biasa pada suatu proyek. Proyek ini adalah permainan di mana seorang Pahlawan tunggal (alias Pahlawan) berjalan di sepanjang garis horizontal sempurna dari kiri ke kanan. Monster mengganggu perjalanan yang luar biasa ini. Atas perintah pengguna, Pahlawan terkenal memotong mereka dengan pedang ke dalam kubis dan tidak meledak ke kumis. Proyek ini sudah memiliki 100K baris, dan โ€œAnda membutuhkan lebih banyak baris fitur!โ€ Mari kita lihat Pahlawan kita:

class Hero {
    func strike() {
        //   1
    }
    //   
}

Misalkan juga bahwa tim pribadi saya memimpin, Vasily, juga bisa pergi berlibur. Mendadak. Siapa sangka? Lebih jauh, situasi berkembang sebagai standar: seekor Burung Hantu terbang masuk dan sangat dibutuhkan, tepat kemarin, untuk memungkinkan untuk memilih sebelum permulaan permainan: bermain untuk Pahlawan dengan klub atau dengan pedang.

Itu pasti sangat cepat. Dan ya, tentu saja, dia sendiri adalah seorang programmer selama lebih dari bertahun-tahun daripada yang bisa saya jalani, jadi dia tahu bahwa tugasnya adalah selama lima menit. Tapi biarlah, estimasi selama 2 jam. Anda hanya perlu menambahkan kotak centang dan if-chik.

Catatan: if-chik dalam nilai @! ^% $ # @ ^% & @! 11 $ # $% @ ^% &! @ !!! di &! ^% # $ ^%! 1 jika-apik!

Pikiranku: โ€œHa! Dua jam! Selesai! ":

class Hero {
    enum WeaponTypes {
        case sword:
        case club:
    }
	
    var weaponType: WeaponTypes?

    func strike() {
        guard let weaponType = weaponType else {
            assertionFailure()
            return	
        }
        //  : switch  Swift  if- -    !
        switch (weaponType) {
            case .sword: //     
            case .club:  //     
        }
    }
    //  
}

Jika Anda mengetahui dalam keputusan saya, maka, sayang: Saya punya dua berita untuk Anda:

  1. Seolah baik: kami berdua memberikan. Kami memberikan - dari kata memberikan nilai atau dari kata lucu (melalui air mata) kode.
  2. Dan yang buruk: tanpa Vasily, proyek kapet.

Jadi apa yang terjadi? Tampaknya sejauh ini tidak ada. Tapi mari kita lihat apa yang terjadi selanjutnya (sementara kita melakukan semua yang kita bisa untuk tetap berlibur dengan mudah). Dan kemudian akan ada ini: departemen QA akan memperhatikan bobot Pahlawan kita. Dan tidak, ini bukan karena Pahlawan harus melakukan diet, tapi inilah alasannya:

var weight: Stone {
    return meatbagWeight + pantsWeight + helmetWeight + swordWeight
}

Saya lupa memperbaiki perhitungan berat badan. Nah, Anda berpikir, salah, dengan siapa tidak terjadi ?! Tamparan, fuck-tibidoch, siap:

var weight: Stone {
    //   guard let weaponType
    let weightWithoutWeapon = meatbagWeight + pantsWeight + helmetWeight
    switch (weaponType) {
        case .sword: return weightWithoutWeapon + swordWeight
        case .club:  return weightWithoutWeapon + clubWeight
    }
}

Tapi cepat perbaiki! Pahlawan sekarang melompat, dengan mempertimbangkan berat, tetapi. Berorientasi pada hasil! Ini bukan untuk Anda melihat pusar, astronot arsitektur.

Nah, sungguh, lalu dia mengoreksi sedikit lagi. Dalam daftar mantra, saya harus melakukan ini:

var spells: [Spells] {
    //   guard let weaponType 
    // ,   let spellsWithoutWeapon: [Spells]
    switch (weaponType) {
        case .sword:
            //  let swordSpells: [Spells]
            return spellsWithoutWeapon + swordSpells
	case .club:
            //  let clubSpells: [Spells]
            return spellsWithoutWeapon + clubSpells
    }
}

Dan kemudian Petya datang dan menghancurkan segalanya. Ya, sebenarnya, kita punya proyek Juni. Leha.

Dia hanya perlu menambahkan konsep "Level Senjata" ke formula untuk menghitung kekuatan pukulan dan bobot Pahlawan. Dan Petya melewatkan satu dari empat kasus. Tapi tidak ada apa-apa! Saya mengoreksi semuanya:

func strike() {
    //guard let weaponType
    switch (weaponType) {
        case .sword: //        weaponGrade
        case .club:  //        weaponGrade
    }
}

var weight: Stone {
    // guard let weaponType
    let weightWithoutWeapon = meatbagWeight + pantsWeight + helmetWeight
    switch (weaponType) {
        case .sword: return weightWithoutWeapon + swordWeight / grade
	case .club:  return weightWithoutWeapon + pow(clubWeight, 1 / grade)
    }
}

var spells: [Spells] {
    //     , ! 
    // ,     .    ,    ,   .   !
}

Tak perlu dikatakan, ketika (tiba-tiba!) Itu perlu untuk menambahkan busur / perbarui formula, lagi ada kasus lupa, bug dan itu saja.

Apa yang salah? Di mana saya salah, dan apa (kecuali teman) yang dengan mudah dikatakan ketika dia kembali dari liburan?

Di sini Anda tidak dapat membaca cerita lebih lanjut, tetapi, misalnya, pikirkan tentang yang abadi, tentang arsitektur.

Dan dengan mereka yang masih memutuskan untuk membaca, kami melanjutkan.

Jadi, mari kita lihat klasik:
Optimalisasi prematur adalah akar dari semua kejahatan!
Dan ... eh ... bukan itu. Begini masalahnya:

Dalam bahasa OOP (seperti Swift, misalnya), ada tiga cara utama untuk memperluas kemampuan kelas:

  1. Cara pertama adalah "naif". Kami baru saja melihatnya. Menambahkan kotak centang. Menambah Tanggung Jawab. Kelas pembengkakan.
  2. Cara kedua adalah pewarisan. Semua orang tahu mekanisme yang kuat untuk menggunakan kembali kode. Seseorang dapat, misalnya:
    • Untuk mewarisi Pahlawan baru dengan Busur dan Pahlawan dari Dubina dari Pahlawan (yang memegang pedang, tapi ini tidak tercermin dalam nama kelas Pahlawan sekarang). Dan kemudian di ahli waris, timpa metode yang diubah. Jalan ini sangat buruk (percayalah padaku).
    • Jadikan kelas dasar (atau protokol) sebagai Pahlawan, dan hapus semua fitur yang terkait dengan jenis senjata tertentu sebagai ahli waris
      : Pahlawan dengan
      pedang: Pahlawan, Pahlawan dengan busur: Pahlawan, Pahlawan dengan
      Dubina: Pahlawan.
      Ini lebih baik, tetapi nama-nama kelas ini sendiri memandang kami entah bagaimana tidak senang, sengit dan pada saat yang sama sedih dan bingung. Jika mereka tidak melihat seseorang seperti itu, maka saya akan mencoba untuk menulis artikel lain, di mana selain Pahlawan maskulin yang membosankan mereka akan ...
  3. Cara ketiga adalah pemisahan tanggung jawab melalui suntikan ketergantungan. Itu bisa berupa ketergantungan yang ditutup oleh protokol atau penutupan (seolah-olah ditutup oleh tanda tangan), apa pun. Yang utama adalah bahwa pelaksanaan tanggung jawab baru harus meninggalkan kelas utama.

Bagaimana ini terlihat dalam kasus kami? Misalnya, seperti ini (keputusan dari Vasily):

class Hero {
    let weapon: Weapon //   , ..    

    init (_ weapon: Weapon) { //     
        self.weapon = weapon
    }
	
    func strike() {
        weapon.strike()
    }

    var weight: Stone {
        return meatbagWeight + pantsWeight + helmetWeight + weapon.weight
    }

    var spells: [Spells] {
        //   
        return spellsWithoutWeapon + weapon.spells
    }
}

Apa yang Anda butuhkan? Ninjutsu - protokol:

protocol Weapon {
    func strike()
    var weight: Stone {get}
    var spells: [Spells] {get}
}

Contoh implementasi protokol:

class Sword: Weapon {
    func strike() {
        // ,     Hero  switch   .sword
    }

    var weight: Stone {
        // ,     Hero  switch   .sword
    }

    var spells: [Spells] {
        // ,     Hero  switch   .sword
    }
}

Demikian pula, kelas Sword ditulis untuk: Club, Bow, Pike, dll. "Sangat mudah untuk melihat" (c) bahwa dalam arsitektur baru semua kode yang berlaku untuk setiap jenis senjata tertentu dikelompokkan dalam kelas yang sesuai, dan tidak menyebar menurut Pahlawan bersama dengan jenis senjata lainnya. Ini membuatnya lebih mudah untuk membaca dan memahami Hero dan senjata tertentu. Plus, karena persyaratan protokol yang diberlakukan, jauh lebih mudah untuk melacak semua metode yang perlu diimplementasikan ketika menambahkan senjata jenis baru atau ketika menambahkan fitur baru ke senjata (misalnya, senjata mungkin memiliki metode perhitungan harga).

Di sini Anda dapat melihat bahwa injeksi ketergantungan telah mempersulit pembuatan objek kelas Pahlawan. Apa yang biasanya dilakukan sebagai:

let lastHero = Hero()

Sekarang telah berubah menjadi seperangkat instruksi yang akan digandakan di mana pun diperlukan untuk membuat Pahlawan. Namun, Vasily menangani ini:

class HeroFactory {
    static func makeSwordsman() -> Hero { // ,  static -  
        let weapon = Sword(/*  */)
        return Hero(weapon)
    }

    static func makeClubman() -> Hero {
        let weapon = Club(/*  */)
        return Hero(weapon)
    }
}

Jelas bahwa Vasily harus berkeringat untuk menyebarkan tumpukan yang ditumpuk dirancang oleh Petya (dan saya).

Tentu saja, melihat solusi terakhir, pemikiran berikut mungkin muncul:
Ok, ternyata, norma. Memang nyaman untuk membaca dan memperluas, tetapi apakah semua pabrik / protokol / dependensi ini merupakan kumpulan biaya tambahan? Kode yang tidak memberikan apa pun dalam hal fitur, tetapi hanya ada untuk mengatur kode. "Kode untuk mengatur kode", mgm. Apakah benar-benar perlu memagari taman ini selalu dan di mana-mana?
Jawaban jujur โ€‹โ€‹atas pertanyaan pertama adalah:
Ya, ini merupakan biaya tambahan untuk fitur yang sangat disukai bisnis.
Dan untuk pertanyaan "kapan?" bagian jawaban:

Filosofi manusia pedang atau "kapan Anda harus memerintah?"


Pada awalnya adalah seorang pria pedang. Dalam arti sistem kode lama, ini cukup normal. Selama ada satu Pahlawan dan satu senjata, tidak perlu membedakan antara mereka - semua sama, tidak ada yang lain untuk pahlawan. Dan kode monolitik mengkonfirmasi fakta ini dengan teksnya.

Pendekar pedang - itu bahkan tidak terdengar begitu buruk.

Dan apa yang dilakukan oleh suntingan "naif" pertama? Apa yang menyebabkan penambahan if-chik?

Menambahkan if-chic menyebabkan ... mutan! Mutant, mis. objek bisa berubah yang dapat bermutasi antara "manusia" dan "manusia Dubin". Pada saat yang sama, jika Anda membuat sedikit kesalahan dalam implementasi mutan, maka keadaan "manusia-medullin" muncul. Jangan lakukan seperti ini! Tidak perlu untuk "manusia dubin" sama sekali.

Itu perlu:

  1. Man + kecanduan pada pedang (kebutuhan akan pedang);
  2. Man + kecanduan klub (perlu klub).

Tidak setiap kecanduan itu jahat! Kecanduan alkohol ini jahat, dan protokolnya bagus. Ya, bahkan ketergantungan pada objek lebih baik daripada "manusia dubin"!

Kapan mutasi terjadi? Transformasi menjadi mutan terjadi ketika bendera ditambahkan: kode monolitik tetap monolitik, tetapi ketika bendera diubah (bermutasi), perilaku objek yang sama mulai berubah secara signifikan.

Dengan mudah akan memilih dua tahap mutasi:

  1. Menambahkan bendera dan "jika" pertama (atau "beralih", atau mekanisme percabangan lainnya) ke bendera. Situasinya mengancam, tetapi dapat ditahan: pahlawan dituang dengan limbah radioaktif, tetapi ia mengatasinya.
  2. ยซifยป , . . โ€” . , - - .

Dengan demikian, dalam situasi yang dipertimbangkan, untuk mencegah terjadinya hutang teknis, masuk akal untuk membangun arsitektur sebelum melewati tahap pertama, dalam kasus terburuk, ke yang kedua.

Apa sebenarnya yang dilakukan Vasily untuk mengobati mutan?

Dari sudut pandang teknis - digunakan protokol tertutup ketergantungan injeksi.

Dari filosofis - tanggung jawab bersama.

Semua fitur karya kelas, yang dapat dipertukarkan satu sama lain (yang berarti mereka adalah alternatif satu sama lain), telah dihapus dari Hero tergantung. Fungsionalitas alternatif yang baru - implementasi pekerjaan pedang, implementasi pekerjaan klub - pada penampilannya menjadi berbeda satu sama lain dan berbeda dari yang lain seperti sebelumnyatidak ada kode Pahlawan alternatif . Jadi sudah ada dalam kode "naif" sesuatu yang barumuncul yang berbeda dalam perilaku alternatifnya dari bagian yang tidak diperdebatkan dari Pahlawan. Jadi dalam kode "naif" muncul deskripsi implisit entitas bisnis baru: pedang dan klub, tersebar menurut Pahlawan. Untuk membuatnya nyaman untuk beroperasi dengan entitas bisnis baru, menjadi perlu untuk membedakan mereka sebagai entitas kode yang terpisahdengan nama mereka sendiri. Jadi ada pembagian tanggung jawab.

PS TL; DR;

  1. Lihat benderanya?
  2. Jadilah pria, sial! Hapus itu!
  3. Injeksi kecanduan
  4. ...
  5. Keuntungan! 11

All Articles