Přinejmenším na veřejnosti. Mnohdy se v různých APIs můžete setkat
s tím, že různé objekty veřejně vystavují metody/vlasnosti, které mají
návratový typ List<T>, v lepším případě
IList<T>. Krásná ukázka nerozvážného návrhu.
Proč je špatné vracet
List<T>
Když vracíte List<T>, znamená to, že říkáte
všechno o vnitřní implementaci a zavíráte si vrátka pro její možnou
změnu. Generický List sice neni nerozšiřitelný (sealed), ale
ani nebyl při návrhu moc k rozšiřování zamýšlen. Navíc tim porušujute
jeden ze základních principů OOP – a to
zapouzdření. Dále tim umožňujete konzumentovi vaši kolekci
přímo měnit.
Ukázka naivního/lenošného přístupu
Pro ukázku mějme třídu reprezentující uživatele User,
kterému můžeme přiřadit uživatelské role Role. Naivní
přístup:
public class User {
public List<Role> Roles { get; private set; }
}
public class UserConsumerCode {
public void SomeMethod {
var user = new User();
user.Roles.Add(Role.Admin);
// ...
user.Roles.Remove(Role.Reviewer);
// ...
var isAdmin = user.Roles.Contains(Role.Admin);
}
}
Jak vidíte, klient má nad kolekcí rolí uživatele plnou moc. Může si
přidávat, odebírat, přetřizovat jak se mu zlíbí. A my nemáme nejmenší
šanci tyto celkem důležité operace jakkoli ovlivnit, nebo u nich vědět.
Kdybychom změnili typ kolekce Roles na IList<Role>,
tak sice můžeme změnit vnitřní implementaci a místo
List<T> použít třeba LinkedList<T>, ale
stále zveřejňujeme příliš mnoho. Jedinou výhodou tohoto řešení je, že
jsme nemuseli napsat moc kódu a máme hodně funkcionality.
Jak zlepšit náš kód?
Rovnou přeskočím použití ICollection<T>, a přejdeme
k tomu, jak by to mělo vypadat. Zachováme si zapouzdření, uzavřeme se
vůči nechtěné manipulaci z vnějšku a otevřeme se snadným vnitřním
změnám a rozšiřitelnosti (Open/closed
principle).
public class User {
private readonly ICollection<Role> _roles;
public User() {
_roles = new List<Role>();
}
public IEnumerable<Role> Roles {
get { return _roles.ToArray(); }
}
public void AddRole(Role role) {
_roles.Add(role);
}
public void RemoveRole(Role role) {
_role.Remove(role);
}
public bool IsInRole(Role role) {
return _roles.Contains(role);
}
}
Pěkně jsme obohatili a zpřehlednili naše API (zápis
user.IsInRole(Role.Admin) říká mnohem více, než
user.Roles.Contains(Role.Admin), navíc neporušuje Demeterův zákon),
skryli jsme implementační detaily a zároveň nechali otevřená vrátka pro
snadnou výměnu vnitřní implementace… Dalším krokem by měly být
kontrakty manipulačních metod, aby byly bezpečné. Dále pak můžeme přidat
vyvolávání událostí, abychom o manipulaci věděli. Míst pro
rozšíření je tu spousta.
Hloupá otázka na závěr
„Ale, jak teď zjistim, kolik rolí má uživatel přiřazených, když
IEnumerablenemá vlastnostCountaniLength?“using System.Linq;;)











Komentáře
Michal
09.10 - 1. srpna 2009 | #
Aleš Roubíček
09.32 - 1. srpna 2009 | #
Tomáš Jecha
17.29 - 1. srpna 2009 | #
Aleš Roubíček
18.04 - 1. srpna 2009 | #
Tomáš Jecha
18.37 - 1. srpna 2009 | #
Aleš Roubíček
20.49 - 1. srpna 2009 | #
Augi
21.19 - 1. srpna 2009 | #
Aleš Roubíček
22.01 - 1. srpna 2009 | #
Rene Stein
22.20 - 1. srpna 2009 | #
stej
10.05 - 3. srpna 2009 | #
Aleš Roubíček
10.27 - 3. srpna 2009 | #
Steve
20.33 - 29. srpna 2009 | #
Aleš Roubíček
08.31 - 30. srpna 2009 | #
Steve
23.16 - 30. srpna 2009 | #
Místo pro tvůj názor