Cómo evitar el tipo de datos de la subclase al utilizar el patrón del objeto nulo.
Tengo una interfaz Value con un método para mostrar el valor como una cadena.
Normalmente, el valor es un número entero, por lo que IntegerValue implementa Value.
A veces, el valor es desconocido, para lo cual utilizo el patrón de objeto nulo, por lo que UnknownValue implementa Value.
Cuando el valor es realmente un número entero, es útil para el cliente verificar si el valor es lo suficientemente alto (IntegerValue.isEnough). Esto afecta cómo se muestra este valor al usuario más adelante. Sin embargo, si el valor es desconocido, no tiene sentido verificar si es lo suficientemente alto; el valor es desconocido. Según el Principio de Segregación de Interfaces, UnknownValue no debería tener un método isEnough.
interface Value {
toString(): string;
}
class IntegerValue implements Value {
private value: number;
constructor(v: number) { this.value = v }
isEnough() { return this.value >= 30 }
toString() { return '' + this.value }
}
class UnknownValue implements Value {
toString() { return 'desconocido' }
}
Pero el cliente accede a un Value y no sabrá si es un IntegerValue. Así que tendría que comprobarlo y luego convertirlo al tipo correcto.
if(value.toString() !== 'desconocido') {
handleInteger(value as IntegerValue) // <-- comprobar si isEnough dentro
} else {
handleUnknown(value)
}
Me preguntaba si había un patrón de diseño que pudiera resolver esto con polimorfismo, sin necesidad de conversión de tipos.
Estaba considerando el patrón de Visitor de la siguiente manera:
interface ValueVisitor {
handleInteger(v: IntegerValue): void;
handleUnknown(): void
}
class ViewValueVisitor implements ValueVisitor { ... }
class JsonSerializerValueVisitor implements ValueVisitor { ... }
interface Value {
toString(): string;
acceptVisitor(v: ValueVisitor): void;
}
class IntegerValue implements Value {
...
acceptVisitor(v) { v.handleInteger(this) }
}
class UnknownValue implements Value {
...
acceptVisitor(v) { v.handleUnknown() }
}
Pero el patrón de Visitor viola el Principio de Abierto/Cerrado. Me preguntaba si hay una mejor solución.
davy.ai
Una posible solución sería utilizar el patrón de objeto nulo (Null Object Pattern) también para el método
isEnough
. En otras palabras, paraUnknownValue
,isEnough
siempre devolvería falso. En lugar de comprobar el tipo deValue
y realizar una conversión de tipos, el cliente simplemente puede llamar aisEnough
enValue
y, si devuelve falso, manejarlo como un valor desconocido. De esta manera, la interfazValue
se mantiene intacta y no viola el Principio de Segregación de Interfaces.Entonces, el código del cliente se vería así:
De esta manera, el cliente no necesita conocer el tipo de
Value
y simplemente puede llamar aisEnough
. El comportamiento deUnknownValue.isEnough
está determinado por la clase y, por lo tanto, cada clase puede ser responsable de su propio comportamiento. Esta solución también no viola ni el Principio de Segregación de Interfaces ni el Principio Abierto-Cerrado.