Scala中的安全/不安全模式

时间:2018-08-20 22:23:53

标签: scala

嗨,我有一个可以提取json的类,并且想要做一个安全/不安全的版本。

目前我有这样的类定义

class Safe {
  def getA: Option[String] = ...
  def getB: Option[Int] = ...

  ... etc ... 
}

然后是一个不安全的版本,该版本仅委派给Safe类:

class Unsafe(delegate: Safe) {
  def getA: String = delegate.getA.get
  def getB: Int = delegate.getB.get

  ... etc ... 
}

这可行,但显然主要的问题是委托是手动维护的,并且如果我们更改了Safe接口的任何内容,则必须手动确保{{1 }}类。

在Scala中是否有更多的惯用语和较少的手动模式可以做到这一点?

2 个答案:

答案 0 :(得分:1)

这是我的建议的实现。

  1. 定义由类型构造函数参数化的Extractor接口:

    trait Extractor[F[_]] { outer =>
      def getA: F[String]
      def getB: F[Int]
    
      /* insert `transform` here */
    }
    
  2. 一劳永逸地实施transform方法,该方法需要任意F ~> G

      def transform[G[_]](natTrafo: F ~> G): Extractor[G] = 
        new Extractor[G] {
          def getA: G[String] = natTrafo[String](outer.getA)
          def getB: G[Int] = natTrafo[Int](outer.getB)
        }
    

    在这里,F ~> G是一种多态函数,可以将任意类型的F[A]G[A]({{1 }},或您想在提取器中A进行的其他数千种类型):

    String

    此接口无处不在,在Scalaz和Cats中可用(那里称为Int),有时也称为“自然转换”。

  3. 实施get

    trait ~>[F[_], G[_]] {
      def apply[A](fa: F[A]): G[A]
    }
    
  4. 通过为FunctionK提供简单的SafeExtractor实现,免费获得class SafeExtractor extends Extractor[Option] { def getA: Option[String] = None /* do sth. more clever here? */ def getB: Option[Int] = None }

    UnsafeExtractor

您现在还可以轻松地重用相同的Option ~> Id函数,通过transform将结果从type Id[X] = X val safe: Extractor[Option] = new SafeExtractor() val unsafe: Extractor[Id] = safe.transform( new ~>[Option, Id] { def apply[A](x: Option[A]): Id[A] = x.get } ) 转换为transform,或将Extractor[Future]转换为{{1} },捕获所有错误等。

完整代码

Extractor[Id]

答案 1 :(得分:0)

Andrey Tyukin的答案是解决此特定问题的好方法,但是可能存在更大的设计问题。

您的代码假定JSON的结构(在Safe中定义)与代码中的数据结构(在Unsafe中定义)相匹配。当您想要更改代码中的数据结构,或者更改JSON格式,或者想要从其他来源(例如XML)导入数据时,或者当您希望对以下内容进行更复杂的验证时,这会导致问题数据。

因此,解决此问题的正确方法是按照适合您的应用程序的方式设计应用程序数据结构(您的Unsafe类)。然后,您提供一个JSON读取库,该库将传入的数据转换为该格式。该库在内部使用您的Safe类。这可以执行可能需要的任何验证/数据条件,并且可以调整以适应JSON格式的更改,而不会影响系统的其余部分。您的单元测试框架将确保您的两个类保持同步。

此设计模式是separation of concerns

的示例

以下是一些示例代码:

在主应用程序中:

// Data structure for use by application
case class Unsafe(a: String, b: Int)

// Abstract interface for loading data
trait Loader {
  def load(): Unsafe
}

在数据加载库中:

// JSON implementation of loading interface
object JsonLoader extends Loader {

  protected case class Safe(
    a: Option[String],
    b: Option[Int]
  )

  def load(): Unsafe = {
    val json = rawLibrary.readData() // Load data in JSON format
    val safe: Safe = jsonLibrary.extract[Safe](json)

    // Validate/condition the raw data here
    val a = safe.a.getOrElse("")
    val b = safe.b.getOrElse(0)

    // Return the application data
    Unsafe(a, b)
  }
}

从JSON到应用程序数据的映射隐藏在JsonLoader对象内部。这样可以更轻松地确保它们同步,从而可以更改JSON格式而不会影响更大的代码。