Вы можете агрегировать все параметры конструктора в один класс case, а затем передать этот класс case в конструктор. Например:
case class ErrorData(message:String, publicMessage:String, objectData:Option[Object], backtrace:Exception)
abstract class ErrorMessages(data:ErrorData)
case class MyError(data:ErrorData) extends ErrorMessages(data)
Это, вероятно, нуждается в некоторой доработке. Например, вы можете захотеть сделать компоненты ErrorData
доступны через ErrorMessages
, а также:
abstract class ErrorMessages(data:ErrorData) {
def message:String = data.message
def publicMessage:String = data.publicMessage
// ...
}
Таким образом, MyError
также наследует методы, позволяющие прямой доступ к message
, publicMessage
, objectData
и т.д. Эффективно, то существование ErrorData
скрыто во внешнем мире. Это всего лишь утилита для создания экземпляров подтипов ErrorMessages
. Это, однако, также потребует от вас повторного введения модификаторов override
из вашего примера.
Вот альтернативный дизайн:
Может быть, вы также хотите, чтобы пропустить конструктор вообще. Вы можете сделать это, сделав компоненты анкеты ErrorMessages
. Пример:
abstract class ErrorMessages { // could also be a trait now
def message:String
def publicMessage:String
def objectData:Option[Object]
// ...
}
В этом случае, вы могли бы реализовать свой случай класс MyError
так же, как в вашем примере, за исключением override
модификаторов. Это полностью обходило бы прохождение параметров конструктора и в качестве дополнительного бонуса оставалось открытым для разработчиков, как он хочет реализовать эти абстрактные методы. (Например, у вас может быть реализация подтипа, общедоступное сообщение которой всегда совпадает с сообщением.)
Наконец, вы можете быть творческим и объединить эти стратегии. Возможно, вы не хотите интегрировать все параметры конструктора в один класс большого класса, но только некоторые из них, которые подходят друг к другу тематически. Когда у вас много параметров конструктора, иногда это может быть признаком того, что вам нужны более интегративные типы данных. Пример:
case class Message(internalText:String, publicText:String)
case class ErrorOrigin(objectData:Option[Object], backtrace:Exception)
trait ErrorMessages { // you can use the constructor approach or abstract members, as shown here
def message:Message
def origin:ErrorOrigin
def internalMessageText = message internalText
def publicMessageText = message publicText
def objectData = origin objectData
def backtrace = origin backTrace
}
case class MyErrorMessage(message:Message, origin:ErrorOrigin) extends ErrorMessages
Или вы можете смешать оба пути вверх:
abstract class ErrorMessages(val origin:ErrorOrigin) {
def message:Message
def internalMessageText = message internalText
// ...
}
case class MyErrorMessage(message:Message, override val origin:ErrorOrigin)
Не уверен, если это разделение хорошо для вас, или если эти имена типов и члены имеют смысл в вашем случае. Но я надеюсь, что вы видите картину: есть некоторые заметные альтернативы, вы можете поиграть с ними и выбрать то, что подходит лучше всего.
Однако нельзя исключить передачу параметров наследуемого конструктора. Было бы неплохо иметь литерал ...
, где компилятор Scala будет соответствовать всем параметрам конструктора суперпотока, которые имеют те же имена, что и в подтипе, но такой функции не существует.
Изменить 'abstract class ErrorMessages' на' trait ErrorMessages', а затем вызвать 'case class MyError (message: String, ...) extends ErrorMessages' –