隐藏设计的外观:寻求相关可变字段的更好替代方案

更好的方法是,对它们操作一堆可变的布尔字段和方法,或者对它们之间的各个状态和转换进行显式表达? 让我们通过模拟多阶段感染的进程来研究一个示例。

1.隐藏在原始可变字段和方法中的设计

下面的类具有许多相互关联的可变(公共)字段,我应该使用其状态之间的转换方法来完成这些类,这让我感到非常不安(var是可变字段,val是不可变的):

class Person (val id: Int) {
  
  var infected = false
  var sick = false
  var immune = false
  var dead = false

  // to complete with simulation logic
  // [infection progression]
}

字段保留感染进度的状态。 他们彼此依赖-f.ex. 当生病时 ,这个人也必须被感染 ;当他死时 ,她一定不免疫

感染的进展是:健康->受感染且具有传染性,有40%的机会(但尚未明显患病)->在第6天明显有病->在第14天死亡,有25%的机会死亡->如果没有死亡,在第16天就开始免疫-并非明显患病,但仍具有传染性->在第18天就变得健康。

我将状态保留在许多字段中的问题在于,这些规则没有明确表达,并且开放了一些缺陷,例如将病态设置为true,而忘记设置也被感染 。 这也很难理解。 您可以通过研究更改字段的方法来学习规则,但是这需要大量的精力,要区分偶然的实现细节和有意的设计并不容易,并且此代码无法防止上述错误。

优点:

  • 熟悉的
  • 易于设计

缺点:

  • 代码中尚不清楚感染进展的规则(嗯,即使实际上已经显示了方法也不是这样),即,它传达的域概念和规则不佳
  • 多个字段的值必须同步,不能确保导致缺陷
  • 导致意大利面条代码

2.状态和过渡的显式和预防缺陷的表达

我更希望将规则作为设计的中心部分,而不是将其建立在我们如何保持状态的实现细节(即一组要管理的布尔值)的基础上。 换句话说,我想展示隐藏在其中的设计。 我想对状态有一个明确的概念-健康,正在孵化,生病,死亡,免疫-在它们之间进行清晰的转换,我希望这些状态明确地携带有关该人是否具有传染性以及是否可见的辅助信息。或不。

明确表示此设计的一种方法是:

def randomBelow = ... 

/** When should the health state change and what to */
class HealthChange(val time: Int, val newHealth: Health) {
  def immediate = (time == 0)
}
/** The root of the health hierarchy; the health (i.e. infection) evolves in stages */
sealed abstract class Health(val infectious: Boolean, val visiblyInfectious: Boolean) {
  def evolve(): Option[HealthChange] // returns None if no further change possibly/expected
}
/** In some stages the person is infected but it isn't visible*/
sealed abstract class CovertlyInfected extends Health(infectious = true, visiblyInfectious = false) {}
/** In other stages the person is infected and it is clearly visible*/
sealed abstract class VisiblyInfected extends Health(infectious = true, visiblyInfectious = true) {}

case object HEALTHY extends Health(infectious = false, visiblyInfectious = false) {
  def evolve() = // 40% chance of getting infected, triggered on meeting an infected person
    if (randomBelow(101) <= 40/*%*/)
      Some(new HealthChange(0, INCUBATIOUS))
    else
      None // no change, stays healthy
}
case object INCUBATIOUS extends CovertlyInfected {
  def evolve() = // After 6 days if infection without visible effects becomes sick
    Some(new HealthChange(6, SICK))
}
case object SICK extends VisiblyInfected {
  def evolve() = // die with 25% on day 14 or stays sick for 2 more days, then immune
    if (randomBelow(101) <= 25/*%*/)
        Some(new HealthChange(14 - 6, DEAD))
      else
        Some(new HealthChange(16 - 6, IMMUNE))
}
case object DEAD extends VisiblyInfected {
  def evolve() = None // Dead people stay dead
}
/** The symptoms have disappeared but the person can still infect others for 2 more days */
case object IMMUNE extends CovertlyInfected {
  def evolve() =
    Some(new HealthChange(18 - 16, HEALTHY))
}

class Person (val id: Int, var health: Health = HEALTHY) {
  def tryInfect() { // upon meeting an infected person
    if (health != HEALTHY) throw new IllegalStateException("Only healthy can be infected")
    health.evolve().foreach(
      healthChng => setHealth(healthChng.newHealth) // infection happens immediately
    )
  }
  /** Set the new health stage and schedule the next health change, if any */
  def setHealth(h: Health) {
    this.health = h
    h.evolve().foreach(hNext => {
      if (hNext.immediate)
        setHealth(hNext.newHealth)
      else
        afterDelay(hNext.time) {setHealth(hNext.newHealth)}
    })
  }
}

优点

  • 现在,感染的规则和阶段是代码的明确且一流的成员。 实践中的领域驱动设计
  • 状态之间的转换是清晰,明确的,并且我们无法使人进入无效状态(前提是我们正确定义了转换)
  • 我们不再需要同步多个变量的状态

缺点

  • 该代码可能比一堆布尔字段和在它们的状态之间转换的方法更长
  • 这似乎很复杂,因为我们突然有了一个类层次结构,而不是一个类和几个方法。 但是它实际上防止了原始意大利面条代码的复杂性,因此,尽管不容易理解,但按照R. Hickey来说却是“简单”的

结论

我经常遇到这样的代码,特别是在较旧的旧版应用程序中,这些旧版应用程序是根据不断变化的业务需求而开发的,主要侧重于“功能”,而没有相应地更新/重构底层设计。 坦白说,这是地狱。 在许多相互依赖的字段上工作的低级代码(在一个类中也可能有许多不相关的字段或仅在某些用例中依赖于这些字段的字段)是隐藏和增加缺陷的天堂。由于设计(规则,概念和意图)尚未明确(至今),因此很难理解。 难以理解意味着难以改变。

因此,定期检查代码并显示隐藏在其中的设计,隐藏低级实现细节以及使关键概念,状态,转换,规则等成为代码库的一流成员非常重要,以便阅读代码感觉是交流和学习,而不是考古学。

更新

更新1

Jerrinot的Enum和基于Java的代码(为了便于阅读而包括在此处):

public enum Health {
    HEALTHY(false, false, false, false),
    INCUBATIOUS(true, false, false, false),
    SICK(true...),
    DEAD(....);

    private boolean infected;
    private boolean sick;
    private boolean immune;
    private boolean dead;

    private Health(boolean infected, boolean sick, boolean immune, boolean dead) {
        this.infected  = infected;
        this.sick = sick;
        this.immune = immune;
        this.dead = dead;
    }

    public boolean isInfected() {
        return infected;
    }

    public boolean isSick() {
        return sick;
    }

    public Health evolve() {
        switch (this) {
           case HEALTHY:
               return computeProbabability..() ? INCUBATIOUS : HEALTHY;
[....]
        }
    }
}
更新2

Jerrinot在Scala中的版本:

sealed class Health(val infectious: Boolean, val visiblyInfectious: Boolean) {

  def evolve(implicit randomGenerator: RandomIntGenerator, config: MySimConfig): Option[HealthChange] =
    this match {
      case Healthy =>
        if (randomGenerator.randomBelow(101) <= config.transmissibilityPct)
          Some(new HealthChange(0, Incubatious))
        else None
      case Incubatious => 
        Some(new HealthChange(6, Sick))
      case Sick =>
        if (randomGenerator.randomBelow(101) <= config.mortalityPct)
          Some(new HealthChange(14 - 6, Dead))
        else None
      case Dead => 
        None
      case Immune => 
        Some(new HealthChange(18 - 16, Healthy))
      case Vaccinated => 
        None        
    }
}
//                                 infected?  visibly?
object Healthy extends      Health(false,     false)
object Incubatious extends  Health(true,      false)
object Sick extends         Health(true,      true)
object Dead extends         Health(true,      true)
object Immune extends       Health(true,      false)
object Vaccinated extends   Health(false,     false)

具有几乎完整代码的“原始”版本,不包括以下情况下发生的信息:

// P.S.: Excuse the underscores ...
  class Person (val id: Int) {

    // Simplified primitive version, assuming the information when should the evolution
    // happen is handled somewhere else
    def evolveHealth(): Unit = {
      if (!_infected)
        if (randomGenerator.randomBelow(101) <= config.transmissibilityPct)
          this._infected = true
      if (_infected && !_sick && !_immune)
        this._sick = true
      if (_sick)
        if (randomGenerator.randomBelow(101) <= config.mortalityPct)
          this._dead = true
        else
          this._unDead = true // did not die but still sick
      if (_unDead)
        this._immune = true
        this._sick = true
      if (_immune)
        this._immune = false
        this._infected = false
    }
    
    private var _unDead = false
    private var _infected = false
    private var _sick = false
    private var _immune = false
    private var _dead = false
  }


翻译自: https://www.javacodegeeks.com/2013/11/surfacing-hidden-design-seeking-a-better-alternative-to-interrelated-mutable-fields.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值