Видение приемлемого решения позволяет перенести функциональность предварительной обработки с экземпляров на связанный объект. Главная оставшаяся трудность заключается в том, что вы хотите иметь возможность абстрагироваться над арностью и типами (т. Е. Формой) вашего класса case. конструкторы. Это возможно с помощью реализации HList
и значений полиморфной функции от shapeless .
Первые несколько предварительных,
import shapeless.HList._
import shapeless.Functions._
import shapeless.Poly._
import shapeless.TypeOperators._
// Implementation of your Input wrapper
case class Input[T](value: T)
// Value extractor as a shapeless polymorphic function value
object value extends (Input ~> Id) {
def default[T](i : Input[T]) = i.value
}
Теперь мы можем определить базовый класс препроцессора, который предоставляет метод apply, который принимает HList
из Input
типов, отображает полиморфную функцию value
на него (т. Е. Выполняет предварительную обработку) и затем передает полученный HList
не Input
типов к предоставленному конструктору класса дел (который приведен в виде списка, см. Ниже),
// Pre-processer base class
abstract class Preprocessor[In <: HList, Out <: HList, R](ctor : Out => R)
(implicit mapper : MapperAux[value.type, In, Out]) {
def apply(in : In) = ctor(in map value)
}
Теперь мы определим класс case с типами компонентов постобработки,
case class Foo(input1 : Int, input2 : String)
и добавить одну строку шаблона,
object FooBuilder extends Preprocessor((Foo.apply _).hlisted)
(здесь метод фабрики сопутствующих объектов Foo предоставляется в качестве аргумента конструктора Preprocessor
в форме HListed, как требуется выше.)
Теперь мы можем создавать Foo
экземпляров, используя FooBuilder
.
val foo = FooBuilder(Input(23) :: Input("foo") :: HNil)
К сожалению, (в настоящее время) невозможно объединить объект FooBuilder
с объектом-компаньоном Foo
: если вы попытаетесь использовать расширение Foo
Preprocessor
, вы обнаружите, что Foo
фабричный метод недоступен для передачи в качестве аргумента конструктора Preprocessor
.
Чтобы проиллюстрировать, что это решение действительно абстрагируется от типа и арности, вот как мы можем добавить второй класс case различной формы,
case class Bar(input1 : Int, input2 : String, input3 : Boolean)
object BarBuilder extends Preprocessor((Bar.apply _).hlisted)
val bar = BarBuilder(Input(23) :: Input("foo") :: Input(true) :: HNil)