Pregunta

Estoy tratando de comparar compareCriteria. Los sencillos como 'between' y 'inArray' o 'greaterThan'. Yo uso el polimorfismo para estas clases. Un método que comparten desde la interfaz compareCriteria es 'matchCompareCriteria'.

Lo que estoy tratando de evitar es hacer que cada clase verifique el tipo de compareCriteria con el que deben coincidir. Por ejemplo, el objeto inArray verificará si matchCompareCriteria recibe un objeto inArray, de lo contrario, devolverá false, en caso de que sepa cómo comparar.

Tal vez instanceof es perfectamente legítimo en este caso (los objetos se conocen a sí mismos) pero aún así estoy buscando posibles formas de evitarlo. ¿Alguna idea?

Ejemplo de pseudocódigo:

betweenXandY = create new between class(x, y)
greaterThanZ = create new greaterThan class(z)
greaterThanZ.matchCompareCriteria(betweenXandY)

si X e Y son mayores que Z, devolverá verdadero.

editar:

1) instanceof es lo que veo, por ahora, según sea necesario en el método matchCompareCriteria. Me gustaría deshacerme de él

2) matchCompareCritera verifica si un compareCriteria está contenido por otro. Si todos los valores posibles de uno están contenidos en el otro, devuelve verdadero. Para muchas combinaciones de compareCriteria, no tiene sentido compararlas para que devuelvan el valor falso (como si entre Alfa y betweenNum fueran incompatibles).

¿Fue útil?

Solución

El problema que está describiendo se llama doble envío . El nombre proviene del hecho de que debe decidir qué bit de código ejecutar (despachar) en función de los tipos de dos objetos (por lo tanto: doble).

Normalmente, en OO hay un solo envío: llamar a un método en un objeto hace que la ejecución del método se ejecute por ese objeto.

En su caso, tiene dos objetos, y la implementación a ejecutar depende de los tipos de ambos objetos. Fundamentalmente, hay un acoplamiento implícito en esto que " se siente mal " cuando anteriormente solo ha tratado con situaciones OO estándar. Pero no está realmente mal, solo está ligeramente fuera del dominio del problema de lo que las características básicas de OO están directamente adaptadas para resolver.

Si está utilizando un lenguaje dinámico (o un lenguaje de tipo estático con reflexión, que es lo suficientemente dinámico para este propósito) puede implementarlo con un método de despachador en una clase base. En pseudocódigo:

class OperatorBase
{
    bool matchCompareCriteria(var other)
    {
        var comparisonMethod = this.GetMethod("matchCompareCriteria" + other.TypeName);
        if (comparisonMethod == null)
            return false;

        return comparisonMethod(other);
    }
}

Aquí estoy imaginando que el lenguaje tiene un método incorporado en cada clase llamada GetMethod que me permite buscar un método por nombre y también una propiedad TypeName en cada objeto que recibe Me el nombre del tipo del objeto. Entonces, si la otra clase es un GreaterThan , y la clase derivada tiene un método llamado matchCompareCriteriaGreaterThan, llamaremos a ese método:

class SomeOperator : Base
{
    bool matchCompareCriteriaGreaterThan(var other)
    {
        // 'other' is definitely a GreaterThan, no need to check
    }
}

Así que solo tienes que escribir un método con el nombre correcto y se produce el envío.

En un lenguaje de tipo estático que admite la sobrecarga de métodos por tipo de argumento, podemos evitar tener que inventar una convención de nomenclatura concatenada; por ejemplo, aquí está en C #:

class OperatorBase
{
    public bool CompareWith(object other)
    {
        var compare = GetType().GetMethod("CompareWithType", new[] { other.GetType() });
        if (compare == null)
            return false;

        return (bool)compare.Invoke(this, new[] { other });
    }
}

class GreaterThan : OperatorBase { }
class LessThan : OperatorBase { }

class WithinRange : OperatorBase
{
    // Just write whatever versions of CompareWithType you need.

    public bool CompareWithType(GreaterThan gt)
    {
        return true;
    }

    public bool CompareWithType(LessThan gt)
    {
        return true;
    }
}

class Program
{
    static void Main(string[] args)
    {
        GreaterThan gt = new GreaterThan();
        WithinRange wr = new WithinRange();

        Console.WriteLine(wr.CompareWith(gt));
    }
}

Si tuviera que agregar un nuevo tipo a su modelo, tendría que mirar cada tipo anterior y preguntarse si necesitan interactuar con el nuevo tipo de alguna manera. En consecuencia, cada tipo tiene que definir una forma de interactuar con todos los demás tipos , incluso si la interacción es un valor predeterminado realmente simple (como " no hacer nada excepto devolver verdadero "). Incluso ese simple defecto representa una elección deliberada que debes hacer. Esto está disfrazado por la conveniencia de no tener que escribir explícitamente ningún código para el caso más común.

Por lo tanto, puede tener más sentido capturar las relaciones entre todos los tipos en una tabla externa, en lugar de dispersarla alrededor de todos los objetos. El valor de la centralización será que puede ver instantáneamente si se ha perdido alguna interacción importante entre los tipos.

Por lo tanto, podría tener un diccionario / mapa / tabla hash (como se llame en su idioma) que asigna un tipo a otro diccionario. El segundo diccionario asigna un segundo tipo a la función de comparación correcta para esos dos tipos. La función general CompareWith usaría esa estructura de datos para buscar la función de comparación correcta para llamar.

El enfoque correcto dependerá de la cantidad de tipos que pueda terminar en su modelo.

Otros consejos

Ya que hace referencia a instanceof , asumo que estamos trabajando en Java aquí. Esto podría permitirle hacer uso de la sobrecarga. Considere una interfaz llamada SomeInterface , que tiene un solo método:

public interface SomeInterface {
    public boolean test (SomeInterface s);
}

Ahora, definimos dos clases (con nombre inteligente) que implementan SomeInterface : Some1 y Some2 . Some2 es aburrido: test siempre devuelve false. Pero Some1 anula la función test cuando se le da un Some2 :

public class Some1 implements SomeInterface {
    public boolean test (SomeInterface s) {
        return false;
    }

    public boolean test (Some2 s) {
        return true;
    }
}

Esto nos permite evitar tener línea tras línea de sentencias if para realizar la comprobación de tipos. Pero hay una advertencia. Considere este código:

Some1 s1 = new Some1 ();
Some2 s2 = new Some2 ();
SomeInterface inter = new Some2 ();

System.out.println(s1.test(s2));     // true
System.out.println(s2.test(s1));     // false
System.out.println(s1.test(inter));  // false

¿Ves esa tercera prueba? Aunque inter es de tipo Some2 , en su lugar se trata como un SomeInterface . La resolución de sobrecarga se determina en tiempo de compilación en Java, lo que podría hacerla completamente inútil para usted.

Eso lo vuelve a poner en la casilla uno: usando instanceof (que se evalúa en tiempo de ejecución). Incluso si lo haces de esa manera, sigue siendo un mal diseño. Cada una de tus clases tiene que saber sobre todas las demás. Si decide agregar otro, debe regresar a todos los existentes para agregar la funcionalidad para manejar la nueva clase. Esto se vuelve terriblemente imposible de mantener, lo que es una buena señal de que el diseño es malo.

Un rediseño está en orden, pero sin mucha más información, no puedo darle un buen impulso en la dirección correcta.

Necesitas crear una súper clase o interfaz llamada Criterios. Luego, cada subclase concreta implementará la interfaz de Criterios. entre, mayor que etc son criterios.

la clase Criterios especificará el método matchCompareCriteria que acepta un Criterio. La lógica real residirá en las subclases.

Está buscando el patrón de diseño de estrategia o el patrón de diseño de plantilla.

Si entiendo bien, su método se basa en la verificación de tipos. Eso es bastante difícil de evitar, y el polimorfismo falla en resolver el problema. En su ejemplo, inArray necesita para verificar el tipo de parámetro porque el comportamiento del método depende de esto. No puede hacerlo por polimorfismo, lo que significa que no puede poner un método polimórfico en sus clases para manejar este caso. Esto se debe a que su matchCompareCriteria depende del tipo del parámetro en lugar de su comportamiento .

La regla de no usar instanceof es válida cuando verifica el tipo de objeto para elegir qué comportamiento se debe tener. Claramente, ese comportamiento pertenece a los diferentes objetos cuyo tipo usted verifica. Pero en este caso, el comportamiento de su objeto depende del tipo de objeto que se le pasa y pertenece al objeto que llama, no a los llamados como antes. El caso es similar a cuando anula equals () . Realice una comprobación de tipo para que el objeto que se pasa sea del mismo tipo que el objeto this y luego implemente su comportamiento: si la prueba falla, devuelva falso; de lo contrario, haz las pruebas de igualdad.

Conclusión: el uso de instanceof está bien en este caso.

Aquí hay un artículo más largo de Steve Yegge, que explica mejor, Pienso, usando un ejemplo simple y directo. Creo que esto se adapta muy bien a su problema.

Recuerde: El polimorfismo es bueno, excepto cuando no lo es . :)

El enfoque de Smalltalk sería introducir más capas a la jerarquía. Así que entre y mayorThan serían subclases de rangedCompareCriteria (o algo así), y rangeCompareCriteria :: matchCompareCriteria devolvería < strong> true cuando se le preguntó si dos instancias de sí mismo eran comparables.

Hablando de eso, probablemente quieras cambiar el nombre " matchCompareCriteria " a algo que expresa un poco mejor la intención.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top