Краткий способ принудительного внедрения factory в Scala

Предположим, у нас есть черта T. Каков наилучший способ добиться следующего:

  • Каждый, кто пишет реализацию T, должен быть вынужден предоставить возможность, позволяющую инициализировать T без параметров, т. е. нам, вероятно, придется принудительно реализовать реализацию настраиваемой фабрики.
  • Вся логика/данные, которые зависят только от фактических параметров инициализации (определенной реализации A или T), должны обрабатываться/храниться централизованно, но должны быть доступны как на фабрике, так и на A.

Самый простой/краткий способ, который я вижу для достижения этого (приблизительно), состоит в том, чтобы добавить трейт для фабрики и связать T с этой фабрикой:

trait T {
  val factory: TFactory
}
trait TFactory {
  def build(): T
  val description: String   // example for logic/data that only depend on the parameters
}

// example implementation:
class A(val factory: AFactory, paramA: Int, paramB: Int, paramC: Int) extends T

class AFactory(paramA: Int, paramB: Int, paramC: Int) extends TFactory {
  def build = new A(this, paramA, paramB, paramC)
  val description = f"$paramA $paramB $paramC"
}

Очевидно, что на самом деле это не «принуждает» реализацию фабрики (пока доступна альтернативная реализация), и, очевидно, можно генерировать экземпляры A, которые ссылаются на «неправильный» TFactory. Что мне также не нравится в этом подходе, так это повторение параметров инициализации. Я часто создаю еще один класс AParams, который снова оборачивает все параметры (например, чтобы облегчить добавление новых параметров). Таким образом, я получаю три класса, которые, по моему мнению, являются шаблонными для этой простой задачи.

Мой вопрос в том, существует ли (возможно, совершенно) другой подход, который достигает тех же основных целей, но является более кратким?


person bluenote10    schedule 12.12.2013    source источник


Ответы (2)


Я не совсем уверен, что полностью понимаю ваши требования, но что вы думаете об этом поведении?

trait TFactory{
    def build():T
    val description:String
}

trait T extends TFactory

//can't declare A without build and not make it abstract
class A(paramA: Int, paramB: Int, paramC: Int) extends T {
    def build = new A(paramA, paramB, paramC)
    val description = f"$paramA $paramB $paramC"    
}

val a1 = new A(1, 4, 5)
val a2 = a1.build()

//We can give ourselves as a factory to something that expects TFactory
val factory:TFactory = a1
val a_new = factory.build()

//More likely we can just give our build method
def func(f: ()=>T) = {
    val new_t = f()
    new_t
}
val a_newer = func(a1.build)


println(a1 +": " + a1.description)
println(a2 +": " + a2.description)
println(a_new +": " + a_new.description)
println(a_newer +": " + a_newer.description)

Выход:

Main$$anon$1$A@69267649: 1 4 5
Main$$anon$1$A@69b1fbf4: 1 4 5
Main$$anon$1$A@24148662: 1 4 5
Main$$anon$1$A@3f829e6f: 1 4 5
person Eugene Cheipesh    schedule 12.12.2013
comment
Определенно интересная идея, спасибо! Практическая проблема, которую я вижу, заключается в том, что интуитивно я потеряю представление о том, что фабрика легковесна, в то время как фактическая реализация T может быть довольно тяжеловесной. Часто создание реального A в моих случаях использования включает в себя большой объем инициализации, в результате чего экземпляр A требует значительного объема памяти. Я, вероятно, часто заканчивал бы экземпляром A, который я бы никогда не использовал в его реальном смысле быть T, а просто как фабрику с ненужными накладными расходами. Но, возможно, это плата за упрощение. - person bluenote10; 12.12.2013
comment
Похоже, вам нужно использовать параметры конструктора T в вашей фабрике, поэтому я не понимаю, как вы могли бы иметь такую ​​​​фабрику без экземпляра T. Нет, если, как вы говорите, вы не переносите параметры в класс который может быть предоставлен как фабрике, так и конструктору подкласса T. - person Eugene Cheipesh; 12.12.2013
comment
Говоря чисто с точки зрения дизайна, я не уверен, что вам нужно навязывать такую ​​фабрику, если это не единственный способ построить T. Предположительно, у вас есть какой-то метод, которому нужна фабрика, которая может быть просто функцией: MakeTsAndDoUsefulThings(factory: ()=>T). Затем в будущем, если я как клиент вашего кода сделаю SonOfT и обнаружу, что мне нужно использовать эту функцию, я реализую фабрику для SonOfT, возможно, в моем сопутствующем объекте и сделаю этот вызов: MakeTsAndDoUsefulThings(SonOfT.defaultFactory) Если мне никогда не понадобится использовать этот метод , мне никогда не понадобится создавать фабрику, что кажется нормальным. - person Eugene Cheipesh; 12.12.2013

Добавьте параметр типа представления:

trait Factory[Prod] {
  def build(): Prod
}

trait Prod[Repr] {
  def factory: Factory[Repr]
}

Или, если вы хотите «принудить», чтобы тип оставался прежним (я бы не стал этого делать, если вы не получите от этого что-то):

trait Prod[Repr <: Prod[Repr]] {
  def factory: Factory[Repr]
}

Затем:

case class AConfig(a: Int, b: Int)

case class A(config: AConfig) extends Prod[A] {
  def factory = AFactory(config)
}

case class AFactory(config: AConfig) extends Factory[A] {
  def build() = A(config)
}

val f0 = AFactory(AConfig(1, 2))
val p0 = f0.build()
val f1 = p0.factory
val p1 = f1.build()
assert(p0 == p1)
person 0__    schedule 13.12.2013