Domanda

Sto cercando di confrontare compareCriteria. Quelli semplici come "Between" e "inArray" o "GreaterThan". Uso il polimorfismo per queste classi. Un metodo che condividono dall'interfaccia compareCriteria è 'matchCompareCriteria'.

Quello che sto cercando di evitare è che ogni classe verifichi il tipo di compareCriteria a cui dovrebbero corrispondere. Ad esempio, l'oggetto inArray verificherà se matchCompareCriteria viene passato a un oggetto inArray, in caso contrario restituirà false, nel caso in cui sappia come confrontare.

Forse instanceof è perfettamente legittimo in questo caso (gli oggetti conoscono se stessi) ma sto ancora cercando possibili modi per evitarlo. Qualche idea?

esempio di pseudo-codice:

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

se X e Y sono maggiori di Z, tornerà vero.

modifica:

1) instanceof è quello che vedo, per ora, secondo necessità nel metodo matchCompareCriteria. Vorrei liberarmene

2) matchCompareCritera verifica se un compareCriteria è contenuto da un altro. Se tutti i possibili valori di uno sono contenuti dall'altro, esso restituisce true. Per molte combinazioni di compareCriteria non ha nemmeno senso confrontarle, quindi restituiscono false (come tra Alpha e BetweenNum sarebbe incompatibile).

È stato utile?

Soluzione

Il problema che stai descrivendo si chiama double dispatch . Il nome deriva dal fatto che è necessario decidere quale bit di codice eseguire (invio) in base ai tipi di due oggetti (quindi: double).

Normalmente in OO c'è un singolo invio: la chiamata di un metodo su un oggetto provoca l'esecuzione dell'implementazione del metodo da parte dell'oggetto.

Nel tuo caso, hai due oggetti e l'implementazione da eseguire dipende dai tipi di entrambi gli oggetti. Fondamentalmente, c'è un accoppiamento implicito da ciò che "si sente sbagliato". quando in precedenza hai gestito solo situazioni OO standard. Ma non è proprio sbagliato - è solo leggermente al di fuori del dominio problematico di ciò che le funzionalità di base di OO sono direttamente adatte alla risoluzione.

Se stai usando un linguaggio dinamico (o un linguaggio di tipo statico con reflection, che è abbastanza dinamico per questo scopo) puoi implementarlo con un metodo dispatcher in una classe base. In pseudo-codice:

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

        return comparisonMethod(other);
    }
}

Qui sto immaginando che il linguaggio abbia un metodo integrato in ogni classe chiamato GetMethod che mi permette di cercare un metodo per nome e anche una proprietà TypeName su ogni oggetto che ottiene me il nome del tipo di oggetto. Quindi se l'altra classe è un GreaterThan , e la classe derivata ha un metodo chiamato matchCompareCriteriaGreaterThan, chiameremo quel metodo:

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

Quindi devi solo scrivere un metodo con il nome corretto e si verifica l'invio.

In un linguaggio tipicamente statico che supporta il sovraccarico del metodo per tipo di argomento, possiamo evitare di dover inventare una convenzione di denominazione concatenata - ad esempio, eccola qui in 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));
    }
}

Se dovessi aggiungere un nuovo tipo al tuo modello, dovresti esaminare ogni tipo precedente e chiederti se devono interagire con il nuovo tipo in qualche modo. Di conseguenza ogni tipo deve definire un modo di interagire con ogni altro tipo - anche se l'interazione è un default davvero semplice (come " non fare nulla tranne restituire true "). Anche quel semplice default rappresenta una scelta deliberata che devi fare. Questo è mascherato dalla comodità di non dover scrivere esplicitamente alcun codice per il caso più comune.

Pertanto, potrebbe essere più sensato acquisire le relazioni tra tutti i tipi in una tabella esterna, invece di disperderlo attorno a tutti gli oggetti. Il valore della centralizzazione sarà che puoi immediatamente vedere se hai perso interazioni importanti tra i tipi.

Quindi potresti avere un dizionario / mappa / hashtable (qualunque cosa venga chiamata nella tua lingua) che associ un tipo a un altro dizionario. Il secondo dizionario associa un secondo tipo alla funzione di confronto corretta per questi due tipi. La funzione generale CompareWith userebbe quella struttura di dati per cercare la giusta funzione di confronto da chiamare.

L'approccio giusto dipenderà dal numero di tipi che probabilmente finirai nel tuo modello.

Altri suggerimenti

Dato che fai riferimento a instanceof , presumo che qui stiamo lavorando in Java. Ciò potrebbe consentire di utilizzare il sovraccarico. Considera un'interfaccia chiamata SomeInterface , che ha un solo metodo:

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

Ora, definiamo due classi (con un nome intelligente) che implementano SomeInterface : Some1 e Some2 . Some2 è noioso: test restituisce sempre false. Some1 sostituisce la funzione test quando viene assegnato un Some2 :

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

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

Questo ci consente di evitare di avere riga dopo riga di istruzioni if ??per il controllo del tipo. Ma c'è un avvertimento. Considera questo codice:

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

Vedi quel terzo test? Anche se inter è di tipo Some2 , viene invece trattato come SomeInterface . La risoluzione del sovraccarico è determinata in fase di compilazione in Java, il che potrebbe renderla completamente inutile per te.

Questo ti riporta al punto di partenza: usando instanceof (che viene valutato in fase di esecuzione). Anche se lo fai in questo modo, è ancora un cattivo design. Ognuna delle tue lezioni deve conoscere tutte le altre. Se decidi di aggiungerne un altro, devi tornare a tutti quelli esistenti per aggiungere funzionalità per gestire la nuova classe. Questo diventa orribilmente irraggiungibile in fretta, il che è un buon segno che il design è cattivo.

Una riprogettazione è in ordine, ma senza molte più informazioni, non posso darti una spinta particolarmente buona nella giusta direzione.

Devi creare una super classe o un'interfaccia chiamata Criteria. Quindi ogni sottoclasse concreta implementerà l'interfaccia Criteria. tra, maggiore di ecc. sono i criteri.

la classe Criteria specificherà il metodo matchCompareCriteria che accetta un criterio. La logica effettiva risiederà nelle sottoclassi.

Stai cercando il modello di progettazione strategica o il modello di progettazione modello.

Se ho capito bene, il tuo metodo si basa sul controllo del tipo. È abbastanza difficile da evitare e il polimorfismo non riesce a risolvere il problema. Dal tuo esempio, inArray ha bisogno di verificare il tipo di parametro perché il comportamento del metodo dipende da questo. Non puoi farlo tramite il polimorfismo, il che significa che non puoi mettere un metodo polimorfico nelle tue classi per gestire questo caso. Questo perché matchCompareCriteria dipende dal tipo del parametro piuttosto che dal suo comportamento .

La regola di non usare instanceof è valida quando si controlla il tipo di un oggetto per scegliere quale comportamento avere. Chiaramente, quel comportamento appartiene ai diversi oggetti di cui controlli il tipo. Ma in questo caso, il comportamento del tuo oggetto dipende dal tipo di oggetto in cui ti trovi e appartiene all'oggetto chiamante, non a quelli chiamati come prima. Il caso è simile a quando si sostituisce equals () . Esegui un controllo del tipo in modo che l'oggetto passato sia dello stesso tipo di questo e quindi attui il tuo comportamento: se il test fallisce, restituisci false; in caso contrario, eseguire i test di uguaglianza.

Conclusione: l'utilizzo di instanceof va bene in questo caso.

Ecco un articolo più lungo di Steve Yegge, che spiega meglio, Penso, usando un esempio semplice e diretto. Penso che questa mappa sia ottima per il tuo problema.

Ricorda: il polimorfismo è buono, tranne quando non lo è . :)

L'approccio Smalltalk sarebbe quello di introdurre più livelli nella gerarchia. Quindi tra e maggioreThan sarebbero sottoclassi di rangedCompareCriteria (o qualcosa del genere) e rangeCompareCriteria :: matchCompareCriteria restituirà < strong> true alla domanda se due istanze di se stessa fossero comparabili.

A proposito, probabilmente vorrai rinominare " matchCompareCriteria " a qualcosa che esprima un po 'meglio l'intento.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top