Scala代码重构:消除重复逻辑并解决隐式转换编译错误
问题:Scala重构模式匹配后JSON序列化失败(找不到隐式Writes)
我有一段通过模式匹配处理OCPP请求并返回响应的Scala代码,原代码中OCPPCallError和OCPPCallResult的构建逻辑存在大量重复,后续还要新增10个模式匹配分支,因此尝试重构,但编译失败,提示找不到Writes[Product with Serializable]的隐式转换。
原代码:
def handleProvisioningServiceCSRequests(provisioningCsRequest: ProvisioningCSRequest, chargingStationId: String, msgTypeId: Int, msgId: String): Future[OCPPCallResponse] = provisioningCsRequest match { case bootNotificationRequest: BootNotificationRequest => provisioningService.bootNotification(bootNotificationRequest, chargingStationId).runToFuture.materialize.map { case Failure(fail) => populateOCPPCallError(msgId, msgTypeId, OCPPCallErrorCode.InternalError(), fail.getMessage) case Success(response) => OCPPCallResult( messageTypeId = msgTypeId, messageId = msgId, payload = Json.toJson(response) ) } case notifyReportRequest: NotifyReportRequest => provisioningService.notifyReport(notifyReportRequest, chargingStationId).runToFuture.materialize.map { case Failure(fail) => populateOCPPCallError(msgId, msgTypeId, OCPPCallErrorCode.InternalError(), fail.getMessage) case Success(response) => OCPPCallResult( messageTypeId = msgTypeId, messageId = msgId, payload = Json.toJson(response) ) } }
重构后的代码(编译报错):
def handleProvisioningServiceCSRequests(provisioningCsRequest: ProvisioningCSRequest, chargingStationId: String, msgTypeId: Int, msgId: String): Future[OCPPCallResponse] = { val result = provisioningCsRequest match { case bootNotificationRequest: BootNotificationRequest => provisioningService.bootNotification(bootNotificationRequest, chargingStationId).runToFuture case notifyReportRequest: NotifyReportRequest => provisioningService.notifyReport(notifyReportRequest, chargingStationId).runToFuture } result.materialize.map { case Failure(fail) => populateOCPPCallError(msgId, msgTypeId, OCPPCallErrorCode.InternalError(), fail.getMessage) case Success(response) => OCPPCallResult( messageTypeId = msgTypeId, messageId = msgId, payload = Json.toJson(response)) // 报错:找不到Writes[Product with Serializable]的隐式转换 } }
原因分析
重构后,result的类型被Scala编译器推断为Future[Product with Serializable]——这是BootNotificationResponse和NotifyReportResponse的公共超类型。而Json.toJson需要对应具体类型的隐式Writes实例,这个模糊的超类型并没有对应的Writes定义,因此编译失败。
解决方案
方案一:使用高阶函数封装重复逻辑(推荐,无需修改现有响应类型结构)
把错误处理和结果封装逻辑抽成一个高阶函数,利用上下文绑定确保每个具体响应类型都有对应的Writes:
// 封装重复的响应处理逻辑 private def wrapResponse[T: Writes](msgId: String, msgTypeId: Int): Future[T] => Future[OCPPCallResponse] = { future => future.materialize.map { case Failure(fail) => populateOCPPCallError(msgId, msgTypeId, OCPPCallErrorCode.InternalError(), fail.getMessage) case Success(response) => OCPPCallResult( messageTypeId = msgTypeId, messageId = msgId, payload = Json.toJson(response) ) } } // 重构后的主方法 def handleProvisioningServiceCSRequests(provisioningCsRequest: ProvisioningCSRequest, chargingStationId: String, msgTypeId: Int, msgId: String): Future[OCPPCallResponse] = provisioningCsRequest match { case req: BootNotificationRequest => wrapResponse(msgId, msgTypeId)(provisioningService.bootNotification(req, chargingStationId).runToFuture) case req: NotifyReportRequest => wrapResponse(msgId, msgTypeId)(provisioningService.notifyReport(req, chargingStationId).runToFuture) // 后续新增分支只需一行代码调用wrapResponse即可,完全避免重复 }
方案二:定义公共响应父类型并提供统一Writes
如果所有响应类型可以继承自同一个 trait,可以为这个父类型定义隐式Writes:
- 定义公共父类型:
trait OCPPServiceResponse case class BootNotificationResponse(/* 字段 */) extends OCPPServiceResponse case class NotifyReportResponse(/* 字段 */) extends OCPPServiceResponse // 后续新增的响应类型都继承OCPPServiceResponse
- 为父类型定义隐式
Writes:
import play.api.libs.json._ implicit val ocppServiceResponseWrites: Writes[OCPPServiceResponse] = Writes { case resp: BootNotificationResponse => Json.toJson(resp) case resp: NotifyReportResponse => Json.toJson(resp) // 后续新增响应类型时,在这里添加对应的case分支 }
- 指定
result的类型为Future[OCPPServiceResponse]:
def handleProvisioningServiceCSRequests(provisioningCsRequest: ProvisioningCSRequest, chargingStationId: String, msgTypeId: Int, msgId: String): Future[OCPPCallResponse] = { val result: Future[OCPPServiceResponse] = provisioningCsRequest match { case req: BootNotificationRequest => provisioningService.bootNotification(req, chargingStationId).runToFuture case req: NotifyReportRequest => provisioningService.notifyReport(req, chargingStationId).runToFuture } result.materialize.map { case Failure(fail) => populateOCPPCallError(msgId, msgTypeId, OCPPCallErrorCode.InternalError(), fail.getMessage) case Success(response) => OCPPCallResult( messageTypeId = msgTypeId, messageId = msgId, payload = Json.toJson(response) ) } }
内容的提问来源于stack exchange,提问作者joesan
相关产品推荐
相关产品推荐

