Design pattern per il metodo per accettare uno dei vari elementi statici in C #?

3

Voglio illustrare la mia domanda per mezzo di un esempio (si spera) rappresentativo.

Diciamo che ho una situazione in cui sto sviluppando una libreria di classi in C # da utilizzare in alcune simulazioni. Voglio definire i pianeti (Terra, Marte, Saturno, ecc.) E ognuno di loro ha alcune proprietà e metodi comuni nell'interfaccia (nome, firma, ecc.) Ma unici nell'implementazione per ciascuno. Cioè Earth.Mass non è uguale a Mars.Mass.

Finora niente di grave.

Voglio avere altre classi / metodi / algoritmi che accettano come argomento un pianeta e ne usano le proprietà e il metodo definiti:

double MyApparentWeight(Planet someplanet, double MassInKG)
double DistanceFromInKM(Planet someplanet, Planet originplanet)

I pianeti potrebbero essere utilizzati dappertutto da qualcuno che utilizza questa libreria. Tutti i "Terreni" dovrebbero essere identici e tutti i "Saturn" dovrebbero essere identici. Ovviamente i "Terreni" sono distinti da quelli di "Saturno".

Voglio anche che un utente della libreria sia in grado di creare facilmente un nuovo pianeta (ad esempio Vulcan, Endor, Tralfamadore, Persefone, Rupert, ecc.). Dovrebbe essere costretto a fornire le proprietà e i metodi di quelli esistenti e quindi essere utilizzabile con i metodi MyApparentWeight e DistanceFromInKM.

Quindi l'approccio di livello base è che esiste una classe Planet e quindi quelli specifici (Terra, Vulcan, ecc.) ereditano semplicemente da essa. In questo caso, tuttavia, l'utente deve creare un'istanza della Terra e portarla in giro o crearne una nuova ogni volta che è necessaria. Questo potrebbe essere inutilmente goffo (soprattutto alla luce degli utenti previsti) e probabilmente un colpo di performance dal momento che alcuni pianeti potrebbero avere passaggi di inizializzazione della data di lettura dai file, ecc.

L'idea successiva era la precedente ma con un paradigma singleton. Questo è fondamentalmente quello che ho fatto finora. Questo sembra dettare l'implementazione interna a tutti i nuovi Pianeti che vengono creati (e di nuovo alcuni di quelli che riguardano il potenziale livello di alcuni utenti).

Questo progetto è il modo in cui ho tagliato i miei denti C # /. NET. Il mio approccio iniziale ingenuo era di provare a fare classi statiche con interfacce o ereditarietà. Ovviamente ho imparato rapidamente la follia di quel percorso. Ad un livello elevato è il tipo di paradigma che avrei voluto comunque.

C'è qualche altro motivo di progettazione che mi manca che potrebbe coprire questo tipo di situazione?

Apprezzo i suggerimenti.

Aggiornamenti

Forse non ho chiarito che questo esempio di Planet avrebbe dovuto essere solo strutturalmente illustrativo e non direttamente rappresentativo dei calcoli effettivi. Alcune persone sembravano un po 'coinvolte nell'implementazione di quali dati vengono rappresentati all'interno di essi e in altri aspetti periferici.

I suggerimenti dei singoli pianeti (come la Terra) pur essendo istanze della classe Planet non hanno catturato i miei punti su implementazioni potenzialmente significative per i loro meccanismi interni che supportano i loro metodi e proprietà. Sono anche incerto sul modo in cui aiuta a non voler gestire il trascinamento di quelle istanze potenzialmente ovunque si desideri utilizzarle o dover creare nuove istanze.

L'estensione di Glenn del suggerimento di Stephen penso che in realtà funzioni meglio nel contesto di ciò di cui ho bisogno qui. Posso accoppiarlo con il suggerimento di rsbarro di avere proprietà statiche per i bambini costruiti in Planet . (In realtà più come interface IPlanet implementatori). Ciò fornisce l'accesso più semplice per gli utenti agli oggetti più comunemente usati e offre un leggero miglioramento delle prestazioni di non dover recuperare ogni volta dal dizionario per nome. Offre ancora la possibilità per gli utenti di aggiungere dinamicamente più tipi di Pianeti.

Non ho ancora il rappresentante per votare su tutte le risposte che ho citato sopra o lo farei.

Grazie a tutti per i vostri suggerimenti e feedback.

    
posta kcc 27.03.2014 - 01:16
fonte

6 risposte

1

Avrei una classe planet generica, con proprietà di sola lettura impostate dai parametri del costruttore.

class Planet
{
    public string Name { get; private set; }
    public double Mass { get; private set; }

    public Planet(string name, double mass)
    {
        this.Name = name;
        this.Mass = mass;
    }
}

Avrei quindi una classe PlanetFactory per la generazione di pianeti ben noti:

static class PlanetFactory
{
    public static Planet CreateEarth() 
    { 
        return new Planet("Earth", 597219000000000000000000); 
    }

    public static CreateByName(string name)
    {
        if (name.Equals("Earth")) return CreateEarth();
    }
}

Avrei quindi una classe manager per contenere tutti i tuoi pianeti:

class PlanetManager
{
    private Dictionary<string, Planet> planets;

    public GetPlanet(string name)
    {
        if (!planets.ContainsKey(name))
        {
            planets.add(name, PlanetFactory.CreateByName(name));
        }

        return planets[name];
    }
}

Questo approccio significa che puoi facilmente aggiungere nuovi pianeti conosciuti e fornire un meccanismo per l'aggiunta di nuovi pianeti attraverso la classe PlanetManager (anche se avresti bisogno di un metodo diverso per farlo).

    
risposta data 27.03.2014 - 01:59
fonte
4

Potresti creare proprietà statiche su Planet per ciascuno dei pianeti specifici che desideri creare.

Ad esempio:

public class Planet
{
    public string Name { get; set; }
    public double Mass { get; set; }

    private static Planet _earth;
    public static Planet Earth { get { return GetEarth(); } }

    //Use this approach so that you only create a planet if it is needed
    private static Planet GetEarth()
    {
         if(_earth == null)
              _earth = new Planet { Name = "Earth", Mass = 123.0 };
         return _earth;
    }
}

Questo approccio è simile a come funziona la classe System.Drawing.Color . Dai uno sguardo a questo in ILSpy , in particolare le proprietà dei colori con nome come Aqua , White , ecc.

    
risposta data 27.03.2014 - 01:38
fonte
0

Vorrei utilizzare una soluzione simile a Stephens, in quanto implica un dizionario e PlanetManager è statico.

Questo esempio presuppone che tu abbia già un% base di tipo% di tipo co_de e sottoclassi di Planet (come Planet ) per pianeti diversi. Ciò funzionerebbe anche per le implementazioni planetarie senza sottoclassi (come in tutti i pianeti sono istanze di Earth e non una classe derivata). Se stai estendendo Planet per creare tutti i tuoi pianeti, ti consiglio di creare Planet astratto in modo che debba essere esteso per creare istanze.

public static class PlanetManager
{
    static PlanetManager()
    {
        // Initialize the planets dictionary in a static constructor
        _planets = new Dictionary<string, Planet>();
        // Add existing planets to the dictionary using the appropriate constructor
        _planets.Add("earth", new Earth());
    }

    private static Dictionary<string, Planet> _planets;

    public static void AddPlanet(string name, Planet planet)
    {
        if(name == null)
        {
            throw new ArgumentNullException("name");
        }

        if(planet == null)
        {
            throw new ArgumentNullException("planet");
        }

        if(_planets.ContainsKey(name.ToLower()))
        {
            // Only one planet per name
            throw new InvalidOperationException("That planet already exists.");
        }

        // Add the planet to the dictionary
        _planets[name.ToLower()] = planet;
    }

    public static Planet GetPlanet(string name)
    {
        if(name == null)
        {
            // Null checks
            throw new ArgumentNullException("name");
        }

        // Case-insensitive planet matching that lets exceptions fall through to caller
        return _planets[name.ToLower()];
    }
}

Questa implementazione non è thread-safe.

    
risposta data 27.03.2014 - 02:46
fonte
0

Sei, per qualsiasi motivo, dati hard-coding nel tuo programma. Questo è di per sé un odore di codice (cosa fai quando Marte subisce una collisione catastrofica e perde il 5% se è di massa?), Ma a volte il codice reale è maleodorante e ci sono alcuni modi per farlo meglio di altri.

Dato che nel nostro esempio vogliamo consentire ai programmatori downstream di creare le proprie istanze di pianeta, vorremmo una classe Planet o una struct non statica. E poiché ci sono anche dei pianeti reali, qualche forma di metodo statico per popolare quella classe sarebbe appropriata. Potresti creare una nuova sottoclasse per ogni pianeta standard, oppure potresti creare un metodo statico distinto per ogni pianeta, ma penso che entrambi i modelli introducano troppa complessità.

Un semplice singolo statico 'IPlanet statico GetPlanet (nome stringa)' dovrebbe essere sufficiente per l'interfaccia, e la codifica hard dei valori racchiusi all'interno di una struttura di controllo è più che abbastanza complessa. Se un metodo di otto o nove diramazioni ti sembra troppo complesso, puoi sempre percorrere la strada pulita e includere semplicemente un file XML con i dati nell'assembly.

Naturalmente, se i tuoi "pianeti" avessero davvero metodi e comportamenti interni diversi, sarebbero appropriate classi diverse ... ma un singolo metodo "GetPlanet" sarebbe comunque un modo giusto per selezionarle.

    
risposta data 27.03.2014 - 04:45
fonte
0

In particolare, sembra che questo si adatti esattamente al schema dei pesi volanti . Molte delle risposte lo hanno implementato in vari modi. Conoscere il nome del modello può aiutarti a ricercare altri possibili approcci.

    
risposta data 27.03.2014 - 20:25
fonte
0

Simile a Stephen, ma per un'API più pulita, usa un enum per i pianeti ed eredita PlanetManager dal Dizionario. Mi piace così:

public enum SolSystemPlanet { Earth }

public class PlanetManager : Dictionary<SolSystemPlanet, Planet>
{
    new public Planet this[SolSystemPlanet planet]
    {
        get 
        {
            if (!this.ContainsKey(planet))
                this.Add(planet, Create(planet));

            return base[planet];
        }
    }

    private static Planet Create(SolSystemPlanet planet)
    {
        switch (planet)
        {
            case SolSystemPlanet.Earth:
                return new Earth();
            default:
                throw new ArgumentException("Planet Not Supported");
        }
    }
}

Il risultato finale è che ottieni pianeti in questo modo:

PlanetManager planets = new PlanetManager();
Console.WriteLine(planets[SolSystemPlanet.Earth].ToString());
    
risposta data 27.03.2014 - 22:57
fonte

Leggi altre domande sui tag