具有隐式参数的Scala设计模式(Scala中的Play 2.x)

时间:2013-03-22 19:05:01

标签: scala playframework playframework-2.0 implicit

我正在开发一个Play 2.1项目,需要一些关于scala设计问题的指导。 对于我们的应用程序,需要在模型层存储来自传入请求的客户端信息的请求上下文对象。

case class ClientContext(clientName: String) 

object ClientContext {
  def apply(request: Request) = {
    new ClientContext(request.params("clientName")) //pseudo code
  }
}

我的模特

object MyDAO {
  def findAll(context: ClientContext) = { ... }
}

然后在我们的控制器中,我们需要将它传递给模型的dao方法:

object MyController extends Controller {
  def index = Action { implicit request => 
    val results = MyDAO.findAll(ClientContext(request))
    Ok(results)
  }
}

implicit request类提供Action(我想)这种方法的问题是我需要为每个控制器操作编写implicit request =>ClientContext(request)打电话给MyDAO.findAll。

有没有办法通过Action包装器和隐式值来改进代码?我希望能够将context: ClientContext声明为MyDAO.findAll方法中的隐式参数,并按以下方式编写我的操作:

object MyDAO {
  def findAll(implicit context: ClientContext) = { ... }
}

def index = ActionWithContext {
  val results = MyDAO.findAll
  Ok(results)
} 

是否可以编写ActionWithContext(带有apply方法的方法或对象)来实现它?我现在最接近的是以下

def ActionWithContext(action: ClientContext => Result) = {
  Action { implicit request =>
    action(ClientContext(request))
  }
} 

使用

def index = ActionWithContext { context => 
  val results = MyDAO.findAll(context)
  Ok(results)
}

任何改进此设计的建议都会有所帮助。谢谢!

PS:老实说,如果这是在Java上,我甚至不会考虑进一步简化代码,但由于它是scala,我认为这可能是学习一些scala模式的好机会。

2 个答案:

答案 0 :(得分:3)

我使用了implicits实现了类似的东西:

我称之为Header而不是Context,但我们都在完成同样的事情。

我的所有控制器混合Header特征:

object Accounts extends AuthController with Header { ... }

我的Header特征看起来像这样:

trait Header {
    implicit def withUserInfo(implicit maybeUser: Option[User]): UserInfo = {
        // create user info object
    }
}

然后我可以这样写我的控制器动作:

def index = MaybeAuthenticated { implicit maybeUser => implicit request =>
    // do stuff
    val foo = new Foo()
    Ok(views.html.accounts.index(foo))
}

模板的方法签名如下:

@(foo: Foo)(implicit userInfo: UserInfo)

MaybeAuthenticated只是一个动作,它可以选择恢复User对象,它来自play20-auth模块。事实上,我已经向您展示了两种可能性:

  1. 使用隐式函数混合特征,该函数采用隐式参数。
  2. 编写自己的操作方法,例如MaybeAuthenticated
  3. MaybeAuthenticated看起来像这样:

    private def maybeAuthenticated(f: Option[Account] => Request[AnyContent] => Result): Action[AnyContent] = {
        Action(BodyParsers.parse.anyContent)(req => f(restoreUser(req))(req))
    }
    
    protected def MaybeAuthenticated = maybeAuthenticated _
    

    我认为第一种方法更容易理解。

    编辑:我认为有必要进一步解释隐含。

    让我们考虑一下上面使用的implicit

    隐式def withUserInfo(隐式maybeUser:Option [User]):UserInfo

    Header混合的对象中,此方法将在范围内。编译器将搜索要求UserInfo对象位于Option[User]已在范围内的范围的函数。编译器将隐式地调用withUserInfo以提供缺少的UserInfo对象。

    请注意我的模板所需的隐式UserInfo对象。当我调用此模板函数(Ok(...)调用)时,编译器必须填写隐式UserInfo对象。它将通过调用withUserInfo并传递范围内的隐式maybeUser来实现。

    希望澄清一点含义。

答案 1 :(得分:1)

感谢Ryan的建议,这是另一个解决方案 在ClientContext对象中,使apply方法也是一个隐式转换,它接受一个隐式参数

object ClientContext {
  implicit def apply(implicit request: Request) = {
    new ClientContext(request.params("clientName")) //pseudo code
  }
}

然后在控制器中你可以写

def index = Action { implicit request => 
  val results = MyDAO.findAll
  Ok(results)
}

我不确定是否有办法摆脱implicit request,但这对我来说非常简洁。