首页 > 解决方案 > 具有泛型的 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
}

如果可能由于工厂方法调用,我很乐意避免类型擦除,并想知道为什么该代码不起作用

标签: scalagenericsfactoryfactory-pattern

解决方案


您在这里所做的(显然是偶然的?)是创建一个广义代数数据类型,或简称 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方法。


推荐阅读