Quale design / architettura / modello utilizzare per un sistema di pesi volumetrici serializzabile / sovrascrivibile?

0

Sto facendo una partita con i treni. Ho bisogno di serializzare quale treno è usato per ogni livello. Quindi posso semplicemente fare:

public class Level {
    Track track;
    Train train;
    [...]
}

public class Train {
    Wagon[] wagons;
}

public class Wagon {
    float length;
    float weight;
}

//Example:
level = new Level (track, new Wagon (1f,2f));

Ma poi, ho bisogno di avere tipi di Carri da cui posso scegliere. Mi piace così:

public class Wagon {
    float length;
    float weight;
    public static Wagon Small { get { return new Wagon (1f,2f); }}
    public static Wagon Big { get { return new Wagon (10f,20f); }}
}

//Example:
level = new Level (track, Wagon.Small);

Funziona. Ma se voglio modificare un parametro del vagone, devo deserializzare, modificare e riserializzare ogni Wagon.

Quindi mi sono inventato questo:

public enum WagonType {
    Small,
    Big
}

public class WagonParams {
    float length;
    float weight;
}

public class Wagon {
    WagonType type;
    WagonParams params { get { return WagonFactory.GetParams(type); }}
}

public WagonFactory {
    static WagonParams small;
    static WagonParams big;
    static WagonFactory () {
        small = new WagonParams (1f,2f);
        big = new WagonParams (10f,20f);
    }
    public static WagonParams GetParams (WagonType type) {
        switch (type) {
            case WagonType.Small: return small;
            case WagonType.Big: return big;
        }
    }
}

//Example:
level = new Level (track, new Wagon(WagonType.Small));

che sembra un po 'eccessivo, ma funziona.

Ora dì che voglio un SmallWagon ma con più peso, posso fare:

public interface IOverrideWagonField {
    WagonParams Override (WagonParams params);
}

public class OverrideWeight : IOverrideWagonField {
    float weight;
    WagonParams Override (WagonParams params) {
        return new WagonParams (params.length, weight);
    }
}

public class Wagon {
    WagonType type;
    IOverrideField overrider;
    WagonParams params { get { return WagonFactory.GetParams(type,overrider); }}
}

public WagonFactory {
    static WagonParams small;
    static WagonParams big;
    static WagonFactory () {
        small = new WagonParams (1f,2f);
        big = new WagonParams (10f,20f);
    }
    public static WagonParams GetParams (WagonType type, IOverrideWagonField overrider) {
        switch (type) {
            case WagonType.Small: return overrider != null? overrider.Override(small) : small;
            case WagonType.Big: return overrider != null? overrider.Override(big) : big;
        }
    }
}

//Example:
level = new Level(track, new Wagon(WagonType.Small,null)); //Normal Wagon
level = new Level(track, new Wagon(WagonType.Small,new OverrideWeight (23f)); //Override weight

Ciò che mi infastidisce (oltre alla complessità) è che ogni vagone usa la memoria per un IOverrideWagonField che potrebbero non usare.

Il sistema non funziona se si desidera eseguire l'override di più campi. Avrebbe bisogno di un array di IOverrideWagonField, ma creerebbe un nuovo oggetto per ogni campo di overross. (Il metodo Override () chiama "nuovo")

Quindi ritengo che questo sistema sia tutt'altro che perfetto.

Che ne pensi? Come andresti ad implementare un sistema del genere?

    
posta Souk21 13.08.2016 - 12:31
fonte

1 risposta

1

Sembra un caso di over-engineering , personalmente lo terrei semplice: tratta tutti i vagoni allo stesso modo.

Codice base:

public class Wagon
{
    public float Length { get; set; }
    public float Weight { get; set; }
}

public class Train
{
    public List<Wagon> Wagons { get; set; }
}

public class Level
{
    public Train Train { get; set; }

    public static Level Deserialize(TextReader textReader)
    {
        if (textReader == null) throw new ArgumentNullException(nameof(textReader));
        var serializer = new XmlSerializer(typeof(Level));
        var deserialize = serializer.Deserialize(textReader);
        var level = deserialize as Level;
        return level;
    }

    public static void Serialize(TextWriter textWriter, Level level)
    {
        if (textWriter == null) throw new ArgumentNullException(nameof(textWriter));
        if (level == null) throw new ArgumentNullException(nameof(level));
        var serializer = new XmlSerializer(typeof(Level));
        serializer.Serialize(textWriter, level);
    }
}

Demo:

public class Demo
{
    public Demo()
    {
        // some level
        var level = new Level
        {
            Train = new Train
            {
                Wagons = new List<Wagon>(new[]
                {
                    new Wagon
                    {
                        Length = 100.0f,
                        Weight = 100.0f
                    },
                    new Wagon
                    {
                        Length = 200.0f,
                        Weight = 200.0f
                    }
                })
            }
        };

        // serialize
        string xml;
        using (var writer = new StringWriter())
        {
            Level.Serialize(writer, level);
            xml = writer.ToString();
        }

        // deserialize
        using (var reader = new StringReader(xml))
        {
            var level1 = Level.Deserialize(reader);
        }
    }
}

Puoi sottoclasse Wagon come segue:

public class SmallWagon : Wagon
{
    public SmallWagon()
    {
        Length = 10.0f;
        Weight = 10.0f;
    }
}

public class LargeWagon : Wagon
{
    public LargeWagon()
    {
        Length = 10000.0f;
        Weight = 10000.0f;
    }
}

Ma dovrai includere questi tipi in Wagon affinché la serializzazione funzioni:

[XmlInclude(typeof(SmallWagon))]
[XmlInclude(typeof(LargeWagon))]
public class Wagon
{
    public float Length { get; set; }
    public float Weight { get; set; }
}

In qualche modo puoi mitigare questo noioso lavoro raccogliendoli automaticamente e usando l'altro costruttore:

var extraTypes = Assembly
    .GetExecutingAssembly()
    .GetTypes()
    .Where(s => s.BaseType == typeof(Wagon))
    .ToArray();
var serializer = new XmlSerializer(typeof(Level), extraTypes);

Tuttavia, sembra un odore di codice, perché ora ci sono molti tipi di vagoni:

<?xml version="1.0" encoding="utf-16"?>
<Level xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <Train>
    <Wagons>
      <Wagon xsi:type="SmallWagon">
        <Length>10</Length>
        <Weight>10</Weight>
      </Wagon>
      <Wagon xsi:type="LargeWagon">
        <Length>10000</Length>
        <Weight>10000</Weight>
      </Wagon>
      <Wagon>
        <Length>100</Length>
        <Weight>100</Weight>
      </Wagon>
      <Wagon>
        <Length>200</Length>
        <Weight>200</Weight>
      </Wagon>
    </Wagons>
  </Train>
</Level>

Per facilitare il processo di costruzione di carri preimpostati vorrei usare qualche classe statica o qualche sistema di costruzione primitivo:

public static class WagonHelper
{
    public static Wagon CreateLargeWagon()
    {
        return new Wagon
        {
            Length = 10000.0f,
            Weight = 10000.0f
        };
    }

    public static Wagon CreateSmallWagon()
    {
        return new Wagon
        {
            Length = 10.0f,
            Weight = 10.0f
        };
    }
}

// or

public abstract class WagonBuilder
{
    public abstract Wagon Build();
}

public class LargeWagonBuilder : WagonBuilder
{
    public override Wagon Build()
    {
        return new Wagon
        {
            Length = 10000.0f,
            Weight = 10000.0f
        };
    }
}

Attenersi al principio KISS,

Per quanto riguarda la serializzazione molte volte non c'è nulla di sbagliato in questo, Unity3D lo fa molto frequentemente per gestire cose come l'assemblaggio di ricarica dopo una ricostruzione e così via.

    
risposta data 14.08.2016 - 01:24
fonte

Leggi altre domande sui tag