es.davy.ai

Preguntas y respuestas de programación confiables

¿Tienes una pregunta?

Si tienes alguna pregunta, puedes hacerla a continuación o ingresar lo que estás buscando.

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.

Tags:  , , , ,

Answer

  1. Avatar for 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, para UnknownValue, isEnough siempre devolvería falso. En lugar de comprobar el tipo de Value y realizar una conversión de tipos, el cliente simplemente puede llamar a isEnough en Value y, si devuelve falso, manejarlo como un valor desconocido. De esta manera, la interfaz Value se mantiene intacta y no viola el Principio de Segregación de Interfaces.

    interface Value {
      toString(): string;
      isEnough(): boolean;  // añadido a la interfaz
    }
    
    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 {
      isEnough() { return false }  // siempre devuelve falso
      toString() { return 'desconocido' }
    }
    

    Entonces, el código del cliente se vería así:

    if (value.isEnough()) {
      manejarEntero(value)
    } else {
      manejarDesconocido(value)
    }
    

    De esta manera, el cliente no necesita conocer el tipo de Value y simplemente puede llamar a isEnough. El comportamiento de UnknownValue.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.

Comments are closed.