Принудительная реализация признака для обеспечения внутреннего класса, который может расширяться - PullRequest
0 голосов
/ 30 августа 2018

Возможно ли для какого-либо признака наложить требование, чтобы его реализующие классы реализовали некоторый внутренний класс, который он затем может расширить? Э.Г.

trait BaseTrait {
  // not actually an "abstract class", but a requirement that
  // subclasses provide a class named Foo with this constructor signature
  abstract class Foo(bar: Bar)

  def normalFoo(bar: Bar): Foo = new Foo(bar)

  // trait needs to be able to extend the Foo class implemented by the subclass.
  // this seems to be the impossible part, as far as I can tell...
  def fancyFoo(bar: Bar): Foo with SomeMixin = new Foo(bar) with SomeMixin {
    def anExtraMethod() = println("I'm an extra!")
  }
}

object ThingA extends BaseTrait {
  class Foo(bar: Bar) {
    def getThingAStuff() = println("I'm part of ThingA")
  }
}
object ThingB extends BaseTrait {
  class Foo(bar: Bar) {
    def getThingBStuff() = println("I'm part of ThingB")
  }
}

// calling `fancyFoo` on the concrete implementations should grant
// access to the specific methods in their respective `Foo` classes,
// as well as the "extra method" that the trait adds
val aFoo: ThingA.Foo with SomeMixin = ThingA.fancyFoo(bar)
aFoo.getThingAStuff()
aFoo.anExtraMethod()

val bFoo: ThingB.Foo with SomeMixin = ThingB.fancyFoo(bar)
bFoo.getThingBStuff()
bFoo.anExtraMethod()

Причина, по которой я хочу это, заключается в том, что у меня есть большое количество ThingX классов, которые в настоящее время вынуждены реализовывать свой собственный эквивалент fancyFoo (и другие подобные методы, которые требуют добавления Mixin в свои определенный класс Foo). Я хочу сократить шаблон, переместив fancyFoo и его друзей в BaseTrait, но я не смог придумать ничего менее многословного, чем то, что уже есть.


редактирование:

Мое обобщение, приведенное выше, могло скрыть общее намерение, так что вот некоторые предпосылки:

Мой реальный пример использования вращается вокруг моделирования схемы базы данных и некоторой логики объединения таблиц. Команда начала отходить от «поднятого» синтаксиса Slick и больше к raw sql, и эта система появилась, чтобы помочь в написании необработанных запросов.

Foo = TableReference. Каждый из объектов ThingX представляет конкретную таблицу, а их соответствующие ссылочные классы содержат методы для ссылки на столбцы этой таблицы.

SomeMixin = TableJoin, который должен был добавить логику соединения (то есть, как добраться до одной таблицы из другой таблицы). Объекты ThingX обычно определяют def direct для получения прямой ссылки на таблицу (т. Е. Начало предложения FROM в запросе SQL), def from(someOtherRef), который создает INNER JOIN, и def optFrom(someOtherRef), который создает LEFT JOIN. Эти три метода - это то, что я пытался абстрагировать в BaseTrait.

Я полагаю, что do нужно иметь возможность предоставить простой TableReference, а также TableReference with TableJoin, поскольку у нас есть утилита для объединения всей логики соединения, и мы хотим запретить ссылки без какой-либо логики соединения передаются в него. В базе кода есть несколько простых ссылок.

Я надеюсь определить что-то вроде

trait TableSupport {
  type Reference <: TableReference
  trait CanMatch[Ref] {
    // corresponds to the `ON` part of a `JOIN` clause
    def matchCondition(self: Reference, other: Ref): RawSQL
  }
  def defaultAlias: String

  // All of the below would be implemented by the `TableSupport` trait
  // in terms of the `Reference` constructor and mixing in TableJoin.
  // But currently each table companion has to explicitly implement these.

  def reference(alias: String = defaultAlias): Reference = ???
  def direct(alias: String = defaultAlias): Reference with TableJoin = ???
  def from[Ref: CanMatch](ref: Ref, alias: String = defaultAlias): Reference with TableJoin = ???
  def optFrom[Ref: CanMatch](ref: Ref, alias: String = defaultAlias): Reference with TableJoin = ???
}

Я застрял в последних четырех методах, описанных выше, поскольку кажется, что они требуют, казалось бы, несуществующей возможности, о которой я спрашиваю в моем исходном вопросе, или для TableSupport разработчиков, чтобы явно определить отдельные методы для создания Reference и Reference with TableJoin, что в конечном итоге сводит на нет цель сокращения шаблонов из-за дополнительного шаблона внедрения этих методов.

1 Ответ

0 голосов
/ 11 сентября 2018

Решение, которое я нашел, состояло в том, чтобы обернуть классы, а не расширять их, и использовать неявную распаковку, чтобы позволить мне взаимодействовать с вещами так же, как если бы они были расширены .

SomeTableRef with TableJoin становится TableJoin[SomeTableRef], т.е.

class TableJoin[T <: TableReference](val self: T, val joinStep: RawSQL)
object TableJoin {
  import language.implicitConversions
  implicit def unwrap[T <: TableReference](tj: TableJoin[T]): T = tj.self
}

Поскольку компилятор может найти метод unwrap для TableJoin [T] без какого-либо импорта, я могу рассматривать его так же, как если бы это был миксин:

class SomeTableRef(alias: String) extends TableReference {
  def id = column("ID")
}
val joinedRef = new TableJoin(new SomeTableRef(defaultAlias), /* raw sql */)
joinedRef.id // compiles fine because of `unwrap`

Используя этот подход, я смог реализовать методы direct, from и optFrom, как я и надеялся.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...