在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) {
 ...