Компилятор говорит, что ваш companion
определен над OutputMessage
как общий параметр, а не какой-то конкретный подтип. Чтобы обойти это, вы хотите использовать трюк, известный как F-bound generic . Также мне не нравится идея сохранения этого сопутствующего объекта как val
в каждом сообщении (ведь вы не хотите, чтобы оно сериализовалось, не так ли?). Определение его как def
ИМХО - намного лучший компромисс. Код будет выглядеть так (компаньоны останутся прежними):
abstract class OutputMessage[M <: OutputMessage[M]]() {
self: M => // required to match Json.toJson signature
protected def companion: OutputMessageCompanion[M]
def toJson: JsValue = Json.toJson(this)(companion.fmt)
}
case class NotifyTableChange(tableStatus: BizTable) extends OutputMessage[NotifyTableChange] {
override protected def companion: OutputMessageCompanion[NotifyTableChange] = NotifyTableChange
}
Вы также можете увидеть стандартные коллекции Scala для реализации того же подхода.
Но если все, что вам нужно для companion
- это кодирование в формате JSON, вы можете избавиться от него следующим образом:
abstract class OutputMessage[M <: OutputMessage[M]]() {
self: M => // required to match Json.toJson signature
implicit protected def fmt: OFormat[M]
def toJson: JsValue = Json.toJson(this)
}
case class NotifyTableChange(tableStatus: BizTable) extends OutputMessage[NotifyTableChange] {
override implicit protected def fmt: OFormat[NotifyTableChange] = Json.format[NotifyTableChange]
}
Очевидно, что вы также хотите декодировать из JSON, но вам все равно нужен объект-компаньон.
Ответы на комментарии
- Обращение к компаньону через def - означает, что это «метод», определенный таким образом один раз для всех экземпляров подтипа (и не сериализованный)?
Все, что вы объявляете с помощью val
, получает поле, хранящееся в объекте (экземпляре класса). По умолчанию сериализаторы пытаются сериализовать все поля. Обычно есть какой-то способ сказать, что некоторые поля следует игнорировать (например, некоторые @IgnoreAnnotation
). Также это означает, что у вас будет еще один указатель / ссылка в каждом объекте, который использует память без веской причины, это может или не может быть проблемой для вас. Объявляя его как def
, вы получаете метод, так что вы можете хранить только один объект в каком-то «статическом» месте, например, объект-компаньон, или каждый раз создавать его по требованию.
- Я немного новичок в Scala, и у меня появилась привычка помещать формат в объект-компаньон. Вы порекомендуете / обратитесь к какому-нибудь источнику, о том, как решить, где лучше всего разместить ваши методы?
Scala - необычный язык, и нет прямого отображения, охватывающего все варианты использования концепции object
на других языках. В качестве первого практического правила для object
есть два основных использования:
То, где вы будете использовать static
в других языках, то есть контейнер для статических методов, констант и статических переменных (хотя переменные не рекомендуется, особенно статические в Scala)
Реализация шаблона Singleton.
- Под f-bound generic - вы имеете в виду нижнюю границу M, являющуюся OutputMessage [M] (кстати, почему нормально использовать M дважды в одном и том же выражении?)
К сожалению, вики предоставляет только базовое описание. Вся идея F-ограниченного полиморфизма заключается в том, чтобы иметь возможность доступа к типу подкласса в типе базового класса каким-либо общим способом. Обычно ограничение A <: B
означает, что A
должен быть подтипом B
. Здесь с M <: OutputMessage[M]
это означает, что M
должен быть подтипом OutputMessage[M]
, который может быть легко удовлетворен только путем объявления дочернего класса (есть другие непростые способы удовлетворить это) как:
class Child extends OutputMessage[Child}
Такой прием позволяет использовать M
в качестве аргумента или типа результата в методах.
- Я немного озадачен собой ...
Наконец, бит self
- это еще один трюк, который необходим, потому что F-ограниченного полиморфизма было недостаточно в данном конкретном случае. Обычно он используется с trait
, когда черты используются как смесь . В таком случае вы можете захотеть ограничить, в какие классы можно смешивать черту. И в том же типе это позволяет вам использовать методы из этого базового типа в вашем миксине trait
.
Я бы сказал, что конкретное использование в моем ответе немного необычно, но имеет тот же двойной эффект:
При компиляции OutputMessage
компилятор может предположить, что тип также каким-то образом будет иметь тип M
(каким бы M
ни был)
При компиляции любого подтипа компилятор обеспечивает выполнение ограничения # 1.Например, он не позволит вам сделать
case class SomeChild(i: Int) extends OutputMessage[SomeChild]
// this will fail because passing SomeChild breaks the restriction of self:M
case class AnotherChild(i: Int) extends OutputMessage[SomeChild]
На самом деле, поскольку мне все равно пришлось использовать self:M
, вы, вероятно, можете удалить здесь ограниченную F часть, живя всего
abstract class OutputMessage[M]() {
self: M =>
...
}
но я бы остался с этим, чтобы лучше передать смысл.