scala - 具有泛型的 Scala 工厂模式
问题描述
我想将工厂方法与可以与特定实现一起使用的泛型一起使用。在服务类中,我希望具有类型安全性,但在控制器中操作只知道接口。
代码
我已经定义了不同类型的操作类型
trait Transaction {
val amount: BigDecimal
}
case class CreditCardTransaction(amount: BigDecimal, ccNumber: String, expiry: String) extends Transaction
case class BankTransaction(amount: BigDecimal, bankAccount: String) extends Transaction
和可以与特定操作类型一起使用的服务
trait Service[T <: Transaction] {
def transfer(transaction: T)
}
class CCService() extends Service[CreditCardTransaction] {
override def transfer(transaction: CreditCardTransaction): Unit = println("pay with cc")
}
class TTService() extends Service[BankTransaction] {
override def transfer(transaction: BankTransaction): Unit = println("pay with telex transfer")
}
我用具体实例创建了工厂
class PaymentSystemFactory(ccService: CCService, ttService: TTService) {
def getService(paymentMethod: String) = paymentMethod match {
case "cc" => ccService
case "tt" => ttService
}
}
和解析器从外部服务获取特定事务
object Parser {
def parse(service: Service[_ <: Transaction]) = service match {
case _: Service[CreditCardTransaction] => CreditCardTransaction(100, "Name", "01/01")
case _: Service[BankTransaction] => BankTransaction(100, "1234")
}
}
但是由于提供的类型与 PaymentSystemFactory 方法不匹配,该代码不想编译:
object App {
val factory = new PaymentSystemFactory(new CCService, new TTService)
val service = factory.getService("cc") // return Service[_ >: CreditCardTransaction with BankTransaction <: Transaction]
val transaction: Transaction = Parser.parse(service)
service.transfer(transaction) // Failed here: Required _$1 found Transaction
}
如果可能由于工厂方法调用,我很乐意避免类型擦除,并想知道为什么该代码不起作用
解决方案
您在这里所做的(显然是偶然的?)是创建一个广义代数数据类型,或简称 GADT。如果您想了解有关此功能的更多信息,这可能是一个有用的搜索词。
至于如何做到这一点:parse
方法的类型签名需要反映返回事务的类型与服务的事务类型匹配。
另外,您不能这样做case _: Service[CreditCardTransaction]
,由于擦除而无法正常工作。改为使用case _: CCService
。
尝试这个:
object Parser {
def parse[A <: Transaction](service: Service[A]): A = service match {
case _: CCService => CreditCardTransaction(100, "Name", "01/01")
case _: TTService => BankTransaction(100, "1234")
}
}
您还需要更改调用代码:
object App {
val factory = new PaymentSystemFactory(new CCService, new TTService)
factory.getService("cc") match {
case service: Service[a] =>
val transaction: a = Parser.parse(service)
service.transfer(transaction)
}
}
请注意,match
不用于实际区分多种情况。a
相反,在这种情况下,它的唯一目的是为事务类型命名。这是 Scala 语言中最晦涩的特性之一。当您对通配符类型进行模式匹配并使用小写名称(如a
类型参数)时,它不会检查类型是否a
(就像检查大写名称一样),但它会创建一个新的类型变量以后可以使用。在这种情况下,它用于声明transaction
变量,也用于隐式调用Parser.parse
方法。
推荐阅读
- javascript - 从 TestCafe 脚本逃逸到 shell
- arrays - 在批处理脚本中输出重复记录
- ansible - 使用 Ansible 将服务器添加到 /etc/exports
- c# - C#木材缺陷检测算法的实现与测试
- sabre - SABRE BFM 欧洲之星搜索,标准 Premier 和 Business Premier 课程没有结果
- python - 根据字典 matplotlib 中的值更改标签颜色
- laravel - 领英 API V2
- azure-iot-central - 配置成功(?)但返回错误
- oracle - Oracle:如何使用 if 条件在存储过程中调用存储过程
- powershell - 检查文件是否为空