Estendi, avvolgi o entrambi per aggiungere generici a una classe che avrebbe dovuto averli?

5

Quindi sto usando un framework C # che ha un ottimo esempio di dove i generici sarebbero utili, tranne che non erano usati. Per ragioni di semplicità, diremo che si trattava di una lista (so che C # ha una sua lista), ma invece di essere Elenco in cui posso specificare il T, lo hanno reso List, risultando in un tipo di controllo da incubo per tutto il codice.

Voglio correggerlo, ma non posso modificare il framework.

Quindi vedo tre modi per risolvere questo problema.

Estendi, a capo o entrambi.

Quindi la domanda di base è, qual è il modo migliore per aggiungere i generici?

Esiste un modello di design particolarmente adatto a questo?

Ora, mentre non sono sicuro che il wrapping o l'estensione siano migliori, se questo fosse un caso semplice, sarei andato con uno di loro e non avrei nemmeno considerato di farlo entrambi.

Ma il CustomList è usato ovunque, e voglio solo sostituirlo a poco a poco.

L'estensione mi consente di usare TypedCustomList come CustomList, in modo che se cambio dove CustomList viene creato da TypedCustomList, in seguito nell'esecuzione del codice il codice può ancora trattarlo come una CustomList.

Tuttavia, non posso trattare un oggetto CustomList come TypedCustomList (anche se assicuro che il tipo generico sia corretto per quella CustomList fornita). (O mi manca come farlo, quale risolverebbe il problema?)

Se lo si avvolge, ora posso lavorare con le CustomList esistenti, ma devo esporre l'oggetto CustomList interno per passarlo ad altro codice (ad esempio, se chiamo un metodo che si aspetta CustomList, devo fare instanceOfTypedCustomList. _customList, non posso passare instanceOfTypedCustomList).

Se lo avvolgo e lo estendo, prendo una CustomList esistente e la trattiamo come una TypedCustomList pur conservando la mia TypedCustomList come lista personalizzata. Ma, come puoi vedere qui sotto, l'opzione "Entrambi" sembra la peggiore.

L'estensione

public class TypedCustomList<T> : CustomList
{
  public TypedCustomList()
    :base()
  {
  }

  public T getElementAt(int i)
  {
    return (T) base.getElementAt(i);
  }

  public void addElement(T element)
  {
    base.addElement(element);
  }

  ...ect.
}

Confezione:

public class TypedCustomList<T>
{
  CustomList _customList { get; private set;}
  public TypedCustomList(CustomList cl)
  {
    _customList = cl;
  }

  public T getElementAt(int i)
  {
    return (T) _customList.getElementAt(i);
  }

  public void addElement(T element)
  {
    _customList.addElement(element);
  }

  ...ect.
}

Entrambi:

public class TypedCustomList<T> : CustomList
{
  private CustomList _customList;

  public TypedCustomList()
    :base()
  {
    _customList = null;
  }

  public TypedCustomList(CustomList cl)
  {
    _customList = cl;
  }

  public T getElementAt(int i)
  {
    if(_customList == null)
    {
      return (T) base.getElementAt(i);
    }
    else
    {
      return (T) _customList.getElementAt(i);
    }
  }

  public void addElement(T element)
  {
    if(_customList == null)
    {
      base.addElement(element);
    }
    else
    {
      _customList.addElement(element);
    }
  }

  ...ect.
}

P.S.

Più sfondo che potrebbe essere rilevante per il problema.

Esiste una classe che ha una CustomList che viene utilizzata in tutta l'applicazione, con diverse parti dell'applicazione con ogni istanza con tipi di oggetto diversi (oltre una dozzina in totale). Voglio che questa classe abbia una sola TypedCustomList per ogni utilizzo. Ma fino a quando non posso essere sicuro che tutti i riferimenti alla sua CustomList sono spariti, volevo aggiungere qualche gestione degli errori. Quindi stavo aggiungendo qualcosa di simile al seguente:

public ClassThatUsesCustomList
{
...
private CusotmList _list;
private TypedCustomList<SomeObject> _SomeObjectCustomList;

public CustomList list
{
    get
    {
      //Already existing code
      if(_list == null)
      {
        _list = new CustomList();
      }

      //My code
      catchMissedGetSet(_list);


      return _list;
    }
    set
    {
      catchMissedGetSet(value);
      _list = value;
    }
}

public SomeObjectCustomList
{
  get
  {
    if(_SomeObjectCustomList == null)
    {
      _SomeObjectCustomList = new TypedCustomList<SomeObject>();
    }

    return _SomeObjectCustomList;
  }
  set
  {
    _SomeObjectCustomList = value;
  }
}


private void catchMissedGetSet(CustomList cl)
{
  bool findMissedSpots = true;

  if(cl == null || cl.Count == 0 || cl[0] is SomeObject)
  {
    SomeObjectCustomList = new SomeObjectCustomList(cl);  //Using wrapping.
    if(findMissedSpots && System.Diagnostics.Debugger.IsAttached)
    {
      System.Diagnostics.Debugger.Break();  
      //Hey, you missed a place.  Use step out to find it.
    }
  }
}


...
}

L'ho fatto per catturare tutti i punti che mi mancavano e per evitare eventuali bug. Mi richiede di avvolgerlo (per quanto posso dire).

    
posta Lawtonfogle 03.12.2014 - 21:26
fonte

1 risposta

1

La risposta è avvolgente, ma qui c'è molto più lavoro che implicito. Capisco perché stai provando a farlo, perché dopo una certa dimensione del codice base può essere davvero un incubo lavorare con il typecasting ogni volta che chiami una funzione di libreria. La sottoclasse di CustomList non è consigliabile, poiché esporrà tutte le funzioni che restituiscono o prevedono un oggetto. Avere una proprietà pubblica come nel secondo esempio è, per lo stesso motivo, anche sconsigliabile. Credo che tu stia affrontando una situazione simile alla seguente:

interface Producer
{
  CustomList produce();
}

interface Consumer
{
  void consume(CustomList list);
}

Ecco come lo avvolgerei:

public class TypedCustomList<T>
{
  private readonly CustomList _customList;
  internal CustomList customList
  {
    get { return _customList; }
  }

  internal TypedCustomList(CustomList cl)
  {
    _customList = cl;
  }

  public T getElementAt(int i)
  {
    return (T) _customList.getElementAt(i);
  }

  public void addElement(T element)
  {
    _customList.addElement(element);
  }

  ... ect.
}

Il produttore:

class TypedProducer<T>
{
  private readonly Producer producer = new Producer();
  public TypedCustomList<T> produce()
  {
    return new TypedCustomList<T>(producer.produce())
  }
}

Il consumatore:

class TypedConsumer<T>
{
  private readonly Consumer consumer = new Consumer();
  public void consume(TypedCustomList<T> list)
  {
    consumer.consume(list.customList);
  }
}

Dovrai mettere le classi di wrapping al proprio assembly, in modo che nascondano le parti internal .

Posso tranquillamente suggerirti questa soluzione, che credo sia generalmente considerata una buona pratica. Dovrai avvolgere il sottoinsieme della libreria che stai utilizzando in questo modo.

    
risposta data 15.01.2015 - 19:57
fonte