2013-12-05 2 views
6

Мы перерабатываем унаследованный method, чтобы вместо этого использовать класс типа - мы хотели бы сконцентрировать все реализации method в одном месте, поскольку их разброс между классами реализации затрудняет техническое обслуживание. Однако мы сталкиваемся с некоторыми проблемами, так как мы довольно новы, чтобы вводить классы. В настоящее время method определяется какПолиморфизм с классами типа Scala

trait MethodTrait { 
    def method: Map[String, Any] = // default implementation 
} 

abstract class SuperClass extends MethodTrait { 
    override def method = super.method ++ // SuperClass implementation 
} 

class Clazz extends SuperClass { 
    override def method = super.method ++ // Clazz implementation 
} 

и так далее, где есть в общей сложности 50 + конкретных классов, иерархия неглубокий (abstract class SuperClass ->abstract class SubSuperClass ->abstract class SubSubSuperClass ->class ConcreteClass это так глубоко, как он идет), и конкретный класс никогда не расширяет другой конкретный класс. (В фактической реализации, method возвращает Play Framework JsObject вместо Map[String, Any].) Мы пытаемся заменить это с классом типа:

trait MethodTrait[T] { 
    def method(target: T): Map[String, Any] 
} 

class MethodType { 
    type M[T] = MethodTrait[T] 
} 

implicit object Clazz1Method extends MethodTrait[Clazz1] { 
    def method(target: Clazz1): Map[String, Any] { ... } 
} 

implicit object Clazz2Method extends MethodTrait[Clazz2] { 
    def method(target: Clazz2): Map[String, Any] { ... } 
} 

// and so on 

Я бегу на две проблемы:

A. Подражание функциональности super.method ++ из предыдущей реализации. В настоящее время я использую

class Clazz1 extends SuperClass 

class Clazz2 extends SubSuperClass 

private def superClassMethod(s: SuperClass): Map[String, Any] = { ... } 

private def subSuperClassMethod(s: SubSuperClass): Map[String, Any] = { 
    superClassMethod(s) ++ ... 
} 

implicit object Clazz1Method extends MethodTrait[Clazz1] { 
    def method(target: Clazz1): Map[String, Any] = { 
    superClassMethod(target) ++ ... 
    } 
} 

implicit object Clazz2Method extends MethodTrait[Clazz2] { 
    def method(target: Clazz2): Map[String, Any] = { 
    subSuperClassMethod(target) ++ ... 
    } 
} 

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

B. Вызов method на суперкласс, например.

val s: SuperClass = new Clazz1() 
s.method 

В идеале я хотел бы быть в состоянии сказать компилятору, что каждый подкласс SuperClass имеет соответствующий неявный объект для method в классе типа и так s.method является типобезопасным (или я получу компиляцию ошибка времени, если я забыл реализовать соответствующий неявный объект для подкласса SuperClass), но вместо того, чтобы все, что я смог придумать это

implicit object SuperClassMethod extends MethodTrait[SuperClass] { 
    def method(target: SuperClass): Map[String, Any] = { 
    target match { 
     case c: Clazz1 => c.method 
     case c: Clazz2 => c.method 
     ... 
    } 
    } 
} 

который некрасиво и не даст мне предупреждение или ошибка компиляции, если я опустил класс, так как не могу определить SuperClass как запечатанная черта.


Мы были бы открыты для альтернатив типа классов, которые позволили бы нам сконцентрировать method код в одном месте. method только вызываются из двух мест:

А. Других method реализаций, например Clazz1 имеет val clazz2: Option[Clazz2], в этом случае method реализация в Clazz1 будет что-то вроде

def method = super.method ++ /* Clazz1 method implementation */ ++ 
    clazz2.map(_.method).getOrElse(Map()) 

B. Верхнего уровня Контроллер Play Framework (т.е. абстрактный класс, из которого наследуются все контроллеры), где мы определили три ActionBuilders, которые вызывают method, например

def MethodAction[T <: MethodTrait](block: Request[AnyContent] => T) = { 
    val f: Request[AnyContent] => SimpleResult = 
    (req: Request[AnyContent]) => Ok(block(req).method) 

    MethodActionBuilder.apply(f) 
} 

ответ

6

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

С помощью этого рефакторинга, вы просто создаете опасность неправильного экземпляра, который выбрал:

trait Foo 
case class Bar() extends Foo 

trait HasBaz[A] { def baz: Set[Any] } 

implicit object FooHasBaz extends HasBaz[Foo] { def baz = Set("foo") } 
implicit object BarHasBaz extends HasBaz[Bar] { def baz = FooHasBaz.baz + "bar" } 

def test[A <: Foo](x: A)(implicit hb: HasBaz[A]): Set[Any] = hb.baz 

val bar: Foo = Bar() 
test(bar) // boom! 

Таким образом, вы в конечном итоге переписывания полиморфной отправки с вашим рисунком согласованью в SuperClassMethod. Вы в основном идете OO -> FP -> OO, в то же время предоставляя идею классов типов непригодными (чтобы быть открытыми), заканчивая скорее типом суммы (все известные подтипы).

+0

Я бы пришел к аналогичному выводу, но по другой причине. Но в любом случае, пока вы описали проблему, не могли бы вы также предложить решение? Мне интересно о ваших мыслях. – Vidya

2

@ 0__ находится на чем-то - неявное разрешение возникает при компиляции, поэтому экземпляр класса типа, который используется для данного ввода, будет не зависит от типа времени выполнения этого ввода.

Чтобы получить нужное поведение, вам нужно будет написать неявное определение, которое будет отражать фактический тип объекта, на который вы хотите позвонить method, чтобы выбрать правильный экземпляр typeclass.

Я думаю, что это скорее проблема обслуживания, чем то, что у вас есть прямо сейчас.

2

Проще говоря: если вы хотите иметь свою реализацию в одном месте, вы должны использовать классы случая для вашей иерархии:

abstract class SuperClass; 

case class Clazz(...) extends SuperClass; 

def method(o : SuperClass) : Map[String, Any] = o match { 
    case Clazz(...) => defaultMethod ++ ... 
    case ... 
} 

(Обратите внимание, что метод, конечно, может быть рекурсивным) Так как вы можете иметь open Sum Тип в scala (компилятор не будет предупреждать о недостающих шаблонах, хотя), что должно решить вашу проблему без необходимости злоупотреблять классными категориями.

Смежные вопросы