在Scala中编写生成器模式,同时隐藏case类直接访问
让我们假设这个生成器:在Scala中编写生成器模式,同时隐藏case类直接访问,scala,design-patterns,neo4j,spring-data-neo4j,Scala,Design Patterns,Neo4j,Spring Data Neo4j,让我们假设这个生成器: @NodeEntity case class Meeting(@Indexed creator: String, @Indexed title: String, @Indexed description: String, @Indexed activityType: String, @Indexed startSchedule: Date, @Indexed endSchedule: Date, @Ind
@NodeEntity
case class Meeting(@Indexed creator: String, @Indexed title: String, @Indexed description: String,
@Indexed activityType: String, @Indexed startSchedule: Date, @Indexed endSchedule: Date,
@Indexed place: String, @Indexed maxParticipants: Int) {
@GraphId private var _id: Long = _
private def this() {
this("unknown", "no title", "no description", "no activity type", new Date(), new Date(), "no place", 0)
//dummy values in order to satisfy Scala's no-arg constructor syntax (required by Spring-Data)
}
}
object Meeting {
def builder(): PossibleMeeting = PossibleMeeting()
case class PossibleMeeting(creator: String = "", title: String = "", description: String = "",
activityType: String = "", schedule: (Date, Date) = (new Date(), new Date()),
place: String = "", maxParticipants: Int = 0) {
def withCreator(creator: String) = {
this.copy(creator = creator)
}
def withTitle(title: String) = {
this.copy(title = title)
}
def withDescription(description: String) = {
this.copy(description = description)
}
def withActivityType(activityType: String) = {
this.copy(activityType = activityType)
}
def withSchedule(schedule: (Date, Date)) = {
this.copy(schedule = schedule)
}
def withPlace(place: String) = {
this.copy(place = place)
}
def withMaxParticipants(maxParticipants: Int) = {
this.copy(maxParticipants = maxParticipants)
}
def build(validator: PossibleMeetingValidator) = {
this.toMeeting(validator)
}
def toMeeting(possibleMeetingValidator: PossibleMeetingValidator): ValidationNEL[MeetingValidationError, Meeting] =
(possibleMeetingValidator.checkForNonEmpty("creator", creator, "Creator must not be empty") |@|
possibleMeetingValidator.checkForNonEmpty("title", title, "Title must not be empty") |@|
possibleMeetingValidator.checkForNonEmpty("description", description, "Description must not be empty") |@|
possibleMeetingValidator.checkForNonEmpty("activityType", activityType, "Activity type must not be empty") |@|
possibleMeetingValidator.checkForEndScheduleConsistency("endSchedule", schedule._1, schedule._2, "Schedule is not consistent since endSchedule date is anterior to startSchedule date") |@|
possibleMeetingValidator.checkForNonEmpty("place", place, "Place must not be empty"))(Meeting(_, _, _, _, schedule._1, _, _, maxParticipants))
}
Spring-Data-Neo4j使用注释@NodeEntity
将对象保存为图形节点
我想强制客户端使用此调用语法创建会议
:
Meeting.builder().
withCreator(creator).
withTitle(title).withDescription(description).
withActivityType(activityType).
withSchedule(schedule).withPlace(place).
withMaxParticipants(maxParticipants).build(PossibleMeetingValidator())
而不是有风险的直接方式:
Meeting(creator, title, description, activityType, schedule, place, maxParticipants)
我尝试了扩展案例类
的著名技巧,这里是通过一个密封的特征来扩展会议
,并将会议
设为私有。此链接显示一个示例:
然而,在这个解决方案中,Spring数据抛出了一个错误,通知会议
必须有一个公共构造函数(表示公共类/案例类)
有没有另一种方法可以隐藏case类,不让外部直接使用,这样Spring数据就不会抱怨访问了?这似乎很难,如果不是不可能的话,但是……那太好了:)好吧,如果我理解正确的话,这可以归结为有一个spring可以调用的构造函数,但scala代码不能。 我看到的一个(丑陋的)解决方案是将协调者私有化为
会议
单例。这将阻止scala代码(单例代码除外)直接实例化它。
另一方面,因为java不支持“作用域私有”的概念,所以从java代码的角度来看,构造函数实际上是公共的。特别是,当通过java反射访问构造函数时,构造函数被视为公共的,可以调用。这意味着spring在实例化会议
时应该没有问题,但同时scala代码将无法直接实例化它。正是你想要的
因此,您所要做的就是(注意参数列表前的private
关键字):
这应该如预期的那样工作,尽管依赖scala代码到JVM模型的不完美映射有点恶心
另一个更干净的选择是教spring如何实例化类,使用调用Metting.builder
的自定义工厂。我不会对这个解决方案作进一步评论,因为我没有spring方面的经验。然而,从快速浏览来看,以下内容似乎是相关的:
你说得对:使
case类
私有化很好,但会将框架需求(在本例中为Spring)与scala代码紧密耦合。我更喜欢建立一个豆子工厂,并实施密封特性技巧。谢谢:)事实上,我不能指望Spring的FactoryBean功能。实际上,我的客户机代码分几个步骤构造对象(使用xxx方法,
,这是构建器的主要优点之一)。Spring的功能允许配置调用的静态方法(Meeting.Builder()
),但在一个步骤中返回一个Meeting
(直到方法结束)!由于我的概念是在验证之前不构建会议
,所以我不能使用这个Spring特性。幸运的是,private case类原理很好地工作:)有一种方法可以为SDN提供自定义实例化器。请参阅:查看此处使用的配置和PersonCreator。
@NodeEntity
case class Meeting private[Meeting] (@Indexed creator: String, @Indexed title: String, @Indexed description: String,
@Indexed activityType: String, @Indexed startSchedule: Date, @Indexed endSchedule: Date,
@Indexed place: String, @Indexed maxParticipants: Int) {
...