Если вы хотите определить свои собственные расширения, вам просто нужно объявить исключения потомков и реализовать каждый желаемый конструктор, вызывающий супер супер конструктор
Ответ 4
Для меня, кажется, существуют три разные потребности, которые имеют динамическое напряжение друг с другом:
- Удобство расширителя
RuntimeException
; то есть минимальный код, который должен быть записан для создания потомка RuntimeException
- Клиент воспринимал легкость использования; т.е. минимальный код, который должен быть записан на сайте вызова
- Предпочтение клиента избегать утечки страшного Java
null
в свой код
Если вам неинтересно номер 3, то этот ответ (одноранговый для этого) выглядит довольно кратким.
Однако, если один из значений числа 3 пытается как можно ближе приблизиться к номерам 1 и 2, решение ниже эффективно инкапсулирует утечку Java null
в ваш API Scala.
class MyRuntimeException (
val optionMessage: Option[String],
val optionCause: Option[Throwable],
val isEnableSuppression: Boolean,
val isWritableStackTrace: Boolean
) extends RuntimeException(
optionMessage match {
case Some(string) => string
case None => null
},
optionCause match {
case Some(throwable) => throwable
case None => null
},
isEnableSuppression,
isWritableStackTrace
) {
def this() =
this(None, None, false, false)
def this(message: String) =
this(Some(message), None, false, false)
def this(cause: Throwable) =
this(None, Some(cause), false, false)
def this(message: String, cause: Throwable) =
this(Some(message), Some(cause), false, false)
}
И если вы хотите исключить необходимость использования new
, где фактически используется MyRuntimeException
, добавьте этот объект-компаньон (который просто переадресует все вызовы приложения к существующему конструктору класса master):
object MyRuntimeException {
def apply: MyRuntimeException =
MyRuntimeException()
def apply(message: String): MyRuntimeException =
MyRuntimeException(optionMessage = Some(message))
def apply(cause: Throwable): MyRuntimeException =
MyRuntimeException(optionCause = Some(cause))
def apply(message: String, cause: Throwable): MyRuntimeException =
MyRuntimeException(optionMessage = Some(message), optionCause = Some(cause))
def apply(
optionMessage: Option[String] = None,
optionCause: Option[Throwable] = None,
isEnableSuppression: Boolean = false,
isWritableStackTrace: Boolean = false
): MyRuntimeException =
new MyRuntimeException(
optionMessage,
optionCause,
isEnableSuppression,
isWritableStackTrace
)
}
Лично я предпочитаю фактически подавлять использование оператора new
в максимально возможном количестве кода, чтобы облегчить возможные будущие рефакторинги. Это особенно полезно, если рефакторинг сильно зависит от шаблона Factory. Мой конечный результат, хотя и более подробный, должен быть очень приятным для клиентов.
object MyRuntimeException {
def apply: MyRuntimeException =
MyRuntimeException()
def apply(message: String): MyRuntimeException =
MyRuntimeException(optionMessage = Some(message))
def apply(cause: Throwable): MyRuntimeException =
MyRuntimeException(optionCause = Some(cause))
def apply(message: String, cause: Throwable): MyRuntimeException =
MyRuntimeException(optionMessage = Some(message), optionCause = Some(cause))
def apply(
optionMessage: Option[String] = None,
optionCause: Option[Throwable] = None,
isEnableSuppression: Boolean = false,
isWritableStackTrace: Boolean = false
): MyRuntimeException =
new MyRuntimeException(
optionMessage,
optionCause,
isEnableSuppression,
isWritableStackTrace
)
}
class MyRuntimeException private[MyRuntimeException] (
val optionMessage: Option[String],
val optionCause: Option[Throwable],
val isEnableSuppression: Boolean,
val isWritableStackTrace: Boolean
) extends RuntimeException(
optionMessage match {
case Some(string) => string
case None => null
},
optionCause match {
case Some(throwable) => throwable
case None => null
},
isEnableSuppression,
isWritableStackTrace
)
Изучение более сложного шаблона RuntimeException:
Это лишь небольшой скачок от исходного вопроса к желанию создать экосистему специализированного RuntimeException
для пакета или API. Идея состоит в том, чтобы определить "корень" RuntimeException
, из которого может быть создана новая экосистема конкретных исключений потомков. Для меня было важно сделать использование catch
и match
намного проще для использования для определенных типов ошибок.
Например, у меня установлен метод validate
, который проверяет набор условий до того, как разрешить создание класса case. Каждое неработающее условие генерирует экземпляр RuntimeException
. А затем список RuntimeInstance
возвращается методом. Это дает клиенту возможность решить, как они хотели бы реагировать; throw
исключение списка исключений, сканирование списка для чего-то определенного и throw
, или просто нажимать всю цепочку вызовов без привлечения очень дорогой команды JVM throw
.
Это конкретное проблемное пространство имеет три разных потомка RuntimeException
, один абстрактный (FailedPrecondition
) и два конкретных (FailedPreconditionMustBeNonEmptyList
и FailedPreconditionsException
).
Первый, FailedPrecondition
, является прямым потомком до RuntimeException
, очень похож на MyRuntimeException
, и является абстрактным (для предотвращения прямых экземпляров). FailedPrecondition
имеет "признак сопутствующих объектов", FailedPreconditionObject
, который действует как экземпляр Factory (подавляет оператор new
).
trait FailedPreconditionObject[F <: FailedPrecondition] {
def apply: F =
apply()
def apply(message: String): F =
apply(optionMessage = Some(message))
def apply(cause: Throwable): F =
apply(optionCause = Some(cause))
def apply(message: String, cause: Throwable): F =
apply(optionMessage = Some(message), optionCause = Some(cause))
def apply(
optionMessage: Option[String] = None
, optionCause: Option[Throwable] = None
, isEnableSuppression: Boolean = false
, isWritableStackTrace: Boolean = false
): F
}
abstract class FailedPrecondition (
val optionMessage: Option[String],
val optionCause: Option[Throwable],
val isEnableSuppression: Boolean,
val isWritableStackTrace: Boolean
) extends RuntimeException(
optionMessage match {
case Some(string) => string
case None => null
},
optionCause match {
case Some(throwable) => throwable
case None => null
},
isEnableSuppression,
isWritableStackTrace
)
Второй, FailedPreconditionMustBeNonEmptyList
, является косвенным потомком RuntimeException
и прямой конкретной реализацией FailedPrecondition
. Он определяет как объект-компаньон, так и класс. Сопутствующий объект расширяет черту FailedPreconditionObject
. И класс просто расширяет абстрактный класс FailedPrecondition
и отмечает его final
, чтобы предотвратить дальнейшие расширения.
object FailedPreconditionMustBeNonEmptyList extends FailedPreconditionObject[FailedPreconditionMustBeNonEmptyList] {
def apply(
optionMessage: Option[String] = None
, optionCause: Option[Throwable] = None
, isEnableSuppression: Boolean = false
, isWritableStackTrace: Boolean = false
): FailedPreconditionMustBeNonEmptyList =
new FailedPreconditionMustBeNonEmptyList(
optionMessage
, optionCause
, isEnableSuppression
, isWritableStackTrace
)
}
final class FailedPreconditionMustBeNonEmptyList private[FailedPreconditionMustBeNonEmptyList] (
optionMessage: Option[String]
, optionCause: Option[Throwable]
, isEnableSuppression: Boolean
, isWritableStackTrace: Boolean
) extends
FailedPrecondition(
optionMessage
, optionCause
, isEnableSuppression
, isWritableStackTrace
)
Третий, FailedPreconditionsException
, является прямым потомком до RuntimeException
, который обертывает List
из FailedPrecondition
, а затем динамически управляет испусканием сообщения об исключении.
object FailedPreconditionsException {
def apply(failedPrecondition: FailedPrecondition): FailedPreconditionsException =
FailedPreconditionsException(List(failedPrecondition))
def apply(failedPreconditions: List[FailedPrecondition]): FailedPreconditionsException =
tryApply(failedPreconditions).get
def tryApply(failedPrecondition: FailedPrecondition): Try[FailedPreconditionsException] =
tryApply(List(failedPrecondition))
def tryApply(failedPreconditions: List[FailedPrecondition]): Try[FailedPreconditionsException] =
if (failedPreconditions.nonEmpty)
Success(new FailedPreconditionsException(failedPreconditions))
else
Failure(FailedPreconditionMustBeNonEmptyList())
private def composeMessage(failedPreconditions: List[FailedPrecondition]): String =
if (failedPreconditions.size > 1)
s"failed preconditions [${failedPreconditions.size}] have occurred - ${failedPreconditions.map(_.optionMessage.getOrElse("")).mkString("|")}"
else
s"failed precondition has occurred - ${failedPreconditions.head.optionMessage.getOrElse("")}"
}
final class FailedPreconditionsException private[FailedPreconditionsException] (
val failedPreconditions: List[FailedPrecondition]
) extends RuntimeException(FailedPreconditionsException.composeMessage(failedPreconditions))
И затем, объединяя все это вместе и аккуратно, я помещаю как FailedPrecondition
, так и FailedPreconditionMustBeNonEmptyList
в объект FailedPreconditionsException
. И вот как выглядит конечный результат:
object FailedPreconditionsException {
trait FailedPreconditionObject[F <: FailedPrecondition] {
def apply: F =
apply()
def apply(message: String): F =
apply(optionMessage = Some(message))
def apply(cause: Throwable): F =
apply(optionCause = Some(cause))
def apply(message: String, cause: Throwable): F =
apply(optionMessage = Some(message), optionCause = Some(cause))
def apply(
optionMessage: Option[String] = None
, optionCause: Option[Throwable] = None
, isEnableSuppression: Boolean = false
, isWritableStackTrace: Boolean = false
): F
}
abstract class FailedPrecondition (
val optionMessage: Option[String]
, val optionCause: Option[Throwable]
, val isEnableSuppression: Boolean
, val isWritableStackTrace: Boolean
) extends RuntimeException(
optionMessage match {
case Some(string) => string
case None => null
},
optionCause match {
case Some(throwable) => throwable
case None => null
},
isEnableSuppression,
isWritableStackTrace
)
object FailedPreconditionMustBeNonEmptyList extends FailedPreconditionObject[FailedPreconditionMustBeNonEmptyList] {
def apply(
optionMessage: Option[String] = None
, optionCause: Option[Throwable] = None
, isEnableSuppression: Boolean = false
, isWritableStackTrace: Boolean = false
): FailedPreconditionMustBeNonEmptyList =
new FailedPreconditionMustBeNonEmptyList(
optionMessage
, optionCause
, isEnableSuppression
, isWritableStackTrace
)
}
final class FailedPreconditionMustBeNonEmptyList private[FailedPreconditionMustBeNonEmptyList] (
optionMessage: Option[String]
, optionCause: Option[Throwable]
, isEnableSuppression: Boolean
, isWritableStackTrace: Boolean
) extends
FailedPrecondition(
optionMessage
, optionCause
, isEnableSuppression
, isWritableStackTrace
)
def apply(failedPrecondition: FailedPrecondition): FailedPreconditionsException =
FailedPreconditionsException(List(failedPrecondition))
def apply(failedPreconditions: List[FailedPrecondition]): FailedPreconditionsException =
tryApply(failedPreconditions).get
def tryApply(failedPrecondition: FailedPrecondition): Try[FailedPreconditionsException] =
tryApply(List(failedPrecondition))
def tryApply(failedPreconditions: List[FailedPrecondition]): Try[FailedPreconditionsException] =
if (failedPreconditions.nonEmpty)
Success(new FailedPreconditionsException(failedPreconditions))
else
Failure(FailedPreconditionMustBeNonEmptyList())
private def composeMessage(failedPreconditions: List[FailedPrecondition]): String =
if (failedPreconditions.size > 1)
s"failed preconditions [${failedPreconditions.size}] have occurred - ${failedPreconditions.map(_.optionMessage.getOrElse("")).mkString("|")}"
else
s"failed precondition has occurred - ${failedPreconditions.head.optionMessage.getOrElse("")}"
}
final class FailedPreconditionsException private[FailedPreconditionsException] (
val failedPreconditions: List[FailedPreconditionsException.FailedPrecondition]
) extends RuntimeException(FailedPreconditionsException.composeMessage(failedPreconditions))
И это будет выглядеть так, как бы клиент использовал вышеуказанный код для создания собственного вывода исключений, называемого FailedPreconditionMustBeNonEmptyString
:
object FailedPreconditionMustBeNonEmptyString extends FailedPreconditionObject[FailedPreconditionMustBeNonEmptyString] {
def apply(
optionMessage: Option[String] = None
, optionCause: Option[Throwable] = None
, isEnableSuppression: Boolean = false
, isWritableStackTrace: Boolean = false
): FailedPreconditionMustBeNonEmptyString =
new FailedPreconditionMustBeNonEmptyString(
optionMessage
, optionCause
, isEnableSuppression
, isWritableStackTrace
)
}
final class FailedPreconditionMustBeNonEmptyString private[FailedPreconditionMustBeNonEmptyString] (
optionMessage: Option[String]
, optionCause: Option[Throwable]
, isEnableSuppression: Boolean
, isWritableStackTrace: Boolean
) extends
FailedPrecondition(
optionMessage
, optionCause
, isEnableSuppression
, isWritableStackTrace
)
И тогда использование этого исключения выглядит следующим образом:
throw FailedPreconditionMustBeNonEmptyString()
Я вышел далеко за пределы исходного вопроса, потому что мне было трудно найти что-то близкое к тому, чтобы быть как конкретным, так и всеобъемлющим в Scala -ifying RuntimeException
в конкретном или расширяться в более общую "экосистему исключений", с которой Я стал таким комфортным, когда на Java.
Мне бы хотелось услышать обратную связь (кроме вариаций, "Вау! Это слишком много для меня".) на моем решении. И мне бы хотелось, чтобы какие-либо дополнительные оптимизации или способы уменьшить многословие БЕЗ УДАЛЕНИЯ любого из значений или терпения, которые я создал для клиентов этого шаблона.