العمارة للمبتدئين أو لماذا لا تحتاج إلى إدخال علم في رجل السيف



حاشية. ملاحظة:

  1. مثال على تطبيق وظائف جديدة في الفصل بإضافة "علم".
  2. تأثيرات.
  3. نهج بديل ومقارنة النتائج.
  4. كيف تتجنب الموقف: "المبالغة المعمارية"؟
  5. اللحظة التي يأتي فيها الوقت لتغيير كل شيء.

قبل أن تبدأ ، بضع ملاحظات:

  • هذه قصة عن هندسة البرمجيات - بمعنى أن العم بوب يستخدم. نعم هذا واحد.
  • جميع الشخصيات وأسمائهم ورموزهم في المقالة وهمية ، وأي صدف مع الواقع عشوائي.


افترض أنني مبرمج-مبرمج عادي في مشروع. المشروع عبارة عن لعبة يسير فيها بطل واحد (يعرف أيضًا باسم Hero) على طول خط أفقي مثالي من اليسار إلى اليمين. تتدخل الوحوش في هذه الرحلة الرائعة. بناء على أمر المستخدم ، يقطع البطل الشهير سيفًا في الملفوف ولا ينفجر في الشارب. يحتوي المشروع بالفعل على 100 ألف خط ، و "أنت بحاجة إلى المزيد من خطوط الميزات!" دعونا نلقي نظرة على بطلنا:

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

افترض أيضًا أن قائد فريقي الشخصي ، فاسيلي ، يمكنه أيضًا الذهاب في إجازة. فجأة. من كان يظن؟ علاوة على ذلك ، يتطور الوضع كمعيار: البومة تطير وتحتاج بشكل عاجل ، أمس ، لتمكينها من الاختيار قبل بداية اللعبة: اللعب للبطل مع النادي أو بالسيف.

يجب أن تكون سريعة للغاية. ونعم ، بالطبع ، هو نفسه كان مبرمجًا لأكثر من سنوات لا أستطيع المشي ، لذا فهو يعلم أن المهمة لمدة خمس دقائق. لكن فليكن ، تقدير لمدة ساعتين. تحتاج فقط إلى إضافة مربع اختيار و if-chik.

ملاحظة: if-chik في القيمة @! ^٪ $ # @ ^٪ & @! 11 $ لها # $٪ @ ^٪ &! @ !!! في &! ^٪ # $ ^٪! 1 if-chic!

أفكاري: "ها! ساعتين! منجز! ":

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:  //     
        }
    }
    //  
}

إذا اكتشفت في قراري ، للأسف: لدي خبران لك:

  1. كما لو كانت جيدة: كلانا يقدم. نسلم - من كلمة تسليم القيمة أو من كلمة مضحكة (من خلال الدموع).
  2. والشر: بدون فاسيلي مشروع الكابيتات.

اذا ماذا حصل؟ لن يبدو شيء حتى الآن. ولكن دعونا نرى ما سيحدث بعد ذلك (بينما نفعل كل ما في وسعنا لإبقاء فاسيلي في إجازة). وبعد ذلك سيكون هناك ما يلي: سيهتم قسم ضمان الجودة بوزن بطلنا. لا ، ليس هذا لأن البطل يجب أن يتبع نظامًا غذائيًا ، ولكن إليك السبب:

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

لقد نسيت إصلاح حساب الوزن. حسنًا ، أنت تظن ، خطأ ، مع من لا يحدث ؟! Slap-bloop ، اللعنة- tibidoch ، جاهزة:

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

ولكن إصلاحه بسرعة! البطل يقفز الآن ، مع مراعاة الوزن ، ولكن. عمل موجه نحو النتيجة! هذا ليس لك أن تنظر إلى السرة ، رواد الفضاء المعماريين.

حسنًا ، حقًا ، قام بتصحيح أكثر قليلاً. في القائمة الإملائية ، كان علي القيام بذلك:

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
    }
}

ثم جاء بيتيا وكسر كل شيء. حسنًا ، الحقيقة هي أنه لدينا مثل هذا المشروع في المشروع. غافل.

كان يحتاج فقط إلى إضافة مفهوم "مستوى السلاح" إلى صيغة حساب قوة الضربة ووزن البطل. وغاب بيتيا عن واحدة من أربع حالات. لكن لا شيء! لقد قمت بتصحيح كل شيء:

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] {
    //     , ! 
    // ,     .    ,    ,   .   !
}

وغني عن القول ، عندما (فجأة!) كان من الضروري إضافة صيغ القوس / التحديث ، مرة أخرى كانت هناك حالات منسية ، أخطاء ، وكان هذا كل شيء.

ماذا حصل؟ أين كنت مخطئا ، وماذا قال (باستثناء الاصحاب) فاسيلي عندما عاد من اجازة؟

هنا لا يمكنك قراءة القصة أكثر ، ولكن ، على سبيل المثال ، فكر في الأبدية ، حول الهندسة المعمارية.

ومع أولئك الذين ما زالوا يقررون القراءة ، نستمر.

لذا ، دعونا نلقي نظرة على الكلاسيكيات:
التحسين المبكر هو أصل كل الشر!
و ... اه ... ليس هذا. إليك الشيء:

في لغات OOP (مثل Swift ، على سبيل المثال) ، هناك ثلاث طرق رئيسية لتوسيع قدرات الفصل:

  1. الطريقة الأولى هي "ساذجة". لقد رأيناه للتو. إضافة مربع اختيار. مضيفا المسؤولية. فئة التورم.
  2. الطريقة الثانية هي الميراث. يعلم الجميع آلية قوية لإعادة استخدام التعليمات البرمجية. يمكن للمرء ، على سبيل المثال:
    • ترث البطل الجديد مع القوس والبطل من Dubina من البطل (الذي يحمل سيفًا ، لكن هذا لا ينعكس في اسم فئة البطل الآن). ثم في الورثة ، تجاوز الأساليب المتغيرة. هذا المسار سيء للغاية (ثق بي فقط).
    • اجعل الفئة الأساسية (أو البروتوكول) بطلًا ، وأزل جميع الميزات المرتبطة بنوع معين من الأسلحة كخلفاء
      : Hero with
      sword: Hero ، Hero with a bow: Hero ، Hero with
      Dubina: Hero.
      هذا أفضل ، لكن أسماء هذه الفئات نفسها تبدو لنا بطريقة ما مستاءة ، بشراسة وفي نفس الوقت حزينة ومحيرة. إذا لم ينظروا إلى شخص مثل هذا ، فسأحاول كتابة مقال آخر ، حيث بالإضافة إلى البطل الممل الممل سيكون ...
  3. الطريقة الثالثة هي فصل المسؤولية عن طريق حقن التبعية. يمكن أن يكون تبعية مغلقة بواسطة بروتوكول أو إغلاق (كما لو تم إغلاقه بتوقيع) ، أيا كان. الشيء الرئيسي هو أن تنفيذ المسؤوليات الجديدة يجب أن يترك الطبقة الرئيسية.

كيف يمكن أن يبدو هذا في حالتنا؟ على سبيل المثال ، مثل هذا (قرار من فاسيلي):

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
    }
}

ماذا تريد أن تكون كذلك؟ نينجوتسو - البروتوكول:

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

مثال على تنفيذ البروتوكول:

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

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

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

وبالمثل ، يتم كتابة دروس السيف لـ: Club ، Bow ، Pike ، إلخ. "من السهل أن ترى" (ج) أنه في الهندسة المعمارية الجديدة يتم تجميع جميع التعليمات البرمجية التي تتعلق بكل نوع معين من الأسلحة في الفئة المقابلة ، ولا يتم نشرها وفقًا للبطل مع أنواع أخرى من الأسلحة. هذا يجعل من السهل قراءة وفهم البطل وأي سلاح معين. بالإضافة إلى ذلك ، نظرًا لمتطلبات البروتوكول المفروض ، من الأسهل بكثير تتبع جميع الطرق التي يجب تنفيذها عند إضافة نوع جديد من السلاح أو عند إضافة ميزة جديدة إلى سلاح (على سبيل المثال ، قد يكون للسلاح طريقة حساب السعر).

هنا يمكنك أن ترى أن حقن التبعية قد عقدت إنشاء كائنات فئة البطل. ما كان يتم القيام به ببساطة:

let lastHero = Hero()

الآن تحولت إلى مجموعة من التعليمات التي سيتم تكرارها حيثما كان ذلك ضروريًا لإنشاء بطل. ومع ذلك ، اهتم فاسيلي بهذا:

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

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

من الواضح أن فاسيلي كان عليه أن يتعرق من أجل تشتيت الكومة التي تراكمت من تصميم بيتيا (وأنا).

بالطبع ، بالنظر إلى الحل الأخير ، قد تنشأ الفكرة التالية:
حسنا ، اتضح ، المعايير. من الملائم القراءة والتوسيع ، ولكن هل كل هذه المصانع / البروتوكولات / التبعيات عبارة عن مجموعة من النفقات العامة؟ رمز لا يعطي أي شيء من حيث الميزات ، ولكنه موجود فقط لتنظيم الرمز. "كود لتنظيم الكود" ، mgm. هل من الضروري حقًا تسييج هذه الحديقة دائمًا وفي كل مكان؟
الجواب الصادق على السؤال الأول هو:
نعم ، هذا عبء للميزات التي يحبها العمل كثيرًا.
وعلى السؤال "متى؟" قسم الإجابات:

فلسفة السيف أم "متى كان عليك أن تحكم؟"


في البداية كان رجل سيف. في نظام الإحساس للرمز القديم ، كان هذا طبيعيًا تمامًا. طالما كان هناك بطل واحد وسلاح واحد ، لم تكن هناك حاجة للتمييز بينهما - على الرغم من ذلك ، لم يكن هناك شيء آخر للبطل. وأكد الرمز المتآلف هذه الحقيقة بنصها.

Swordman - حتى لا يبدو سيئا للغاية.

وما الذي أدت إليه التعديلات "الساذجة" الأولى؟ ما الذي أدت إليه إضافة if-chik؟

أدت إضافة لمسة أنيقة إلى ... متحولة! متحولة ، أي كائن قابل للتغيير يمكن أن يتحول بين "الإنسان" و "الإنسان دوبين". في الوقت نفسه ، إذا ارتكبت خطأً بسيطًا في تنفيذ المتحولة ، فإن حالة "الإنسان-ميدولين" تنشأ. لا تقم بمثل هذا! ليست هناك حاجة إلى "الإنسان Dubin" على الإطلاق.

انه ضروري:

  1. الرجل + إدمان السيف (الحاجة إلى السيف) ؛
  2. رجل + إدمان على ناد (بحاجة إلى ناد).

ليس كل إدمان شرير! هذا الإدمان على الكحول شرير ، والبروتوكول جيد. نعم ، حتى الاعتماد على الشيء أفضل من "الإنسان دوبين"!

متى حدثت الطفرة؟ حدث التحول إلى متحولة عند إضافة العلم: ظل الرمز المتآلف متجانسة ، ولكن عندما تم تغيير العلم (متحورًا) ، بدأ سلوك نفس الكائن يتغير بشكل كبير.

فاسيلي يفرز مرحلتين من الطفرة:

  1. إضافة علامة وأول "إذا" (أو "مفتاح تبديل" ، أو آلية أخرى متفرعة) إلى العلم. الوضع مهدد ، لكن محتمل: البطل يسكب بالنفايات المشعة ، لكنه يتغلب عليه.
  2. «if» , . . — . , - - .

وبالتالي ، في الحالة المدروسة ، من أجل منع حدوث الديون التقنية ، فمن المنطقي بناء الهندسة المعمارية قبل المرور بالمرحلة الأولى ، في أسوأ الحالات ، إلى المرحلة الثانية.

ماذا فعل فاسيلي بالضبط لعلاج متحولة؟

من وجهة نظر فنية - أغلق بروتوكول استخدام التبعية للحقن.

من المسؤولية الفلسفية - المشتركة.

تمت إزالة جميع ميزات عمل الفصل ، والتي يمكن تبادلها مع بعضها البعض (مما يعني أنها بديلة لبعضها البعض) ، من البطل اعتمادًا. وظيفة بديلة جديدة - تنفيذ عمل السيف ، تنفيذ عمل النادي - عند ظهوره أصبح مختلفًا عن الآخر ويختلف عن الباقي كما كان من قبللا يوجد رمز بطل بديل . لذلك بالفعل في الكود "الساذج" ظهر شيء جديد كان مختلفًا في سلوكه البديل عن الجزء غير المتنازع عليه من البطل. لذا في الكود "الساذج" ظهر وصف ضمني للكيانات التجارية الجديدة: سيف ونادي، منتشر وفقًا للبطل. من أجل جعلها ملائمة للعمل مع كيانات تجارية جديدة، أصبح من الضروري تمييزها ككيانات تعليمات برمجية منفصلة بأسمائها الخاصة. لذلك كان هناك تقسيم للمسؤولية.

PS TL ؛ DR ؛

  1. ترى العلم؟
  2. كن رجلاً ، اللعنة! محو!
  3. حقن الإدمان
  4. ...
  5. الربح! 11

All Articles