Bázová třída pro typové kolekce v .NET Frameworku 2.0
V souvislosti s uvedením generiky v .NET Frameworku se v různých článcích dočtete, jak generika usnadní vytváření a použití typových kolekcí. To je sice pravda, ale v článku se kromě zjednodušených příkladů a frikulínských hlášek o dokonalosti .NETu 2.0 z úst (respektiva pera) excitovaných jedinců po právě dokonaném intimním styku se softwarovou emanací božstva Microsoftu málokdy dovíte, jak by taková typová kolekce měla vypadat v běžné aplikaci.
Proč nevyhovuje obyčejné použití generického typu List? (deklarace typové kolekce objednávek ve tvaru List<Order> orderCollection = new List<Order>()).
- Jednou z dobrých praktik u generik je co nejvíce před uživateli (dalšími vývojáři) skrývat informaci, že pracují s generickým typem. Ač mně syntaxe pro práci s generickými typy připadá intuitivní, nemusí si to myslet všichni, a mnoho vývojářů stále asi raději používá důvěrně známý kód OrderCollection orderCollection = new OrderCollection() místo výše zmíněného kódu List<Order> orderCollection = new List<Order>()). Tento požadavek by ale List<T> splnil - typová kolekce může být potomkem List<T>.
- Ve třídě List nejsou metody Add a Remove virtuální. To znamená, že nemůžete po přidání nebo odebrání položky z/do kolekce vyvolávat vlastní události. A to je problém, protože po přidání/odebrání položek z kolekce můžeme chtít nastavit/zrušit rodiče nebo přepočítát sumární hodnoty za položky v kolekci apod.
Bázová třída pro všechny kolekce, kterou používám, je otevřeným generickým typem a jejím předkem je třída Collection<T> z jmenného prostoru System.Collections.ObjectModel. Třída Collection<T> nabízí virtuální chráněné metody InsertItem a RemoveItem, ve kterých můžete vyvolávat potřebné události. Jestliže používate návrhový vzor Layer SuperType a máte tedy jednu bázovou třídu pro všechny business objekty (BusinessObjectBase), je vhodné, aby bázová třída pro kolekce kladla na generický typ T omezení, že musí být potomkem třídy BusinessObjectBase. Omezení slouží k tomu, abyste ve svých kolekcích mohli intuitivně používat všechny atributy a metody deklarované na úrovni společného předka BusinessObjectBase.
Kód kolekce:
public class BusinessCollectionBase<T> : Collection<T>
where T : BusinessObjectBase
{
#region Events
public event EventHandler<CollectionChangeEventArgs> ItemAdded;
public event EventHandler<CollectionChangeEventArgs> ItemRemoved;
#endregion Events
#region Protected methods
/// <summary>
/// Přidání položky do kolekce
/// </summary>
/// <param name="index">Index položky</param>
/// <param name="item">Vkládaná položka</param>
protected override void InsertItem(int index, T item)
{
base.InsertItem(index, item);
OnItemAdded(new CollectionChangeEventArgs(item));
}
/// <summary>
/// Přidání položky do kolekce
/// </summary>
/// <param name="index">Index položky</param>
protected override void RemoveItem(int index)
{
T item = this[index];
base.RemoveItem(index);
OnItemRemoved(new CollectionChangeEventArgs(item));
}
/// <summary>
/// Metoda odpovědná za vyvolání události ItemRemoved
/// </summary>
/// <param name="e">Argumenty události</param>
protected void OnItemRemoved(CollectionChangeEventArgs e)
{
if (ItemRemoved != null)
{
ItemRemoved(this, e);
}
}
/// <summary>
/// Metoda odpovědná za vyvolání události ItemAdded
/// </summary>
/// <param name="e"></param>
protected void OnItemAdded(CollectionChangeEventArgs e)
{
if (ItemAdded != null)
{
ItemAdded(this, e);
}
}
/// <summary>
///
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
public virtual T FindById(Guid id)
{
List<T> mylist = (List<T>) Items;
T objectMeetsCriteria = null;
objectMeetsCriteria = mylist.Find(delegate(T iterObject)
{
if (iterObject.Id == id)
{
return true;
}
else
{
return false;
}
});
return objectMeetsCriteria;
}
/// <summary>
/// Nalezení všech objektů splňujících zadanou podmínku
/// </summary>
/// <param name="criteria">Podmínka výběru</param>
/// <returns>List s objekty, které splňují zadanou podmínku</returns
public virtual List<T> Find(Predicate<T> criteria)
{
List<T> mylist = (List<T>) Items;
return (mylist.FindAll(criteria));
}
/// <summary>
/// Spuštění akce nad všemi elementy v kolekci
/// </summary>
/// <param name="action">Akce, která se má provést</param>
public virtual void ForEach(Action<T> action)
{
List<T> mylist = (List<T>)Items;
mylist.ForEach(action);
}
#endregion Protected methods
}
}
Jak vidíme:
- Třída BusinessCollectionBase je potomkem třídy Collection<T> a vyžaduje, aby typ T byl vždy potomkem BusinessObjectBase. Motivace pro toto rozhodnutí jspu popsány výše.
- Nadeklarovali jsme dvě události ItemAdded a ItemRemoved, které jsou vyvolávany v přepsaných metodách InsertItem (ItemAdded) a RemoveItem (ItemRemoved). Pokud bych měl zájem, mohu jednoduše přidat i události vyvolávané před přidáním/odebráním položky z kolekce.
- Do rozhraní BusinessCollectionBase jsem také přidal několik zajímavých metod.
- Metoda FindById nalezne podle předaného Id (unikátní identifikátor instance) objekt v kolekci. V této metodě opět používáme nové konstrukce z .Net Frameworku 2.0. Implementační objekt kolekce (starý známý List<T> ) vystavuje metodu Find, která očekává generického delegáta Predicate z jmenného prostoru System.
public delegate bool Predicate( T obj);Delegát Predicate je "ukazatelem" na metodu, která očekává jeden generický argument T a vrací true nebo false. Delegát Predicate tedy zastupuje metodu s podmínkou, která je pro předaný argument obj pravdivá nebo nepravdivá. My pro vytvoření podmínky použijeme anonymní metodu, která vrátí true pouze tehdy, když se Id objektu v kolekci shoduje s předaným Id. Atribut Id u generického typu T kolekce můžeme používat právě proto, že jsme zavedli pro typ T omezení (musíš být potomkem BusinessObjectBase) a atribut Id je deklarován ve třídě BusinessOBjectBase.
- Pro pokročilejší operace s elementy kolekce jsme z objektu List<T> zveřejnili metody FindAll A ForEach. Metoda FindAll podle předané podmínky (delegát Predicate) nalezne a vrátí všechny objekty, které jí vyhovují. Metoda ForEach spustí pro všchny elementy v kolekci "akci - činnost" implementovanou v metodě, na níž "ukazuje" další užitečný delegát Action<T>.
public delegate void Action<T> ( T obj);
Když tedy budete chtít všechny objekty v kolekci zrušit, místo psaní cyklu foreach napíšete kód podobný tomuto:
myCol.ForEach(delegate(OrderItem item)
{
item.Discard();
});
Vytváření vlastních typových kolekcí je jednoduché:
/// <summary>
/// Kolekce objektů OrderItem
/// </summary>
public class OrderItemCollection : BusinessCollectionBase<OrderItem>
{
}
Pro úplnost sem dávám triviální kód třídy pro argumenty události CollectionChanged.
/// <summary>
/// Objekt v kolekci
/// </summary>
public class CollectionChangeEventArgs : EventArgs
{
#region Private variables
private BusinessObjectBase m_collectionObject;
#endregion Private variables
#region Constructors
/// <summary>
/// Konstruktor
/// </summary>
/// <param name="collectionObjekt">Objekt v kolekci, kterého se událost týká</param>
public CollectionChangeEventArgs(BusinessObjectBase collectionObject)
{
BasicValidations.AssertNotNull(collectionObject, "collectionObject");
m_collectionObject = collectionObject;
}
/// <summary>
/// Objekt v kolekci, kterého se událost týká
/// </summary>
public BusinessObjectBase CollectionObject
{
get
{
return m_collectionObject;
}
}
#endregion Constructors
}
Související články:
Bázová třída pro business objekty - návrhový vzor Layer Supertype
Cachování řádků z databáze pro business objekty - třída DataCacheHelper
Ukázka použití třídy BusinessObjectBase
>
Sunday, April 9, 2006 2:34:33 PM (Central Europe Standard Time, UTC+01:00)
.NET Framework | Compact .Net Framework | Návrhové vzory