condividi la logica del metodo lungo le classi senza ereditare dalla classe astratta

1

In alcune lingue (ad esempio C #) una classe può avere sempre solo 1 classe base che sembra un problema per quello che sto cercando di fare. Ti darò un esempio di quello che sto cercando di fare, spero che questo chiarisca il mio problema.

Ho molti oggetti dati, alcuni di questi oggetti dati hanno una gerarchia, possono avere un genitore o un figlio. Per queste classi ho creato una classe astratta che ha 2 liste (1 dei genitori e uno dei bambini) ha anche alcuni metodi: AddChild() , AddParent() , RemoveChild() , RemoveParent() .

esempio veloce:

 Owner.AddChild(child):<br>
 Adds the child to the collection of childs of 'Owner'<br>
 It also adds 'Owner' to the collection of parents of 'child'.

Per assicurarmi che tutte le classi che hanno componenti gerarchici facciano questo, ho creato una classe astratta che ereditano tutte, quindi nessuno si dimentica di farlo in entrambi i modi.

Ho anche creato una classe per gli oggetti che voglio aggiornare. Questa è una classe astratta non necessariamente per la parte di aggiornamento ma per la parte uguale poiché ho bisogno che tutte queste classi sovrascrivano il metodo Equals(object obj) e GetHashcode() e non è possibile definire una sovrascrittura in un'interfaccia (almeno non in C #)

Ora chiaramente sto sbagliando tutto questo, quindi la mia domanda come faccio a progettare le mie classi in modo che ottengano le funzionalità di cui hanno bisogno (e condividono con altre classi) senza creare una classe astratta.

    
posta Vincent 10.04.2017 - 13:54
fonte

2 risposte

5

To make sure all classes which have hierarchical components do this I created an abstract class which they all inherit so no one forgets to do it both ways.

Un'interfaccia dovrebbe anche "ricordare" agli sviluppatori di aggiungere quei metodi.

.. how do I design my classes so they do get the functionality they need (and share with other classes) without creating an abstract class.

Se la quantità di codice duplicativo è piccola o semplice, usa un'interfaccia e forza semplicemente gli eredi a scrivere le loro implementazioni. Nel tuo esempio, AddChild() , AddParent() , RemoveChild() e RemoveParent() sembrano tutti abbastanza semplici e facili da costruire.

Tuttavia, se le implementazioni sono difficili da ripetere, puoi fornire una classe "interna" (come TreeNode ) per gli ereditari da includere:

class DomNodeOrSomething<T> : ITreeNode
{
  private TreeNode data = new TreeNode<T>();

  void AddChild(T item)
  {
    data.AddChild(item);
  }

  void AddParent(T item)
  {
    data.AddParent(item);
  }

  void RemoveChild(T item)
  {
    data.RemoveChild(item);
  }

  void RemoveParent(T item)
  {
    data.RemoveParent(item);
  }
}

E, se questo non fa galleggiare la tua barca, usa metodi di estensione .

Nell'esempio da MS, un metodo WordCount() viene aggiunto all'interfaccia String . Quando questo spazio dei nomi è incluso, tutti i String avranno "automagicamente" un metodo .WordCount() che li ciondola.

namespace ExtensionMethods
{
    public static class MyExtensions
    {
        public static int WordCount(this String str)
        {
            return str.Split(new char[] { ' ', '.', '?' }, 
                             StringSplitOptions.RemoveEmptyEntries).Length;
        }
    }   
}

Credo che tu possa fare lo stesso genere di cose per qualsiasi interfaccia. Qualcosa come:

namespace ExtensionMethods
{
  public static class TreeNodeExtensions
  {
    public static void AddChild(this ITreeNode self, ITreeNode item)
    {
      self.children.add(item);
    }
  }
}

Ciò richiederebbe comunque che gli ereditari della forza dell'interfaccia ITreeNode forniscano proprietà comuni che i metodi di estensione possono manipolare.

    
risposta data 10.04.2017 - 16:10
fonte
0

Tipicamente quando ho bisogno di implementare un progetto, uso i parametri del modello generico nella classe base, così come nelle classi di raccolta, per specificare il tipo di bambino e / o il tipo genitore. Se si utilizzano i parametri del tipo, il codice comune nella classe base può essere riutilizzato indipendentemente dal tipo di figlio o genitore.

Detto questo, di solito è vantaggioso avere una classe base non generica per entrambi i bambini, i genitori o entrambi, per semplificare la progettazione. Potrebbe essere ancora necessario eseguire il cast dinamico occasionalmente. Inoltre, probabilmente hai ancora bisogno di interfacce, se il tipo genitore può variare in molti modi.

È possibile farlo interamente senza classi base, usando le tecniche wrapper descritte in un'altra risposta, ma io preferisco la soluzione più semplice. Il codice seguente utilizza un'interfaccia per il tipo genitore, per consentire alla classe di base di variare.

public interface IParent<TChild>
    where TChild : class
{
    /// <summary>
    /// Invoked after a change occurs in a <see cref="ChildCollection{TParent, TChild}"/>.
    /// </summary>
    /// <param name="item">The relavent <typeparamref name="TChild"/>.</param>
    /// <param name="changeType">The type of change that occured.</param>
    void OnCollectionChanged(TChild item, CollectionChangeType changeType);
}

public class Model
{
    object _parent;

    protected Model() { }

    /// <summary>
    /// Raised when this <see cref="Model"/> changes.
    /// </summary>
    public event EventHandler Changed;

    internal object ObjectParent
    {
        get { return _parent; }
        set { _parent = value; }
    }

    /// <summary>
    /// Call this method when the <see cref="Model"/> has changed.
    /// Do not call if you use <see cref="Model.SetField{T}"/>:
    /// SetField calls OnChanged automatically.
    /// </summary>
    /// <remarks>
    /// If the parent is a <see cref="Model"/>, 
    /// <see cref="OnChildChanged"/> is called on the parent.
    /// </remarks>
    protected virtual void OnChanged()
    {
        Model modelParent = _parent as Model;
        if (modelParent != null)
            modelParent.OnChildChanged(this);
        if (Changed != null)
            Changed(this, EventArgs.Empty);
    }

    /// <summary>
    /// Called after a child <see cref="Model"/> has changed.
    /// </summary>
    /// <param name="item">The child object that changed.</param>
    protected internal virtual void OnChildChanged(Model item)
    {
        OnChanged();
    }

    /// <summary>
    /// Called after a collection of child <see cref="Model"/>s has changed.
    /// </summary>
    /// <param name="item">The relevant child object.</param>
    /// <param name="changeType">The type of change that occured.</param>
    protected internal virtual void OnCollectionChanged(Model item, CollectionChangeType changeType)
    {
        OnChanged();
    }

    /// <summary>
    /// Set the value of a field, calling <see cref="OnChanged"/> if the value changes.
    /// </summary>
    /// <typeparam name="T">Type of the field set.</typeparam>
    /// <param name="field">A reference to the field to be modified.</param>
    /// <param name="value"></param>
    /// <returns></returns>
    protected bool SetField<T>(ref T field, T value)
        where T : IEquatable<T>
    {
        if (EqualityComparer<T>.Default.Equals(field, value))
            return false;
        field = value;
        OnChanged();
        return true;
    }
}

/// <summary>
/// Base class for a model (non-visual) object contained
/// in a <see cref="ChildCollection{TChild, TParent}"/>.
/// </summary>
/// <typeparam name="TParent">
/// Type of the object containing a <see cref="ChildCollection{TChild, TParent}"/>.
/// </typeparam>
public abstract class Child<TParent> : Model
    where TParent : class
{
    /// <summary>
    /// The object owning this <see cref="Child{TParent}"/>.
    /// </summary>
    [Browsable(false)]
    [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
    public TParent Parent
    {
        get { return (TParent)ObjectParent; }
    }

    /// <summary>Set the <see cref="Child{TParent}.Parent"/> property.</summary>
    /// <param name="parent">The new <see cref="Child{TParent}.Parent"/> value.</param>
    protected internal virtual void Attach(TParent parent)
    {
        Check.ArgumentNotNull(parent, "parent");
        Check.Operation(ObjectParent == null, "Already added to another collection.");
        ObjectParent = parent;
    }

    /// <summary>
    /// Set the <see cref="Child{TParent}.Parent"/> property to <see langword="null"/>.
    /// </summary>
    protected internal virtual void Detach()
    {
        Check.Operation(ObjectParent != null);
        ObjectParent = null;
    }
}

/// <summary>
/// A collection of <see cref="Child{TParent}"/> objects.
/// </summary>
/// <typeparam name="TChild">The type of the child object.</typeparam>
/// <typeparam name="TParent">The type of the parent object containing the collection.</typeparam>
public abstract class ChildCollection<TChild, TParent> : ICollection<TChild>
    where TChild : Child<TParent>
    where TParent : class
{
    readonly TParent _parent;
    readonly List<TChild> _items;

    /// <summary>
    /// Initialize a new instance of the <see cref="ChildCollection{TChild, TParent}"/> class.
    /// </summary>
    /// <param name="parent">The parent of the new collection.</param>
    protected ChildCollection(TParent parent)
    {
        Check.ArgumentNotNull(parent, "parent");
        _parent = parent;
        _items = new List<TChild>();
    }

    /// <summary>
    /// The number of items in this <see cref="ChildCollection{TChild, TParent}"/>.
    /// </summary>
    public int Count { get { return _items.Count; } }

    /// <summary>
    /// Get the <typeparamref name="TChild"/> at the specified index.
    /// </summary>
    /// <param name="index">Index of a <typeparamref name="TChild"/>.</param>
    /// <returns>The <typeparamref name="TChild"/> at <paramref name="index"/>.</returns>
    public virtual TChild this[int index]
    {
        get { return _items[index]; }
    }

    /// <summary>
    /// Is this <see cref="ChildCollection{TChild, TParent}"/> read-only?
    /// Always returns false.
    /// </summary>
    public bool IsReadOnly { get { return false; } }

    /// <summary>
    /// Add the specified <typeparamref name="TChild"/> to this 
    /// <see cref="ChildCollection{TChild, TParent}"/>.
    /// </summary>
    /// <param name="item">The <typeparamref name="TChild"/> to be added.</param>
    public void Add(TChild item)
    {
        Check.ArgumentNotNull(item, "item");
        AddCore(item);
        OnChanged(item, CollectionChangeType.Added);
    }

    /// <summary>
    /// Add all <typeparamref name="TChild"/> objects in the specified collection
    /// to this <see cref="ChildCollection{TChild, TParent}"/>.
    /// </summary>
    /// <param name="items">The <typeparamref name="TChild"/> objects to be added.</param>
    public void AddRange(IEnumerable<TChild> items)
    {
        Check.ArgumentNotNull(items, "items");
        foreach (TChild item in items)
            AddCore(item);
        OnChanged(null, CollectionChangeType.Added);
    }

    void AddCore(TChild item)
    {
        if (Contains(item))
            Remove(item);
        item.Attach(_parent);
        _items.Add(item);
    }

    /// <summary>
    /// Is the specified <typeparamref name="TChild"/> contained in this 
    /// <see cref="ChildCollection{TChild, TParent}"/>?
    /// </summary>
    /// <param name="item">The <typeparamref name="TChild"/> to be found.</param>
    /// <returns>
    /// <see langword="true"/> if <paramref name="item"/> is contained in this
    /// <see cref="ChildCollection{TChild, TParent}"/>; otherwise, <see langword="false"/>.
    /// </returns>
    public bool Contains(TChild item)
    {
        return item.Parent == _parent;
    }

    /// <summary>
    /// Returns the index of the specified <typeparamref name="TChild"/> in this 
    /// <see cref="ChildCollection{TChild, TParent}"/>.
    /// </summary>
    /// <param name="item">The <typeparamref name="TChild"/> whose index is returned.</param>
    /// <returns>
    /// The index of <paramref name="item"/>, if found; -1 if not found. 
    /// </returns>
    public int IndexOf(TChild item)
    {
        return Contains(item) ? _items.IndexOf(item) : -1;
    }

    /// <summary>
    /// Remove the specified <typeparamref name="TChild"/> from this 
    /// <see cref="ChildCollection{TChild, TParent}"/>.
    /// </summary>
    /// <param name="item">The <typeparamref name="TChild"/> to be removed.</param>
    /// <returns>
    /// <see langword="true"/> if <paramref name="item"/> was removed;
    /// <see langword="false"/> if <paramref name="item"/> is not contained
    /// in this <see cref="ChildCollection{TChild, TParent}"/>.
    /// </returns>
    public bool Remove(TChild item)
    {
        Check.ArgumentNotNull(item, "item");
        int index = IndexOf(item);
        if (index < 0)
            return false;
        RemoveAt(index);
        return true;
    }

    /// <summary>
    /// Remove the item at the specified index.
    /// </summary>
    /// <param name="index">The index of the item to be removed.</param>
    public virtual void RemoveAt(int index)
    {
        TChild item = _items[index];
        item.Detach();
        _items.RemoveAt(index);
        OnChanged(item, CollectionChangeType.Removed);
        if (_items.Count == 0)
            OnChanged(null, CollectionChangeType.AllRemoved);
    }

    /// <summary>
    /// Insert a <typeparamref name="TChild"/>
    /// after a different <typeparamref name="TChild"/> in this 
    /// <see cref="ChildCollection{TChild, TParent}"/>.
    /// </summary>
    /// <param name="afterItem">
    /// A <typeparamref name="TChild"/> already in this <see cref="ChildCollection{TChild, TParent}"/>.
    /// </param>
    /// <param name="item">The <typeparamref name="TChild"/> to be inserted.</param>
    public void InsertAfter(TChild afterItem, TChild item)
    {
        int index = 0;
        if (afterItem == null)
            index = 1;
        else
            index = _items.IndexOf(afterItem) + 1;
        Insert(index, item);
    }

    /// <summary>
    /// Insert the specified <typeparamref name="TChild"/> at the specified index.
    /// </summary>
    /// <param name="index">
    /// Zero-based index at which <paramref name="item"/> is to be inserted.
    /// </param>
    /// <param name="item">The <typeparamref name="TChild"/> to be inserted.</param>
    public void Insert(int index, TChild item)
    {
        Check.ArgumentNotNull(item, "item");
        int previousIndex = _items.IndexOf(item);
        if (previousIndex == index)
            return;
        if (previousIndex >= 0)
        {
            _items.RemoveAt(previousIndex);
            if (index > previousIndex)
                --index;
            _items.Insert(index, item);
        }
        else
        {
            _items.Insert(index, item);
            item.Attach(_parent);
        }
        OnChanged(item, CollectionChangeType.Inserted);
    }

    /// <summary>
    /// Remove all <typeparamref name="TChild"/> objects from this
    /// <see cref="ChildCollection{TChild, TParent}"/>.
    /// </summary>
    public void Clear()
    {
        if (Count == 0)
            return;
        foreach (TChild item in _items)
            item.Detach();
        _items.Clear();
        OnChanged(null, CollectionChangeType.AllRemoved);
    }

    /// <summary>
    /// Copy all object references from this
    /// <see cref="ChildCollection{TChild, TParent}"/> to the specified array.
    /// </summary>
    /// <param name="array">Destination where object references are copied.</param>
    /// <param name="arrayIndex">Index in <paramref name="array"/> where copying begins.</param>
    public void CopyTo(TChild[] array, int arrayIndex)
    {
        _items.CopyTo(array, arrayIndex);
    }

    /// <summary>
    /// Get an enumerator suitable for enumerating all <typeparamref name="TChild"/> objects
    /// in this <see cref="ChildCollection{TChild, TParent}"/>.
    /// </summary>
    /// <returns>An enumerator.</returns>
    public IEnumerator<TChild> GetEnumerator()
    {
        return _items.GetEnumerator();
    }

    void OnChanged(TChild item, CollectionChangeType changeType)
    {
        IParent<TChild> parentParent = _parent as IParent<TChild>;
        if (parentParent != null)
            parentParent.OnCollectionChanged(item, changeType);
        Model modelParent = _parent as Model;
        if (modelParent != null)
            modelParent.OnCollectionChanged(item, changeType);
    }
}   
    
risposta data 10.04.2017 - 17:16
fonte