【问题标题】:Refactor OO code to functional将 OO 代码重构为功能性
【发布时间】:2014-06-07 02:42:44
【问题描述】:

想要重构我刚刚遇到的这个面向对象的代码。代码的作者说 MySave 类仅作为 keysinserts 地图的占位符创建。 MySave 类的客户端总是根据每个请求创建 MySave 的新实例,并使用 add() 方法填充键和插入地图。 然后另一个函数将使用 MySave() 的 keysinserts vals 从相应的地图中检索这些值,然后将它们发送到持久层等。

即使在这种 OO 设计中已经存在缺陷(例如,尽管当前的用法是为每个新请求创建 MySave 对象,但以下设计并不能阻止仅创建单个类实例并使用共享状态产生线程安全问题,并且它通过公共vals 等泄漏共享状态。我不想修复 OO 设计,而是想将其转换为功能代码。

在转换为 FP 范式时,您的方法是什么?我刚刚开始了解 Scalaz 状态 monad,但这听起来像是这个 OO 代码的一个很好的 FP 替代方案,还是比这更微不足道的东西可以工作?

class MySave {

  val keys = mutable.Map[Entity Int]() 
  val inserts = mutable.Map[Entity, String]() 
  def add(d: Entity, value: String): \/[Throwable,Unit] = {
    //this is a side effecting method which either updates `keys` or `inserts` map
   // by simply reassigning `keys` or `inserts` vars e.g. keys += d ->value 
   }



 }

【问题讨论】:

    标签: scala scalaz


    【解决方案1】:

    这段代码目前有点难以理解,让我看看能不能梳理出相关的部分。

    免责声明:我不是 Scala 程序员,虽然我用过一点。如果我的语法关闭,请原谅。

    您是说当前发出请求的方式类似于:

    val save = MySave()
    save.add(d, x)
    save.add(d2, y)
    ...
    
    SendToPersistentLayerEtCetera(save)
    

    这种使用模式表明(正确使用的)MySave 与一系列Dictionary, String 对同构。所以你的界面可能看起来像:

    class MySave {
        val keys = Map[Entity, Int]     # immutable maps
        val inserts = Map[Entity, Keys]
    }
    
    def save(inputs : List[Pair[Entity, String]]) : MySave
    

    您可以像以前一样将MySave 传递给SendToPersistentLayerEtCetera,但现在MySave 不能被滥用。 save 是功能性实现还是命令性实现与设计无关。如果你想在功能上实现save,对我来说它看起来像是一个左折叠(将插入Pair[Entity,String]的函数折叠到MySave中,返回新的MySave)。

    如果您预计会有很多 save 调用,以至于创建 List 的开销太大,我建议使用惰性流,它可以在常量内存中左折叠。

    【讨论】:

    • 你的评论是对的不太了解您与 Dictionary 相关的同构评论。还使用您的方法 add() 方法仍然会产生副作用并更新键和插入值?在您的 def save() 中,您传递了一对,但是当persistantLayer 尝试执行 mySave.keys、mySave.inserts 时,该部分将如何工作?抱歉有太多问题,但看起来我缺少了解一些细节。你能详细说明一下吗?
    • 这种方法结合状态单子就是你想要的。
    【解决方案2】:

    将此代码移动到功能更强大的地方,而不是

    val save = new MySave()
    save.add(d, x)
    save.add(d2, y)
    

    您可以执行以下操作

    case class MySave(keys: Map[Dictionary, Int], inserts: Map[Dictionary, String])
    def add(mySave: MySave, d: Dictionary, value: String): MySave = {
    
    val save1 = add(MySave(), d, x)
    val save2 = add(save1, d2, y)
    

    这很好而且不可变,但是你必须绕过状态很糟糕,你真正想要的是状态单子。

    case class MySave(keys: Map[Dictionary, Int], inserts: Map[Dictionary, String])
    
    def add(d: Dictionary, value: String): State[MySave, Unit] = ...
    

    这将让您执行以下操作...

    val prog = for {
      _ <- add(d, x)
      _ <- add(d2, y)
    } yield ()
    
    prog.exec(MySave())
    

    现在您不再需要传递 MySave 实例。

    在实现状态方面的帮助,或者为了找出我的建议,我建议先尝试一下,然后在 SO 上发布问题,或者在 freenode 上尝试#scalaz。

    【讨论】:

      【解决方案3】:

      我还是不明白你的代码的一些细节,但我认为有一些明确的指针可以给出。您可以做的第一件事是使整个 MySave 类不可变,如下所示:

        case class MySave( val keys: Map[Dictionary Int], inserts: Map[Dictionary, String])
      

      您可以做的第二件事是修改add 函数,使其返回MySave 对象的新状态(也许正如您所注意到的,这也可以通过State monad 完成,但该方法并非如此现在对我来说清楚)。新功能可能是这样的:

      def add(dd: Dictionary, value: String): \/[Throwable,MySave] = {
      nmField match {
           case Some(id) =>
           val validId = checkDictType("numeric", value)
           for {
                 k <- validId
           } yield { MySave(keys + (dd -> k) , inserts) }
           case None =>
               for {
                 r <- validateDictTypeAndValue(dd, value)
            } yield { MySave( keys, inserts + (dd -> value) ) }
      }
      }
      

      我猜 checkDictTypevalidateDictTypeAndValue 返回 Either 值,对吧? 我仍然不确定是否有某种方法可以使它更具“功能性”,例如按照您的建议使用 State monad。在这种情况下,您可能需要使用一些 Monad Transformer 使其与Either“互操作”。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2022-07-12
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2010-11-13
        • 1970-01-01
        相关资源
        最近更新 更多