初学者的体系结构或为什么您不需要在剑侠中插入标志



注解:

  1. 通过添加“标志”在类中实现新功能的示例。
  2. 效果。
  3. 替代方法和结果比较。
  4. 如何避免这种情况:“建筑过度杀伤”?
  5. 是时候改变一切了。

在开始之前,请注意以下几点:

  • 这是关于软件体系结构的故事-从Bob叔叔使用的意义上讲。是的,那个。
  • 本文中的所有字符,其名称和代码都是虚构的,与现实的任何巧合都是随机的。


假设我是一个项目的普通程序员。该项目是一款游戏,其中一个英雄(又名英雄)从左到右沿着一条完美的水平线走。怪物干扰了这个美好的旅程。在用户的命令下,英雄以用剑将它们切成卷心菜而著名,并且不会吹入胡须。该项目已经有10万行,“您需要更多行的功能!” 让我们看看我们的英雄:

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

假设我的私人团队领导瓦西里(Vasily)也可以去度假。突然。谁曾想到?此外,情况按标准发展:一只猫头鹰飞来飞去,并迫切需要昨天,以便在游戏开始之前进行选择:用棍棒或剑为英雄出战。

它必须非常快。是的,当然,他本人是一名程序员,工作了超过我所能行走的时间,所以他知道任务只有五分钟。但是就这样,估计需要2个小时。您只需要添加一个复选框和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:  //     
        }
    }
    //  
}

如果您在我的决定中找到了,那么,las:我有两个新闻要告诉您:

  1. 好像不错:我们俩都能兑现。我们交付-从创造价值一词或从有趣(通过眼泪)代码一词。
  2. 坏的一面:没有瓦西里,kapets项目。

所以发生了什么事?到目前为止似乎什么都没有。但是,让我们看看接下来会发生什么(在我们竭尽所能使Vasily休假的同时)。然后是这样:质量检查部门将关注我们英雄的体重。不,这不是因为英雄必须节食,而是以下原因:

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

我忘了修正重量计算。好吧,你认为,错误,与谁不发生?!一巴掌,他妈的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
    }
}

然后Petya来了,打破了一切。好吧,事实是,我们对这个项目有这样的机会。不专心。

他只需要在公式中增加“武器等级”的概念即可计算打击强度和英雄的体重。Petya错过了四个案例之一。但是什么都没有!我纠正了所有问题:

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

不用说,什么时候(突然!)有必要添加弓形/更新公式,但又有被遗忘的情况,错误,仅此而已。

什么地方出了错?我在哪里错了?瓦西里(Vasily)休假归国时,他说了什么(除了队友)?

在这里,您无法进一步阅读该故事,例如,思考永恒的事物,关于建筑的思想。

与那些仍然决定阅读的人一起,我们继续。

因此,让我们看一下经典作品:
过早的优化是万恶之源!
而且...呃...不是这样。事情就是这样:

在OOP语言(例如Swift)中,有三种扩展类功能的主要方法:

  1. 第一种方法是“天真”。我们刚看到他。添加一个复选框。增加责任。膨胀类。
  2. 第二种方法是继承。每个人都知道重用代码的强大机制。例如,可以这样:
    • 从英雄(持有一把剑,但现在没有反映在英雄类的名称中)的杜比娜手中的弓和英雄那里继承新的英雄。然后在继承人中,覆盖更改的方法。这条路非常糟糕(请相信我)。
    • 使基类(或协议)成为英雄,并删除与特定类型的武器相关的所有功能作为继承人
      :带
      剑的英雄:英雄,带有弓箭的英雄:英雄,带有
      杜比纳的英雄:英雄。
      这样做更好,但是这些类的名称本身以某种方式使我们感到不悦,激烈,同时又感到悲伤和困惑。如果他们不看这样的人,那么我将尝试写另一篇文章,除了无聊的男性英雄之外,他们还将...
  3. 第三种方式是通过依赖注入来分离责任。它可以是通过协议关闭的依赖项,也可以是关闭(就像被签名关闭)一样的东西。最主要的是,新职责的执行应该离开主体。

在我们的情况下如何看待?例如,像这样(来自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
    }
}

您需要做什么?Ninjutsu-协议:

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

同样,Sword类也适用于:Club,Bow,Pike等。“很容易看出”(c),在新架构中,适用于每种特定类型武器的所有代码都归为相应的类别,并且不会按照英雄与其他类型的武器一起传播。这使得阅读和理解英雄以及任何特定武器变得更加容易。此外,由于所施加协议的要求,在添加新型武器或向武器添加新功能时(例如,武器可能具有价格计算方法),追踪要实现的所有方法要容易得多。

在这里,您可以看到依赖注入使Hero类对象的创建变得复杂。过去做的事情很简单:

let lastHero = Hero()

现在,它变成了一套说明,可以在需要创建英雄的任何地方重复使用。但是,Vasily照顾了这一点:

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

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

显然,瓦西里必须出汗才能散开Petya(和我)设计的堆积物

当然,查看最后一个解决方案,可能会产生以下想法:
好吧,事实证明,规范。读取和扩展很方便,但是所有这些工厂/协议/依赖项都是一堆开销吗?一种代码,它在功能方面不提供任何内容,而仅用于组织代码。“用于组织代码的代码”,mgm。真的有必要随时随地围着这个花园吗?
对第一个问题的诚实回答是:
是的,这是企业非常喜欢的功能的开销。
关于“何时?”的问题 答案部分:

剑侠的哲学或“你什么时候必须统治?”


一开始是个剑士。在旧代码的意义上,这是很正常的。只要有一个英雄和一种武器,就无需区分它们-一样,英雄没有别的了。整体代码通过其文本证实了这一事实。

剑客-听起来还不算太糟。

最初的“天真”编辑会导致什么?if-chik的添加导致了什么?

添加if-chic导致...一个突变!突变,即 可以在“人类”和“人类杜宾”之间变异的可变对象。同时,如果您在实施突变体时犯了一些错误,则会出现“人髓磷脂”状态。不要这样!根本不需要“人类杜宾”。

有必要:

  1. 人+对剑的依赖(需要剑);
  2. 人+对俱乐部的依赖(需要俱乐部)。

并非每种成瘾都是邪恶的!这种对酒精的依赖是邪恶的,礼节是好的。是的,甚至对物体的依赖都比“人类杜宾”好!

突变何时发生?当添加标志时,会发生向突变体的转换:整体代码仍然是整体的,但是当标志被更改(变异)时,同一对象的行为开始发生显着变化。

瓦西里很容易挑出两个阶段的突变:

  1. 在标志中添加标志和第一个“ if”(或“ switch”或其他分支机制)。局势正在威胁,但可以忍受:英雄倾倒了放射性废料,但他克服了。
  2. «if» , . . — . , - - .

因此,在所考虑的情况下,为了防止发生技术债务,在经过第一阶段(最坏的情况下进入第二阶段)之前构建体系结构是有意义的。

瓦西里到底做了什么治疗突变体?

从技术角度来看-使用了注入依赖封闭协议。

从哲学上讲-分担责任。

该类作品的所有功能可以相互互换(这意味着它们是彼此替代的),因此已从英雄中删除。一种新的替代功能-剑的实施,球杆的实施-外观彼此不同,并且与以前一样不同没有其他英雄代码。因此,在“天真”代码中已经出现了一些东西,它的替代行为与《英雄》无争议的部分有所不同。因此,在“天真”代码中,出现了对新业务实体的隐式描述:剑和棍棒,根据英雄所散布为了方便与新的业务实体一起使用,有必要将它们区分为具有自己名称的独立代码实体因此,存在责任分工。 PS TL; DR;



  1. 看到标志了吗?
  2. 做个男人,该死!删除它!
  3. 成瘾注射
  4. ...
  5. 利润!11

All Articles