Dla mnie wydaje się, istnieją trzy różne potrzeby, które mają dynamiczne napięcie między sobą:
- wygody EXTENDER
RuntimeException
; tj. minimalny kod do zapisania w celu utworzenia potomka o RuntimeException
- Postrzegana przez klienta łatwość użycia; czyli minimalny kod do zapisania w call-site
preferencji
- Klienta w celu uniknięcia przecieki bał Java
null
do ich kodu
Jeśli ktoś nie dba o numer 3, a następnie this answer (peer to ten jeden) wydaje się dość zwięzły.
Jednakże, jeśli jedna wartość numer 3 podczas próby zbliżenia się do liczby 1 i 2, jak to możliwe, poniższe rozwiązanie skutecznie hermetyluje wyciek Java null
do interfejsu 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)
}
A jeśli chcesz wyeliminować konieczności korzystania new
gdzie MyRuntimeException
jest rzeczywiście używany, dodać ten przedmiot do towarzystwa (który tylko przekazuje wszystkich połączeń zastosowania do istniejącej „master” konstruktora klasy):
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
)
}
Osobiście wolę faktycznie tłumić użycie operatora new
w jak największej ilości kodu, aby ułatwić przyszłe refaktoryzacje.Jest to szczególnie pomocne, gdy wspomniane refaktoryzacje są silnie związane z wzorcem Fabryki. Mój końcowy wynik, choć bardziej szczegółowy, powinien być całkiem przyjemny dla klientów.
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
)
Exploring bardziej wyrafinowane RuntimeException wzoru:
to tylko mały krok od pierwotnego pytania pragnąć stworzyć ekosystem wyspecjalizowanych RuntimeException
s na opakowaniu lub API . Chodzi o zdefiniowanie "root" RuntimeException
, z którego można stworzyć nowy ekosystem określonych wyjątków potomnych. Dla mnie ważne było, aby korzystanie z catch
i match
było łatwiejsze do wykorzystania w przypadku określonych typów błędów.
Na przykład mam zdefiniowaną metodę validate
, która weryfikuje zestaw warunków przed zezwoleniem na utworzenie klasy przypadku. Każdy niepowodzenie generuje instancję RuntimeException
. A następnie lista RuntimeInstance
s są zwracane przez metodę. Daje to klientowi możliwość decydowania, w jaki sposób chciałby obsłużyć odpowiedź; throw
wyjątek zatrzymania listy, przeskanuj listę pod kątem czegoś konkretnego i po prostu przepchnij całą rzecz w górę łańcucha wywołań bez angażowania bardzo kosztownej komendy JVM throw
.
Ta szczególna przestrzeń problem ma trzy różne potomków RuntimeException
, jeden abstrakcyjne (FailedPrecondition
) i dwa betonowe (FailedPreconditionMustBeNonEmptyList
i FailedPreconditionsException
).
Pierwsza, FailedPrecondition
, jest potomkiem bezpośrednim do RuntimeException
, bardzo podobnym do MyRuntimeException
i jest abstrakcyjna (aby zapobiec bezpośrednim instancjom). FailedPrecondition
ma "cechę obiektu towarzyszącego", FailedPreconditionObject
, która działa jako fabryka instancji (tłumienie operatora 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
)
Drugi FailedPreconditionMustBeNonEmptyList
, jest pośrednim RuntimeException
potomek i bezpośrednią konkretna realizacja FailedPrecondition
. Definiuje zarówno obiekt towarzyszący, jak i klasę. Obiekt towarzyszący rozszerza cechę FailedPreconditionObject
. Klasa po prostu rozszerza klasę abstrakcyjną FailedPrecondition
i oznacza ją final
, aby zapobiec dalszym rozszerzeniom.
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
)
Trzeci FailedPreconditionsException
jest bezpośrednim potomkiem RuntimeException
który owija List
z FailedPrecondition
S i dynamicznie kieruje emitowanie wiadomości wyjątku.
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))
a potem przenosząc wszystko to razem jako całość i schludny rzeczy, I miejsce zarówno FailedPrecondition
i FailedPreconditionMustBeNonEmptyList
wewnątrz obiektu FailedPreconditionsException
.I to właśnie efekt końcowy wygląda tak:
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))
i to będzie wyglądać dla klienta do korzystania z wyżej kod, aby tworzyć własne wyprowadzenie wyjątek nazywany 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
)
i następnie zastosowanie tego wyjątku wygląda następująco:
throw FailedPreconditionMustBeNonEmptyString()
poszedłem daleko poza odpowiedzi na oryginalne pytanie, bo okazało się, że tak trudne do zlokalizowania anyth bliskość bycia specyficznym i wszechstronnym w Scala-ifying RuntimeException
w specyficznym lub rozszerzającym się do bardziej ogólnego "ekosystemu wyjątków", z którym tak dobrze się rozwijałem w Javie.
Chciałbym usłyszeć opinie (inne niż wariacje na temat "Wow! To zbyt szczegółowe dla mnie") w moim zestawie rozwiązań. I chciałbym, aby wszelkie dodatkowe optymalizacje lub sposoby zmniejszenia gadatliwości BEZ ZGUBIENIA żadnej z wartości lub zwięzłości generowałem dla klientów tego wzorca.
nie powinien wyjątek przedłużenia RuntimeException? – opensas
świetna odpowiedź, co więcej, jeśli zadeklarujesz to jako klasę case, możesz pozbyć się "nowego" ... – opensas
@opensas 'object MyException {def apply (wiadomość: String = null, powód: Throwable = null) = new MyException (message, cause)} 'is enough. – senia