重构OO代码以实现功能

时间:2014-05-31 03:14:59

标签: scala scalaz

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

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

在转换为FP范例时,您的方法是什么?我刚刚开始学习Scalaz state 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 
   }



 }

3 个答案:

答案 0 :(得分:2)

此代码目前有点难以理解,让我看看是否可以梳理相关位。

免责声明:我不是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的费用过高,我会建议使用一个懒惰的流,它可以左右折叠存储器中。

答案 1 :(得分:1)

将此代码移动到更实用的功能,而不是

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)

这是好的和不可改变的,但是你必须绕过州,你真正想要的是状态monad。

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。

答案 2 :(得分:0)

我仍然不了解您的代码的一些细节,但我认为可以给出一些明确的指示。你可以做的第一件事就是让整个MySave类不变为这样:

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

你可以做的第二件事是修改add函数,使它返回MySave对象的新状态(也许你已经注意到这也可以用状态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值,对吗? 我仍然不确定是否有某种方法可以使它更具功能性#34;例如使用你建议的State monad。在这种情况下,您可能希望使用一些Monad Transformer来实现&#34;互操作&#34;与Either