我们日常使用的最常见的模式之一是将对象从一种类型的对象转换为另一种类型。 原因多种多样; 一个原因是要区分外部和内部实现,另一个原因是要用其他信息丰富传入的数据,或者在将数据发送给用户之前过滤掉数据的某些方面。 有几种方法可以实现对象之间的这种转换:
- 幼稚的方法
将转换器代码显式添加到对象:
case class ClassA(s: String) case class ClassB(s: String) { def toClassA = ClassA(s) }
尽管这是最直接,最明显的实现,但它确实将ClassA和ClassB联系在一起,这正是我们要避免的。
- 胖肚子综合征
当我们要在对象之间进行转换时,最好的方法是将逻辑从类中重构出来,使我们可以分别对其进行测试,但仍可以在多个类上使用它。 典型的实现如下所示:
class SomeClass(c1: SomeConverter, c2: AnotherConverter, ...., cn, YetAnotherConverter) { ........... }
转换器本身可以实现为普通类,例如:
enum CustomToStringConverter { INSTANCE; public ClassB convert(ClassA source) { return new ClassB(source.str); } }
这种方法迫使我们为需要这些转换器的每个类包括所有所需的转换器。 一些开发人员可能会想模拟那些转换器,这会将他们的测试与具体转换器紧密耦合。 例如:
// set mock expectations converter1.convert(c1) returns c2 dao.listObj(c2) returns List(c3) converter2.convert(c3) returns o4 someClass.listObj(o0) mustEqual o4
我对这些测试不满意的是,所有代码都流经转换逻辑,最后您将比较某些模拟返回的结果。 例如,如果转换器的模拟期望之一不能完全比较输入对象,并且程序员将不匹配输入对象,则将使用any运算符,从而引起测试的争议。
- 蜥蜴的尾巴
与Scala一起使用的另一种选择是能够继承多个特征并向转换器代码提供特征的能力。 让我们混合和匹配这些转换器。 典型的实现如下所示:
class SomeClass extends AnotherClass with SomeConverter with AnotherConverter..... with YetAnotherConverter { ............... }
使用这种方法将使我们能够将转换器插入多个实现中,同时消除了在测试中模拟转换逻辑的需求(或冲动),但这引发了一个设计问题–是将一个对象转换为与对象相关的另一个对象的能力吗?上课的目的? 它还鼓励开发人员将越来越多的特征堆积到一个类中,并且从不删除旧的未使用的特征。
- 鸵鸟方式
Scala允许我们隐藏问题并使用隐式转换。 这种方法使我们能够真正隐藏问题。 现在,实现将如下所示:
implicit def converto0too2(o0: SomeObject): AnotherObj = ... implicit def convert01to02(o1: AnotherObject): YetAnotherObj = ... def listObj(o0: SomeObj): YetAnotherObj = dao.doSomethingWith(entity = o0)
该代码实际执行的操作是将o0转换为o1,因为这是listObj所需要的。 当结果返回o1并将其隐式转换为o2。 上面的代码对我们隐藏了很多,如果工具没有向我们显示这些转换,我们将感到困惑。 当我们想要在具有相同功能和目的的对象之间进行转换时,隐式转换有效的一个好用例。 这方面的一个很好的例子是在Scala列表和Java列表之间进行转换,两者基本上是相同的,我们不想在我们在这两个列表之间进行转换的所有地方乱扔我们的代码。
总结一下我们遇到的问题:
- 构造函数中大量未使用的垃圾特征或垃圾类列表。
- 不能代表班级真正目的的特质。
- 隐藏其真实流程的代码。 为了解决所有这些问题,Scala通过使用隐式类创建了一个很好的模式。
要编写转换代码,我们可以执行以下操作:
object ObjectsConveters {
implicit class Converto0To1(o0: SomeObject) {
def asO1: AnotherObject = .....
}
implicit class Converto1To2(o0: AnotherObject) {
def asO2With(id: String): YetAnotherObject = .....
}
现在我们的代码将如下所示:
import ObjectsConveters._
def listObj(o0: SomeObj): YetAnotherObj = listObj(o0.asO1).asO2With(id = "someId")
这种方法允许我们同时隐式和显式。 通过查看上面的代码,您可以了解o0转换为o1,结果再次转换为o2。 如果不使用转换,则IDE将优化我们代码中的导入。 我们的测试不会提示我们模拟每个转换器,从而产生规范,这些规范解释了类中代码流的正确行为。 请注意,转换器代码已在其他地方进行了测试。 这种方法使我们可以在代码的其他地方编写更具可读性的测试。 例如,在我们的e2e测试中,我们减少了定义的对象数量:
"some API test" in {
callSomeApi(someId, o0) mustEqual o0.aso2With(id = "someId")
}
现在,此代码更易读,更有意义。 我们传递了一些输入,结果与我们在API调用中使用的对象相同。
翻译自: https://www.javacodegeeks.com/2014/07/explicit-implicit-conversion.html