简体   繁体   English

带有泛型的 Scala 工厂模式

[英]Scala Factory Pattern with Generic

I want to use Factory Method with Generics which can work with specific implementations.我想将工厂方法与泛型一起使用,它可以与特定的实现一起使用。 In service classes i want to have type safety but in controller to operate only know interface.在服务类中,我想要类型安全,但在控制器中操作只知道接口。

Code代码

I have defined different types of operation type我定义了不同类型的操作类型

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

and Services which can work with specific operation types和可以与特定操作类型一起使用的服务

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")
}

I have created factory with concrete instances我用具体实例创建了工厂

class PaymentSystemFactory(ccService: CCService, ttService: TTService) {
  def getService(paymentMethod: String) = paymentMethod match {
    case "cc" => ccService
    case "tt" => ttService
  }
}

And parser to get specific transaction from external service和解析器从外部服务获取特定事务

object Parser {
  def parse(service: Service[_ <: Transaction]) = service  match {
    case _: Service[CreditCardTransaction] => CreditCardTransaction(100, "Name", "01/01")
    case _: Service[BankTransaction] => BankTransaction(100, "1234")
  }
}

But that code doesn't want to compile due provided types mismatch from PaymentSystemFactory method:但是该代码不想从 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
}

I would be happy to avoid type erasure if possible due factory method call and wondered why that code doesn't work如果可能,由于工厂方法调用,我很乐意避免类型擦除,并想知道为什么该代码不起作用

What you've done here (apparently accidentally?) is to create a generalized algebraic data type, or GADT for short.您在这里所做的(显然是意外?)是创建一个广义代数数据类型,或简称为 GADT。 If you want to find out more about this feature, that is probably a useful term to search for.如果您想了解有关此功能的更多信息,这可能是一个有用的搜索词。

As for how to make this work: The type signature of the parse method needs to reflect that the type of the returned transaction matches service's transaction type.至于如何实现: parse方法的类型签名需要反映返回事务的类型与服务的事务类型匹配。

Also, you can't do case _: Service[CreditCardTransaction] , that won't work properly due to erasure.此外,您不能执行case _: Service[CreditCardTransaction] ,因为擦除而无法正常工作。 Use case _: CCService instead.case _: CCService代替。

Try this:尝试这个:

object Parser {
  def parse[A <: Transaction](service: Service[A]): A = service  match {
    case _: CCService => CreditCardTransaction(100, "Name", "01/01")
    case _: TTService => BankTransaction(100, "1234")
  }
}

And you'll need to change the calling code too:您还需要更改调用代码:

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)
  }
}

Note that the match isn't used to actually distinguish between multiple cases.请注意, match并不用于实际区分多种情况。 Instead, its only purpose here is to give a name to the transaction type, a in this case.取而代之的是,在这里它的唯一目的是给一个名称交易类型, a在这种情况下。 This is one of the most obscure features in the Scala language.这是 Scala 语言中最晦涩的特性之一。 When you do a pattern match on a wildcard type and use a lower-case name like a for the type parameter, then it doesn't check that the type is a (like it would for an uppercase name), but it creates a new type variable that you can use later on.当您对通配符类型进行模式匹配并为类型参数使用小写名称(如a ,它不会检查该类型是否为a (就像检查大写名称一样),但它会创建一个新的类型变量,您可以稍后使用。 In this case, it is used to declare the transaction variable, and also implicitly to call the Parser.parse method.在这种情况下,它用于声明transaction变量,也隐式调用Parser.parse方法。

While I was making the below solution, @Mathias proposed another one that I find really nice.在我制定以下解决方案时,@Mathias 提出了另一个我觉得非常好的解决方案。 But I still post it as an alternative that might be interesting.但我仍然将其发布为可能有趣的替代方案。

Instead of type argument (ie generic), you can use type member:您可以使用类型成员,而不是类型参数(即泛型):

trait Service {
  type T <: Transaction
  def transfer(transaction: T): Unit
}
class CCService() extends Service {
  type T = CreditCardTransaction
  override def transfer(transaction: CreditCardTransaction): Unit = println("pay with cc")
}
class TTService() extends Service {
  type T = BankTransaction
  override def transfer(transaction: BankTransaction): Unit = println("pay with telex transfer")
}

// need to use asInstanceOf, I don't know how to tell scala that the type is safe
def parse(service: Service): service.T = service  match {
  case _: CCService => CreditCardTransaction(100, "Name", "01/01").asInstanceOf[service.T]
  case _: TTService => BankTransaction(100, "1234").asInstanceOf[service.T]
}

val factory = new PaymentSystemFactory(new CCService, new TTService)
val service = factory.getService("tt")
val transaction = parse(service) // transaction has type service.T
service.transfer(transaction)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM