Implementácia sortovateľnej silne typovej kolekcie, časť 2

Publikoval Michal Kočí dňa 20.2.2005 o 22:50 v kategórii .Net

Tak a začnime teda implementáciou, aby nezostalo len pri minule zmieňovanej troške teórie. Začnem s tým, že budem robiť kolekciu objektov typu Customer, s tým, že pre iný typ sa len zmení typ Customer na iný typ. Ako som už spomínal, môžete si napríklad vytvoriť šablónu pre CodeSmith, ktorá tento typ bude meniť za Vás.

Začnem asi tým, ako budú prvky v kolekcii ukladané. Abstrakná trieda implementuje dve vlastnosti: InnerList a List. InnerList je typu ArrayList a mala by sa použiť na zhromaždenie jednotlivých prvkov. Navyše si aj ja myslím, že sa pre tieto účely dokonale hodí. Tým, že sú prvky ukladané do ArrayList, je automaticky dosiahnuté to, že môžete pridávať nové, editovať a odstraňovať stávajúce prvky. Ovšem, ja som vychádzal z predpokladu, že to nie vždy musí byť žiadúce. Aj preto som vlastnosti AllowNew, AllowEdit a AllowRemove implementoval nasledovne:

private bool allowNew = true;
private bool allowEdit = true;
private bool allowRemove = true;

public bool AllowNew
{
  get { return allowNew; }
  set { allowNew = value; }
}

public bool AllowEdit
{
  get { return allowEdit; }
  set { allowEdit = value; }
}

public bool AllowRemove
{
  get { return allowRemove; }
  set { allowRemove = value; }
}

Nie sú to teda tri vlastnosti iba na čítanie, ako by sa mohlo pri implementácii javiť ako vhodné, ale ich hodnota sa môže aj meniť. Interne sa ich hodnota ukladá v privátnych premenných, odkiaľ sa aj číta a ktorá je implicitne nastavená na true. Dôvodom pre moje rozhodnutie bolo, že ak budem chcieť bindovať kolekciu na DataGrid, nebudem chcieť, aby sa cez DataGrid dali meniť prvky kolekcie. A DataGrid ich zmenu (pridanie, odstránenie) nepovolí v prípade, že hodnota vlastností Allow... je false.

Z toho vyplýva aj ďaľší fakt. Pri pridávaní, zmene a odstraňovaní bude potrebné (pre zachovanie konzistencie triedy) vyvolať vynímku pri pokuse o akciu ak prislúchajúca premená allow... bude mať hodnotu false.

Ako teda bude pracovať metóda Add. Tá bude prijímať premennú typu Customer a túto bude pridávať do kolekcie. Presne v tomto je vidno tú silnú typovosť. Metóda Add najskôr overí, či allowNew je true, ak nie, vyvolá výnimku. Ak áno, vyvolá udalosť, signalizujúcu, že pridávame prvok do kolekcie. Po pridaní prvku opäť vyvolá udalosť, tentoraz signalizujúcu, že pridávanie bolo dokončené. Nakoniec vráti index práve pridaného prvku v kolekcii. Ako uvidíme neskôr, vyvolávanie udalostí bude hrať ešte veľmi dôležitú úlohu.

Metóda AddNew najskôr vytvorý objekt typu Customer (ktorý na konci metódy vráti ako svoju hodnotu) a zavolá práve spomínanú metódu Add.

Pridanie prvku do kolekcie je možné aj metódou Insert. Tá funguje na rovnakom princípe ako metóda Add:

public int Add(Customer c)
{
  if(!allowNew) throw new NotSupportedException();

  OnInsert(InnerList.Count, c);
  int index = InnerList.Add(c);
  OnInsertComplete(index, c);

  return index;
}

public object AddNew()
{
  Customer c = new Customer();
  Add(c);

  return c;
}

public void Insert(int index, Customer c)
{
  if(!allowNew) throw NotSupportedException();

  OnInsert(index, c);
  InnerList.Insert(index, c);
  OnInsertComplete(index, c);
}

Na zistenie indexu prvku v kolekcii si bude môcť používateľ triedy zavolať metódu IndexOf, ktorej predá objekt typu Customer a vráti sa mu požadovaný index. Táto metóda je implementačne jednoduchá, pretože vnútorne iba zavolá metódu s rovnakým názvom z vnútorného ArrayList-u (prístupného cez vlastnosť InnerList). To isté v bledomodrom robí aj metóda Contains:

public int IndexOf(object value)
{
  return InnerList.IndexOf(value);
}

public bool Contains(object value)
{
  return InnerList.Contains(value);
}

Odstránenie prvku z kolekciu umožňujú metódy Remove a RemoveAt. Prvá si pomocou metódy IndexOf zistí index prvku, pošle signál (udalosťou) že sa ide ostraňovať prvok z kolekcie. Odstráni ho zase vyvolaním metódy Remove vnútornej kolekcie (InnerList) a pošle signál (opäť udalosťou) že bol odstránený prvok z kolekcie.  Index prvku zistení metódou IndexOf sa nepoužíva pri samotnom odstraňovaní, ale ako parameter metódy, ktorá vyvolá udalosť. To aby príjemca udalosti vedel, ktorý prvok bol odstránený.

Metóda RemoveAt je opäť jednoduchá (ako AddNew) a k svojej práci zneužíva metódu Remove. Najskôr si cez indexer nechá vrátiť objekt tupu Customer na príslušnej pozícii vo vnorenej kolekcii a samotné odstránenie deleguje na metódu Remove.

Odstránenie všetkých prvkov kolekcie zabezpečuje metóda Clear. Táto opäť len odstráni prvky vnútornej kolekcie, pričom pred a po samotnom odstránení volá príslušné udalosti. Všimnite si, že metódy Remove, RemoveAt a Clear opäť kontrolujú, či je odstránenie prvku z kolekcie povolené (allowRemove musí byť true).

public void Remove(object value)
{
  if(!allowRemove) throw new NotSupportedException();
  
  int index = IndexOf(value);

  OnRemove(index, value);
  InnerList.Remove(value);
  OnRemoveComplete(index, value);
}

public new void RemoveAt(int index)
{
  Customer c = this[index];
  Remove(c);
}

public new void Clear()
{
  if(!allowRemove) throw new NotSupportedException();

  OnClear();
  InnerList.Clear();
  OnClearComplete();
}

Implementácia indexera takisto nie je zložitá. Pri get požiadavke sa zas a znova použije indexer vnútornej kolekcie a vrátený objekt sa pretypuje na objekt typu Customer.

Pri set požiadavke nesmieme zabudnúť na implementáciu kontroly, či sa do kolekcie môžu pridávať prvky (allowEdit musí byť true) a musíme opäť vyvolať udalosti pred a po zmene prvku. Navyše, musíme v udalosti odoslať aj starý a nový prvok:

public Customer this[int index]
{
  get { return (Customer) InnerList[index]; }
  set 
  { 
    if(!allowEdit) throw new NotSupportedException();

    Customer cOld = this[index];
    Customer cNew = value;

    OnSet(index, cOld, cNew);
    InnerList[index] = value; 
    OnSetComplete(index, cOld, cNew);
  }
}

A túto skupinu uzatvárajú už len dve vlastnosti. Prvou je vlastnosť IsFixedSize, ktorá nám indikuje, či kolekcia má pevne stanovenú veľkosť. Keď sa nad tým zamyslíme, túto podmienku kolekcia spĺňa za predpokladu, že do nej nemôžeme ani pridať nový prvok a navyše z nej nemôžeme ani stávajúci prvok odstrániť. Druhou vlastnosťou je IsReadOnly. Kolekcia je iba na čítanie v tom prípade, ak nielenže do nej nemôžeme pridať nový prvok a nemožeme z nej ani žiaden prvok odstrániť, my však v nej nemôžeme ani žiaden prvok meniť. Vychádzajúc z týchto predpokladov, implementácia týchto vlastností spočíva (a dáva zmysel) iba v implementácii get metód a vyzerá nasledovne:

public bool IsFixedSize
{
  get {
    return ((!allowNew) && (!allowRemove));
  }
}

public bool IsReadOnly
{
  get 
  {
    return ((!allowEdit) && (!allowNew) && (!allowRemove));
  }
}

Tým som dnes vykryl prvé dve skupiny. V ďaľšom príspevku sa pohnem zase o kus ďalej...

Mohlo by ťa tiež zaujímať

Páčil sa ti príspevok?

Zdieľaj príspevok alebo si ho odlož na neskôr

Sleduj ma

Ak nechceš premeškať príspevky ako je tento, sleduj ma na Twitteri, alebo ak máš RSS čítačku, môžeš sledovať môj RSS kanál.