使用特殊规则键入安全的Scala Builder模式



我正在尝试创建一个case类的类型安全生成器,其中它的参数可以是以下类型:

  1. 必需
  2. 可选的
  3. 必需但相互排斥->a.例如,假设我有三个参数:(param1(,(param2,param3(。如果我有param1,我不能设置param2或param3。如果我可以同时设置param2和param3,但不能设置param1
  4. 可选但相互排斥->与上述逻辑相同,但这些是可选参数。这是可选的,我可以设置param1或(param2和param3(

我知道如何获得必需和可选的案例,但无法获得案例3和4。如何继续的任何想法。

这些检查可以在运行时完成,但我希望这些规则集在编译时实现

case class Person(name: String, /*required*/
address: String, /*optional*/
city: String, /* reqd exclusive*/
county: String, /* reqd exclusive*/
state: String /* reqd exclusive*/,
ssn: String, /* optional exclusive*/
insurance: String, /* opt exclusive*/
passport: String /* opt exclusive*/)
// where (city) and (county, state) are required but are mutually exclusive
// (ssn) and (insurance, passport) are optional but are mutually exclusive. 
// If I set passport, I've to set insurance
sealed trait PersonInfo
object PersonInfo {
sealed trait Empty extends PersonInfo
sealed trait Name extends PersonInfo
sealed trait Address extends PersonInfo
type Required = Empty with Name with Address
}
case class PersonBuilder[T <: PersonInfo]
(name: String = "", address: String = "", city: String = "", county: String = "", 
state: String = "", ssn: String = "", insurance: String = "",passport: String ="") {
def withName(name: String): PersonBuilder[T with PersonInfo.Name] =
this.copy(name = name)
def withTask(address: String): PersonBuilder[T with PersonInfo.Address ] =
this.copy(address = address)
def withCity(city: String): PersonBuilder[T] =
this.copy(city = city)
def withCountry(county: String): PersonBuilder[T] =
this.copy(county = county)
def withState(state: String): PersonBuilder[T] =
this.copy(state = state)
def withSsn(ssn: String): PersonBuilder[T] =
this.copy(ssn = ssn)
def withInsurance(insurance: String): PersonBuilder[T] =
this.copy(insurance = insurance)
def withPassport(passport: String): PersonBuilder[T] =
this.copy(passport = passport)
def build(implicit ev: T =:= PersonInfo.Required): Person =
Person(name, address, city, county, state, ssn, insurance, passport)
}

这是构建

val testPerson = PersonBuilder[PersonInfo.Empty]()
.withName("foo")
.withSsn("bar")

如注释中所述,如果创建构建器不是一个硬性要求,那么一个可行的选择可能是在类型中明确这些要求,使用sum类型作为独占选择,Options作为可选选择,如以下示例所示:

sealed abstract class Location extends Product with Serializable {
def value: String
}
object Location {
final case class City(value: String) extends Location
final case class County(value: String) extends Location
final case class State(value: String) extends Location
}
sealed abstract class Identity extends Product with Serializable {
def value: String
}
object Identity {
final case class Ssn(value: String) extends Identity
final case class Insurance(value: String) extends Identity
final case class Passport(value: String) extends Identity
}
final case class Person(
name: String,
address: Option[String],
location: Location,
identity: Option[Identity],
)

Scala3进一步引入了enums,使定义更加紧凑和可读:

enum Location(value: String) {
case City(value: String) extends Location(value)
case County(value: String) extends Location(value)
case State(value: String) extends Location(value)
}
enum Identity(value: String) {
case Ssn(value: String) extends Identity(value)
case Insurance(value: String) extends Identity(value)
case Passport(value: String) extends Identity(value)
}
final case class Person(
name: String,
address: Option[String],
location: Location,
identity: Option[Identity],
)

使Option默认为None,您可以获得与定制构建器非常相似的体验,而无需任何额外的代码:

final case class Person(
name: String,
location: Location,
address: Option[String] = None,
identity: Option[Identity] = None,
)
Person("Alice", Location.City("New York"))
.copy(identity = Some(Identity.Ssn("123456")))

你可以很容易地进一步完善:

final case class Person(
name: String,
location: Location,
address: Option[String] = None,
identity: Option[Identity] = None
) {
def withAddress(address: String): Person =
this.copy(address = Some(address))
def withIdentity(identity: Identity): Person =
this.copy(identity = Some(identity))
}
Person("Alice", Location.City("New York")).withIdentity(Identity.Ssn("123456"))

你可以在Scastie上玩这个代码。

相关内容

  • 没有找到相关文章

最新更新