Quale modello per l'implementazione di più interfacce sullo stesso tipo?

1

Descrizione dell'ambiente:

  • Sto implementando un motore moderno (hobbista) per (un vecchio) videogioco per PlayStation 1
  • I dati grafici sono rappresentati come pacchetti dove possono rappresentare un poligono o uno sprite,
    (e vengono trasformati in modo diverso per finire come pixel sullo schermo)
  • Tutti i tipi di pacchetti di questo tipo (18 in totale) possono essere ridotti a 2 o 3 tipi al massimo (cosa ho fatto per ridurre la superficie del codice), con alcuni flag che indicano ciò che è presente in essi

A scopo informativo,

Questo formato è più o meno una variante del formato HMD di Sony, un efficiente formato di basso livello che viene consumato direttamente dalla GPU della PlayStation 1, a scapito dell'essere ostile da leggere / parse, ecc ... (in realtà sulla vera PlayStation 1, si potrebbero emettere poche chiamate alla libreria di sistema per caricarlo e basta)

Descrizione del problema:

  • Ho bisogno di trovare un modo (semplice ma efficace) per realizzare i loro contenuti
  • contabilità che possono essere poligoni o sprite

Per farla breve,

I seguenti frammenti di codice riportati di seguito probabilmente spiegano meglio qual è il contenuto e l'approccio preliminare,

Interfacce contro cui lavoreremo:

/// <summary>
///     a graphics primitive.
/// </summary>
public interface IPrimitive
{
    bool IsPolygon { get; }
    bool IsSprite { get; }
}

/// <summary>
///     a polygon, i.e. triangle, quad.
/// </summary>
public interface IPolygon : IPrimitive
{
    void GetMesh();
}

/// <summary>
///     a sprite, (internal representation has different meaning than a polygon).
/// </summary>
public interface ISprite : IPrimitive
{
    void GetMesh();
}

Digita presente nei dati di gioco:

/// <summary>
///     a packet representing graphical data : a polygon or a sprite.
/// </summary>
public class GraphicsPacket : IPolygon, ISprite
{
    public GraphicsPacket(Stream stream)
    {
        // read packet ...


        // there will be a flag indicating if it's a sprite
        // and therefore should be treated differently
    }

    public bool IsSprite { get; }

    public bool IsPolygon => !IsSprite;

    void IPolygon.GetMesh()
    {
        if (IsSprite)
            throw new InvalidOperationException("Instance is not a polygon");
    }

    void ISprite.GetMesh()
    {
        if (IsPolygon)
            throw new InvalidOperationException("Instance is not a sprite");
    }
}

Test dell'intera logica:

public class Test
{
    public void BuildPrimitive([NotNull] IPrimitive primitive)
    {
        if (primitive == null)
            throw new ArgumentNullException(nameof(primitive));

        // obviously here, 'as' cast would always be true for all cases

        var polygon = primitive.IsPolygon;
        if (polygon)
        {
            // do something with it
            ((IPolygon)polygon).GetMesh();

            return;
        }

        var sprite = primitive.IsSprite;
        if (sprite)
        {
            // do something with it
            ((ISprite)sprite).GetMesh();

            return;
        }

        throw new NotSupportedException(nameof(primitive));
    }
}

Domanda:

Il mio approccio per il trattamento di un pacchetto grafico dipende dal suo contenuto?

cioè.

  • implementazione dell'interfaccia esplicita
  • naive bool controlla e attiva lo stato non valido

Argomenti forti su come prendere in considerazione di suddividere i dubbi in questo caso,

  • i dati di gioco sono proprio così, perché cambiare la logica?
  • (mio) approccio moderno, orientato agli oggetti, sintetizza questo come pochi tipi (cioè concetto ASCIUTO)
  • Il problema
  • non scomparirà davvero, poiché quei pacchetti grafici saranno sempre un poligono o uno sprite (ad esempio perché combattere contro quella logica dopo tutto?)

Spero che abbia senso per te, altrimenti fammi sapere come posso migliorare la domanda.

    
posta Aybe 18.05.2017 - 21:21
fonte

2 risposte

2

Credo che sia necessaria una sola interfaccia per le classi di implementazione di Poligono e Sprite. Sulla base di ciò che vedo creo un'interfaccia per GraphicsPacket e creo classi Polygon e Sprite che implementano i suoi metodi.

Quindi in tutto il codice lo scrivi per utilizzare gli oggetti GraphicsPacket e non devi decidere più volte se si tratta di un poligono o di uno sprite.

public interface IGraphicsPacket
{
    public GraphicsPacket(Stream stream);

    public GetMesh()
}

public class  Polygon : IGraphicsPacket
{
    public GraphicsPacket(Stream stream)
    {
        //Polygon implementation
    }

    public GetMesh()
    {
        //Polygon implementation
    }
}

public class  Sprite : IGraphicsPacket
{
    public GraphicsPacket(Stream stream)
    {
        //Sprite implementation
    }

    public GetMesh()
    {
        //Sprite implementation
    }
}

test

public class Test
{
    public void BuildPrimitive([NotNull] IGraphicsPacket primitive)
    {
        if (primitive == null)
            throw new ArgumentNullException(nameof(primitive));

        primitive.GetMesh();

        return;
    }
}
    
risposta data 18.05.2017 - 21:50
fonte
0

In realtà, non rispetti il principio DRY con il tuo codice perché test sempre esplicitamente il tipo.

Questo è un esempio perfetto in cui qualcuno dovrebbe usare una gerarchia di classi come già raccomandato nell'altra risposta da jdobrzen .

Il modo in cui eviti la duplicazione del codice consiste nell'implementazione condivisa in una classe base o in una classe helper.

public class PrimitiveBase 
{ 
    /* contains common code, probably with protected */
}

public class Polygone : PrimitiveBase, IPolygone { ... }

Quindi dovresti anche avere un metodo factory che creerà l'oggetto appropriato dallo stream. Un modo sarebbe che tutta la tua interfaccia deriva da IPrimitive e avere una fabbrica.

Qualcosa come:

public class PrimitiveFactory
{
    IPrimitive CreateFromStream(Stream stream)
    {
        // - Read primitive type from stream
        // - Reset stream position (if necessary)
        // - Create appropriate primitive (might be a switch statement)
        // - If data loading is separate from constructor, then load data
    }
}    

Se lo desideri, puoi utilizzare i contenitori DI o un sistema di serializzazione ...

    
risposta data 18.06.2017 - 01:51
fonte

Leggi altre domande sui tag