使用特殊规则键入安全的 Scala Builder 模式
Type safe Scala Builder pattern with special rules
我正在尝试创建案例 class 的类型安全构建器,其中它的参数可以是以下类型:
- 需要
- 可选
- 必需但互斥 ->
一种。前任。假设我有 3 个参数:(param1)、(param2、param3)。如果我有 param1,我不能设置 param2 或 param3。如果我可以同时设置param2和param3,但是我不能设置param1
- 可选但互斥 -> 与上述逻辑相同,但这些是可选参数。这是可选的,我可以设置 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")
如评论中所述,如果创建构建器不是硬性要求,则可行的选择可能是在类型中明确这些要求,使用总和类型作为独占选择,使用 Option
s 作为可选一个,如下例所示:
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],
)
Scala 3 进一步引入了 enum
s,这使得定义更加紧凑和可读:
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
s 默认设置为 None
,您将获得与 custom-made 构建器非常相似的体验,无需任何额外代码:
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"))
您可以尝试使用此代码 here on Scastie。
我正在尝试创建案例 class 的类型安全构建器,其中它的参数可以是以下类型:
- 需要
- 可选
- 必需但互斥 -> 一种。前任。假设我有 3 个参数:(param1)、(param2、param3)。如果我有 param1,我不能设置 param2 或 param3。如果我可以同时设置param2和param3,但是我不能设置param1
- 可选但互斥 -> 与上述逻辑相同,但这些是可选参数。这是可选的,我可以设置 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")
如评论中所述,如果创建构建器不是硬性要求,则可行的选择可能是在类型中明确这些要求,使用总和类型作为独占选择,使用 Option
s 作为可选一个,如下例所示:
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],
)
Scala 3 进一步引入了 enum
s,这使得定义更加紧凑和可读:
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
s 默认设置为 None
,您将获得与 custom-made 构建器非常相似的体验,无需任何额外代码:
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"))
您可以尝试使用此代码 here on Scastie。