Ereditare e memorizzare oggetti che contengono diversi tipi di altri oggetti

3

Per iniziare, questa è più una questione di buone pratiche che altro.

Introduzione all'ambiente

Ho me stesso una classe astratta. Chiamiamo questa classe Item . Ho anche un'altra classe astratta, la chiameremo Container . La classe Container eredita la classe Item .

Ecco il mio dilemma: sto cercando di disegnare correttamente le porzioni OOP di esso e sto avendo un po 'di problemi a causa del mio desiderio di astrarre il più possibile, pur continuando a essere gestibile e comprensibile codice.

Iniziamo con un codice:

public abstract class Item
{
    private Guid _Guid;
    private float _Weight;

    public Guid Guid { get { return _Guid; } set { _Guid = value; } }
    public float Weight { get { return _Weight; } set { _Weight = value; } }

    public virtual ItemType ItemType { get { return ItemType.None; } }
}

public abstract class Container : Item
{
    private float _MaxWeight;
    private List<Item> _Items;

    public List<Item> Items { get { return _Items; } }
    public float MaxWeight { get { return _MaxWeight; } set { _MaxWeight = value; } }
    public abstract ContainerType ContainerType { get; }

    public sealed override ItemType ItemType { get { return ItemType.Container; } }
}

Quindi, con questo codice l'intenzione è per un Container di poter essere trattato come Item , con alcune eccezioni. (Il contenitore avrà proprietà aggiuntive, ecc.)

Il problema è che non tutti gli oggetti Item possono essere aggiunti a Container . Infatti, solo gli oggetti Container che hanno oggetti Container.MaxWeight o non% Container Item possono essere aggiunti.

A partire da ora, questa restrizione non può essere inserita. Il mio primo pensiero è stato quello di creare un ItemCollection nella classe Container , che essenzialmente conserverebbe tutti gli oggetti Item al suo interno. Questo mi permetterebbe di implementare il mio metodo ItemCollection.Add che eseguirà questo filtro.

Il problema entra in gioco quando introduco la mia terza classe. Chiamiamolo a Backpack . Questo eredita Container e appare come segue:

public class Backpack : Container
{
    public sealed override ContainerType ContainerType { get { return ContainerType.Backpack; } }
}

Attenzione: il Backpack (come lo chiamiamo) sarà in grado di contenere% oggetti% co_de, o% oggetti% co_de con un Item minore in essi.

Questo significa anche che potrei avere altre classi (usiamo, ad esempio, Container ) che erediterà anche Container.MaxWeight , ma che non può contenere altri oggetti Pouch .

Infine, potrebbero esserci anche classi aggiuntive che non possono contenere oggetti Container , ma solo oggetti Container trattenuti. (Vedi, ad esempio, Item .)

/// <summary>
/// Represents a collection of <see cref="Container"/> objects owned by an <see cref="Actor"/>.
/// </summary>
public class Inventory : Container
{
    public sealed override ContainerType ContainerType { get { return ContainerType.Inventory; } }
}

Dichiarazioni della domanda

È questo il modo migliore per farlo? Devo implementare il Container come volevo? Che dire degli oggetti Inventory che possono contenere solo altri oggetti ItemCollection ?

Devo usare Container per indicare che questo oggetto può contenere solo altri oggetti Container ? Dovrei invece usare il valore ContainerType.Inventory su di esso? (Qualsiasi oggetto che può contenere solo oggetti Inventory non realmente ha un ContainerType.MaxWeight . Il Container e MaxWeight può contenere è determinato dalla loro idoneità.

Devo creare una classe aggiuntiva che erediti MaxWeight che possa contenere solo altri oggetti Actor , quindi ereditare Container da questo? Allo stesso modo, dovrei creare un'altra classe che eredita da Container che consente o non consente l'aggiunta di oggetti Inventory solo?

Note aggiuntive / Jargon

L'idea è di costringere i programmatori (soprattutto me) a gestire correttamente ogni Container e Item come dovrebbero essere. Il problema sta nel fatto che ci sono diversi tipi di oggetti Container . Inoltre, tieni presente che questo non è tutto del codice in questione, ma il codice aggiuntivo che ho escluso è irrilevante a questo problema. Inoltre, le due classi aggiuntive che ho citato ( Item e Container ) sono completamente concreti. Lavorerò direttamente con quelli. Voglio anche avere un'infrastruttura comune tra di loro, in modo che qualsiasi Inventory che sia un Backpack possa essere lavorato in modo generale. (Piuttosto che affidarsi alla classe concreta appropriata.)

    
posta 202_accepted 26.06.2015 - 20:21
fonte

2 risposte

2

La direzione in cui stai andando è grandiosa. Vorrei fare qualche modifica a questo progetto in questo modo:

public abstract class Container : Item
{
    private float _MaxWeight;
    private List<Item> _Items;

    public List<Item> Items { get { return _Items; } }
    public float MaxWeight { get { return _MaxWeight; } set { _MaxWeight = value; } }
    public abstract ContainerType ContainerType { get; }

    public IEnumerable<IDepositFilter> Filters { get; set; } // Probably a factory can stuff filters as required per each type.

    public void DepositItem(Item item) {
        foreach(var filter in Filters) {
            if(!filter.IsAllowed(item)) {
                throw new ItemNotAllowedException();
            }
        }

        _Items.Add(item);
    }
}

public interface IDepositFilter
{
    bool IsAllowed(Item item);
}

public class DefaultContainerFilter : IDepositFilter {
    // probably do nothing here
}

public class OversizeContainerFilter : IDepositFilter
{
    // Check if the size of the new deposit is larger than leftover space in this container
}

public class ItemTypeFilter : IDepositFilter
{
    // check what is being deposited and allow/disallow accordingly.
}

Notate, ho introdotto una raccolta di filtri che dovrebbe essere usata quando aggiungete qualcosa al vostro contenitore. Puoi aggiungere tutti i filtri che vuoi al tuo contenitore di un tipo specifico. Ogni filtro esegue uno e un solo controllo.

Rimuovo anche ItemType in quanto sembra ridondante: puoi utilizzare le classi per determinare il tipo.

Riutilizza i filtri su tutto ciò che vuoi:)

Spero che questo aiuti.

    
risposta data 26.06.2015 - 20:38
fonte
2

Sfortunatamente, l'esposizione degli articoli della lista pubblica {get;} rompe l'intero meccanismo di incapsulamento e protezione. L'uso del codice può semplicemente fare container.Items.Add e inserire tutto ciò che vogliono. (che è un problema anche nell'altra risposta).

Sempre più spesso comincio a preferire la composizione invece dell'ereditarietà, e quando è necessaria una qualche forma di eredità (per evitare il codice duplicato), un motivo decoratore può venire a posto.

E per il gusto dell'argomento (capisco che l'esempio del codice non sia completo), non vedo un motivo per utilizzare una classe astratta per iniziare. L'interfaccia potrebbe essere più appropriata se vogliamo solo descrivere quali proprietà può avere un articolo.

Sebbene quanto sopra è la strada da percorrere, se c'è un bisogno "reale" di usare l'ereditarietà, ecco un codice di esempio su come mi avvicinerei a questo:

public abstract class Item
{
   public string SomeData{get;set;)
}

public abstract class Container : Item
{
    private readonly List<Item> _items;
    public IEnumerable<Item> Items { get { return _items;}}
    public Container()
    {
       _items = new List<Item>();
    }

    public void AddItem(Item item)
    {
       if (IsValidItem(item))
       {
           _items.Add(item);
       }
    }

    protected virtual bool IsValidItem(Item item)
    {
       ///some validation logic, etc
    }
}

Con il progetto semplicistico / schematico di cui sopra è facile ottenere le domande dal post originale. Negli altri tipi di contenitori, si tratta di eseguire l'override del metodo IsValid protetto per fare tutto ciò che è necessario (incl se deve chiamare il metodo base, ecc.).

Inoltre, non interrompiamo l'incapsulamento, quindi i consumatori esterni della classe non possono aggiungere elementi senza convalida.

Quando iniziano le domande sul resettaggio del container, sulla rimozione di elementi, ecc., probabilmente è meglio che la classe del contenitore erediti da Item, e implementa l'interfaccia ICollection, utilizzando una memoria di backup dell'elenco interno e avanti (tramite alcuni validazione) le corrispondenti chiamate ad esso:

public class Container : Item, ICollection<Item>

NOTA: Come ho detto prima, non andrò con questo approccio, è un incubo da testare ed estendere. E probabilmente nel mondo reale l'implementazione sta per rompere uno o più dei principi SOLID.

    
risposta data 26.06.2015 - 23:28
fonte

Leggi altre domande sui tag