Keerukate parameetrite klasside loomine Lazy<T> abil
12.11.2011 | GunnarEilane vaimuharjutus lõppes mul sellega, et sündis ka midagi kasulikku, mida oma rakendustes kasutada saate. Ma mängisin natuke Lazy<T> klassiga ja aretasin selle ümber oma klassi, mida saate kasutada näiteks keerukamate parameetrite laadimiseks. Tulemus see, et osa koodi on võimalik jälle tõsta eraldi klassidesse üle ja süsteem ise muutub sammukese võrra lihtsamaks.
Lazy<T> kasutamine
Tüüpiline kood Lazy<T> kasutamiseks on midagi sellist:
{
static void Main(string[] args)
{
var temperature = new Lazy<int>(LoadMinimalTemperature);
Console.WriteLine("Minimal room temperature: " + temperature.Value);
Console.ReadLine();
}
protected static int LoadMinimalTemperature()
{
var returnValue = 0;
// Do complex stuff here
return true;
}
}
Mis mulle siin väga ei meeldi, on see väärtuse laadimise meetod. Kui tegemist on lihtsa ja lühida koodiga, siis pole probleemi – me võima kasutada anonüümset delegaati. Kui aga tegemist on keerukama ja mahukama koodiga, siis on selge, et anonüümne delegaat on vale vastus.
Kui meil on süsteemis mitmeid keerukaid parameetreid, mida soovime Lazy<T> abil laadida, siis jõuame peatselt olukorda, kus meil mõni klass sisaldab ohtralt loogikat, mis kõik on seotud erinevate parameetritega. Sellega rikume me sellist asja nagu Single Responsibility Principle.
Baasklassi loomine keerukatele parameetritele
Et SRP vastu mitte eksida, on meil vaja luua keerukate parameetrite jaoks eraldi klassid. Peale mõningast mängimist koodiga leidsin ma seda, et kõige parem idee on luua Lazy<T> ümber lihtne wrapper ning kasutada sellist mustrit nagu Template Method.
{
private Lazy<T> _lazyParam;
public LazyParameter()
{
_lazyParam = new Lazy<T>(Load);
}
protected abstract T Load();
public T Value
{
get { return _lazyParam.Value; }
}
}
Nüüd on meil parameetrite jaoks baasklass, mis peidab meie eest Lazy<T> ära ja koos sellega kõik selle kuus konstruktorit. See konstruktorite hunnik on teine põhjus, miks ma Lazy<T> kasutamist programmi API-st väljapool näha ei taha. Wrapper osa klassist on see, et me toome baasklassi avalikku liidesesse Value omaduse.
Loome parameetri klassi
Järgmiseks loome esimese implementatsiooni, mis meie parameetrite baasklassi kasutab. Oletame, et meil on mõne keeruka protokolliga seadme käest vaja küsida minimaalne ruumi temperatuur.
{
protected override int Load()
{
var returnValue = 0;
// Do complex stuff here
return returnValue;
}
}
Parameetri klass on küllaltki minimaalne ja koosneb peamiselt funktsionaalsusest, mis on vajalik parameetri väärtuse leidmiseks.
Parameetri klassi kasutamine
Vaatame korra ühte primitiivset näidet meie parameetri klassi kasutamisest.
{
static void Main(string[] args)
{
var parameter = new MinimalRoomTemperature();
Console.WriteLine("Minimal room temperature: " + parameter.Value);
Console.ReadLine();
}
}
Kui mõtleme sellele, et keerukaid parameetreid tuleb näiteks 10 tükki juurde, siis näeme, et erilist hoopi see meie koodile ei annaks – see jääks ikka loetavaks (kuigi siinkohal on aeg mõelda sellisele koodi kohendusele nagu Introduce Parameter Object).
Üks märkus veel. Lazy<T> ei hakka laadima parameetri väärtust hetkel, mil me Lazy<T> põhjal objekti loome. Ehk siis – Lazy<T> ei lae väärtust konstruktoris. Selleks, et väärtus laetaks, tuleb kutsuda Lazy<T> küljest Value omadust.
Kokkuvõtteks
Me alustasime sellest, et kasutasime Lazy<T> klassi nii nagu suur ja lai internet meile õpetab. Tuues juurde keerukama konteksti nagu mahukas parameetri laadimine ei lasknud me sel korral koodil kasvada hoomamatuks läbuks, vaid jätkasime tehnilise disainiga. Kasutades wrapper-it ja Template Method mustrit tegime me valmis baasklassi, mis sisemiselt koordineerib Lazy<T> abil parameetri väärtuse laadimist. Baasklassi peale ehitasime me lihtsa struktuuriga klassi, mis sisaldab kõike vajalikku koodi parameetri väärtuse laadimiseks. Eraldi klass on hea seepärast, et siis on kõik parameetriga seotud kood (meil võib olla mitmeid meetode ja omadusi) samas klassis.

29.11.2011 kell 01:53
Hmm, selle asemel, et anda argumendiks funktsioon, eelistasid sa luua ühe abiklassi ja selle alamklassi, kus üks meetod on override’itud …
Mina tegutsen Javas ja justnimelt ootan aega, kus ma ei pea enam selliseid ümber-nurga samme tegema, vaid saan mugavalt first-class funktsioonidega toimetada
29.11.2011 kell 02:52
Iseenesest saab ka lihtsalt Func-i kasutada selliste olukordade jaoks, seda lähenemist olen mina kasutanud, aga kuidas ja kust selle Func-i sisu ette tuleb, on juba iseasi
Oleneb ka sellest mida mõeldakse keerukama loogika all, sest tavaliselt eeldab see ka mingeid muid sõltuvusi, mis viib lõpuks selleni, et koodis ei saa lihtsalt new-da uut lazy parameetri objekti. Ehk siis sellisel juhul ma kasutaksin mingit sellist konstruktsiooni nagu Func(() => _roomTemperatureService.GetMinimal()) või kui Lazy juurde jääda, siis new Lazy(() => _roomTemperatureService.GetMinimal())