链功能以不同的方式

时间:2016-11-01 10:43:47

标签: scala methods composition implicit method-chaining

Scala函数有以下链接方法:

 fn1.andThen(fn2)
 fn1.compose(fn2)

但是如何写这个案子:

我有函数cleanUp(),必须始终作为最后一步调用。 我有很多其他功能,如:

class Helper {
  private[this] val umsHelper = new UmsHelper()
  private[this] val user = umsHelper.createUser()
  def cleanUp = ... // delete user/ and other entities

  def prepareModel(model: TestModel) = {
    // create model on behalf of the user
  }

  def commitModel() = {
    // commit model on behalf of the user
  }
}

一些外部代码可以使用这样的代码:

val help = new Helper()
help.prepareModel()
help.commitModel()
// last step should be called implicitly cleanUp

如何以功能性的方式编写,链接将永远 隐含地调用cleanUp函数作为最后一步?

注意:我认为它是C ++中析构函数的模拟。一些链接(无论这个链如何完成)fn1 andLater fn2 andLater fn3必须调用最后一步cleanUpfn1 andLater fn2 andLater fn3 andLater cleanUp)。错误的直接写cleanUp方法是有一个很大的变化,有人会错过这一步,用户将被泄露(留在数据库中)

3 个答案:

答案 0 :(得分:3)

这是一个更高级的选择:

当你听到“背景”和“步骤”时,会有一种直接浮现在脑海中的功能模式:Monads。汇总您自己的monad实例可以简化将有效步骤放在一起的用户端,同时提供保证,以便在它们之后清理上下文。

在这里,我们将开发一个遵循该模式的“CleanableContext”结构。

我们将构造基于最简单的monad,其唯一的功能是保存一个值。我们打算称之为Context

trait Context[A] { self => 
  def flatMap[B](f:A => Context[B]): Context[B] = f(value)
  def map[B](f:A => B): Context[B] = flatMap(f andThen ((b:B) => Context(b)))
  def value: A
}

object Context {
  def apply[T](x:T): Context[T] = new Context[T] { val value = x  }
}

然后我们有CleanableContext,它能够“自我清理”,提供一些“清理”功能:

trait CleanableContext[A] extends Context[A] {
  override def flatMap[B](f:A => Context[B]): Context[B] = {
    val res = super.flatMap(f)
    cleanup
    res
  }
  def cleanup: Unit
}

现在,我们有一个对象可以生成一个可清除的UserContext,它将负责管理用户的创建和销毁。

object UserContext {
  def apply(x:UserManager): CleanableContext[User] = new CleanableContext[User] {
    val value = x.createUser
    def cleanup = x.deleteUser(value)
  }
}

假设我们已经定义了我们的模型和业务功能:

trait Model
trait TestModel extends Model
trait ValidatedModel extends Model
trait OpResult
object Ops {
  def prepareModel(user: User, model: TestModel): Model = new Model {}

  def validateModel(model: Model): ValidatedModel = new ValidatedModel {}

  def commitModel(user: User, vmodel: ValidatedModel): OpResult = new OpResult {}
}

用法

有了可重复使用的机器,我们的用户可以简洁地表达我们的流程:

import Ops._
val ctxResult = for {
  user <- UserContext(new UserManager{})
  validatedModel <- Context(Ops.prepareModel(user, testModel)).map(Ops.validateModel)
  commitResult <- Context(commitModel(user, validatedModel))
} yield commitResult

该过程的结果仍然是封装的,可以使用Context方法从value“取出”:

val result = ctxResult.value

请注意,我们需要将业务操作封装到{<1}}中,以便在此 monadic组合中使用。还要注意,我们不需要手动创建也不需要清理用于操作的用户。这是为我们照顾的。

此外,如果我们需要多种托管资源,可以使用此方法通过组合不同的上下文来管理其他资源。

有了这个,我只想为问题提供另一个角度。管道更复杂,但它为用户创建了一个坚实的基础,通过组合创建安全的流程。

答案 1 :(得分:2)

我认为问题的核心是 “如何在托管上下文中保留资源” 。即为用户提供使用资源的方法,并防止其在其上下文之外“泄漏”。

一种可能的方法是提供对托管资源的功能访问,其中API需要函数来对相关资源进行操作。让我用一个例子来说明这一点:

首先,我们定义模型的域:(我添加了一些模型的子类型以使示例更清晰)

trait User
trait Model
trait TestModel extends Model
trait ValidatedModel extends Model
trait OpResult
// Some external resource provider
trait Ums {
  def createUser: User
  def deleteUser(user: User)
}

然后我们创建一个类来保存我们的特定上下文。

class Context {
  private val ums = new Ums{ 
    def createUser = new User{} 
    def deleteUser(user: User) = ???
  } 

  def withUserDo[T](ops: User => T):T = {
    val user = ums.createUser
    val result = ops(user)
    ums.deleteUser(user)
    result
  }
}

协同对象在托管资源上提供(某些)操作。用户也可以提供自己的功能。

object Context {
  def prepareModel(model: TestModel): User => Model = ???

  val validateModel: Model => ValidatedModel = ???

  val commitModel: ValidatedModel => OpResult = ???
}

我们可以使用经典声明来实例化我们的上下文并在其上声明操作,例如:

val ctx  = new Context 
val testModel = new TestModel{}

val result = ctx.withUserDo{ user => 
  val preparedModel = prepareModel(testModel)(user)
  val validatedModel = validateModel(preparedModel)
  commitModel(validatedModel)
}

或者,考虑到问题中使用功能组合的愿望,我们可以将其重写为:

val result = ctx.withUserDo{
  prepareModel(testModel) andThen validateModel andThen commitModel
}

答案 2 :(得分:0)

使用autoClean这会在结尾处自动调用cleanUp

创建一个包含所有必要函数的HelperStuff特征。

在Helper对象内部创建HelperStuff的私有实现,然后有一个名为autoClean的方法方法,该方法可以使Helper实例保持私有且安全的方式与rouge用户保持一致。

Helper.autoClean { helperStuff =>

  //write all your code here. clean up will happen automatically
  helper.foo()
  helper.commitModel()

}

这是您的autoClean功能

trait HelperStuff {
 def foo(): Unit
 def commitModel: Unit
 def cleanUp(): Unit
}

object Helper {

  private class Helper extends HelperStuff {
   def foo(): Unit = println("foo")
   def cleanUp(): Unit = println("cleaning done")
  }

  private val helper = new Helper()

  def autoClean[T](code: HelperStuff => T): T = {
    val result = code(helper)
    helper.cleanUp()
    result
  }

}