Pattern per la selezione del caso sul tipo di oggetto (dell'interfaccia)

4

Ho letto che fare un caso specifico è spesso un odore di codice. Tuttavia, ci sono casi in cui un'interfaccia non può risolvere il mio problema.

Ad esempio, ho un set di oggetti filtro data (ultimi 7 giorni, l'anno scorso, ecc.) che implementano un'interfaccia IDateFilter. In un'altra parte del codice, ho bisogno di determinare il tipo di filtro data in modo che le proprietà del grafico possano essere impostate. Potrei voler utilizzare un intervallo di graduazione del grafico diverso, ad esempio, a seconda del filtro della data.

Il problema è che non voglio memorizzare queste informazioni nell'oggetto filtro data in quanto è relativo all'interfaccia e non voglio accoppiarle.

In questo caso, come potrebbe essere evitato un caso selezionato? A parte l'utilizzo della select case su gettype o la creazione di qualche enumerazione per tutti i tipi e quindi l'esposizione del tipo e la selezione di un caso selezionato, non vedo un modo pulito per farlo?

Mi viene in mente lo schema del decoratore ma anche in quel caso avrei comunque bisogno di un caso selezionato.

Qualche idea?

if (type = last 7 days)
 set this property 
else if (type = last year)
 set this property to something different

Sto usando asp net c # nel caso in cui ciò faccia la differenza.

    
posta KingOfHypocrites 23.04.2012 - 14:35
fonte

5 risposte

3

Crea una struttura dati, come un dizionario, che usa il tipo come chiave e il valore è la proprietà del grafico. Se si dispone di più proprietà del grafico associate a un determinato tipo, il valore potrebbe essere un oggetto ChartProperties. Quindi hai qualcosa di simile:

ChartProperties chartProp;
if (propertiesMap.containsKey(currentFilter.getType()){
  chartProp = propertiesMap(currentFilter.getType());
}

Vorrei che le proprietà mappassero una sottoclasse di un dizionario e poi inserissi il codice sopra in un metodo su quella classe.

    
risposta data 23.04.2012 - 22:05
fonte
3

Vorrei che l'interfaccia IDateFilter includesse un metodo / proprietà per interrogare l'intervallo di tempo o qualcosa del genere. Dovresti essere in grado di chiedere l'oggetto quando sono presenti le date di inizio e di fine, o il numero di giorni indietro, ecc.

    
risposta data 23.04.2012 - 15:15
fonte
1

Prima di tutto - perché non stai calcolando l'intervallo di tick dai dati effettivi restituiti dal filtro? Ma va bene, suppongo tu abbia le tue ragioni.

Le dichiarazioni caso-selezionate sono un odore di codice perché potresti finire con più di una se queste istruzioni si selezionano sullo stesso tipo. Se aggiungi un altro caso a uno di essi, devi aggiungerne uno a tutte queste affermazioni. Questo è più incline all'errore dell'alternativa - aggiungendo un metodo astratto all'interfaccia. In quest'ultimo caso il compilatore ti aiuterà a trovare tutti i siti pertinenti nel tuo codice.

Se non c'è la possibilità di integrare il calcolo in IDateFilter con un metodo come getIntervalSize , puoi dividere il client dell'interfaccia IDateFilter in una gerarchia di classi. Se questa è una soluzione praticabile dipende dal cliente il suo creatore e il creatore del filtro concreto. Tuttavia, dovrei avvisarti che potresti incontrare un odore di codice diverso, la gerarchia di classi duplicata. Ma questo potrebbe essere preferibile o (più probabile) un'indicazione di un difetto di progettazione più grande.

    
risposta data 23.04.2012 - 20:51
fonte
1

Dopo aver riflettuto ulteriormente, se vuoi farlo nel modo più pulito possibile, puoi utilizzare una combinazione del modello Strategia e Localizzatore di servizio per raggiungere il tuo obiettivo in un modo orientato agli oggetti.

Ecco la strategia generale (nessun gioco di parole):

  1. Metti la tua implementazione per ogni strategia in una classe separata.
  2. Registra ogni strategia in un elenco statico.
  3. Quando devi gestire un oggetto di un tipo sconosciuto, esegui un'iterazione delle strategie registrate finché non ne trovi una che funzioni.

La parte netta di questo approccio è che puoi stabilire se seguire o meno una strategia specifica basata su criteri più personalizzati rispetto a Type. Supponiamo che tu abbia un'interfaccia che in realtà vuoi gestire in due modi diversi a seconda del valore di una proprietà o se desideri applicare la stessa strategia a più interfacce. Tutto dipende da come lo si implementa.

Questo è decisamente eccessivo per classi come fabbriche, ecc., dove il modello if / else if dovrebbe essere sufficiente ed è in effetti abbastanza appropriato. Supponendo che tu abbia esplorato altre opzioni più semplici o più oo-friendly, ecco un esempio completamente sovradimensionato con il puro scopo di evitare un switch su Type, con conseguente Ultimate Extensible Code (TM).

Supponiamo che tu abbia a che fare con due interfacce esterne non correlate, IBlinger e ISnapper :

interface IBlinger
{
    public string BlingName { get; }
}

interface ISnapper
{
    public string SnapName { get; }
}

E stai cercando di scrivere una classe che gestisca la scrittura di ogni interfaccia sulla console in un modo personalizzato. Dovresti prima definire un'interfaccia IWriteHandler come segue:

interface IWriteHandler
{
    bool Write(object target);
}

E quindi fornire implementazioni:

class BlingWriter : IWriteHandler
{
    public bool Write(object target)
    {
        var bling = target as IBlinger;
        if (bling == null)
            return false;

        Console.WriteLine("BLING: " + bling.BlingName);

        return true;
    }
}

class SnapWriter : IWriteHandler
{
    public bool Write(object target)
    {
        var snap = target as ISnapper;
        if (snap == null)
            return false;

        Console.WriteLine("SNAP: " + snap.SnapName);

        return true;
    }
}

Ed ecco un esempio di utilizzo:

class Writer
{
    static List<IWriteHandler> _handlers = new List<IWriteHandler>();

    static Writer()
    {
        //add them to the list in order of preference, in case the types overlap
        //due to inheritance.
        _handlers.Add(new BlingWriter());
        _handlers.Add(new SnapWriter());
    }

    public void Write(object unknown)
    {
        foreach (var handler in _handlers)
        {
            if (handler.Write(unknown))
                return;
        }

        //default
        Console.WriteLine("UNKNOWN: " + unknown.ToString());
    }
}

E se sembra troppo "boilerplatey", potresti renderlo più generico:

abstract class GenericWriteHandler<T> : IWriteHandler where T : class
{
    public bool Write(object target)
    {
        var asT = target as T;
        if (asT == null)
            return false;

        WriteInternal(asT);

        return true;
    }

    protected abstract void WriteInternal(T target);
}

Che potrebbe essere implementato come:

class SnapWriter : GenericWriteHandler<ISnapper>
{
    protected override void WriteInternal(ISnapper target)
    {
        Console.WriteLine("SNAP: " + target.SnapName);
    }
}

OPPURE, se ti piace lambda / non mi piace creare molte classi, potresti fare:

class ActionWriteHandler<T> : IWriteHandler where T : class
{
    Action<T> _a;

    public ActionWriteHandler(Action<T> action)
    {
        _a = action;
    }

    public bool Write(object target)
    {
        var asT = target as T;
        if (asT == null)
            return false;

        _a(asT);

        return true;
    }

    protected abstract void WriteInternal(T target);
}

Dove la classe Writer è inizializzata come:

static Writer()
{
    _handlers.Add(new ActionWriteHandler<IBlinger>(b => Console.WriteLine(b.BlingName)));
    _handlers.Add(new ActionWriteHandler<ISnapper>(s => Console.WriteLine(s.SnapName)));
}

In effetti, potresti anche generare l'intera Writer generica, ma questo è per un altro giorno ...

Domanda correlata (che ha una risposta simile): link

    
risposta data 23.04.2012 - 23:00
fonte
1

La ragione per cui le dichiarazioni selezionate sono considerate un odore del codice è la causa della regola Zero, One, Infinity alias no tre.

Fondamentalmente le istruzioni switch fanno due cose che sono "cattive" - in primo luogo, sono un elenco esplicito nel codice, con i potenziali problemi associati (mantenimento della sincronizzazione, uso parziale, elenco duplicato per diverse proprietà del stessa cosa [cioè a volte fai case x: b=y; e a volte fai case x: c=z; ]).

In secondo luogo, se aggiungi un nuovo valore o lo porti via, il codice deve essere modificato e quindi distribuito.

Entrambi possono essere riassunti con: il codice è una struttura di dati scadente.

OTOH, a volte il peggio è abbastanza buono (ordina una matrice che contenga sempre esattamente due elementi). Quindi, come con tutti gli odori di codice, è un possibile problema e devi esaminare l'effettivo utilizzo per vedere se si tratta di un problema. Le cose da cercare sono dimensioni, varabilità, frequenza di utilizzo e variazioni.

    
risposta data 02.05.2012 - 14:23
fonte

Leggi altre domande sui tag