Klávesové zkratky na tomto webu - rozšířené Na obsah stránky

Inversion of Control

11.29 - 27. září 2012 | Moje práce

Inversion of Control je princip, který při psaní svých softwérů uplatňuji zhruba 5 let. Nikdy o něm nějak do hloubky neuvažuju, prostě je to způsob jakým software píšu. Nepotřebuju nikoho přesvědčovat, že zrovna ten můj způsob je nejlepší. Poznají to sami. :) Na začátku jsem pouze potřeboval opustit svou komfotní zónu a přestat být control freak.

Je úžasný, že umíte injektovat závislosti, ale k čemu je vám to dobrý, když netušíte, k čemu je vám to dobrý? ;) Když používáte DI, říká vám něco vzor dekorátor, strategy nebo třeba adaptér? Jestli ne nebo ano, ale nepoužíváte je, serte na DI. „Testovatelnost!“ říkáte? Hmm, existují i jiné techniky a třeba vás ani nebudou tak bolet.

Vítejte ve světě za zrcadlem

Důležité je udělat ten první krok. Obrátit své myšlení naruby. Pak přestat řešit detaily. Pro to, abych udělal tohle potřebuju něco, co umí tohle a tohle. Jak to dělá? Koho to zajímá? Jak se to ke mně dostane? Koho to zajímá? Je to opravdu důležité? Ne, neni. Důležité je, co dělá váš kus kódu, jakou má business value. Čím víc se snaží menežovat, místo aby něco dělal, tím horší ten kód je. To je fakt. Smiřte se s tím.

Moje práce spočívá v tom, že vytvářím různobarvené kousíčky lega. Pak přiložím návod. Ne nějak přesný (zapomeňte na XML, NEON, YAML, JSON a já nevím co ještě), jen řeknu, že červené dvouřadové lištičky jsou támhle v té krabičce, popřípadě se některé můžou ještě válet pod postelí. Nechme stroj ať nás překvapí. Stroj netrpí přehnanou potřebou se kreativně vyjádřit a umí táhnout na branku. Většinou má lepší výsledky než lidi.

A když mu něco chybí, nebojí se ozvat, nesnaží se to nějak zaplácnout něčím, co tam nepatří. Nebojí se ranit mé city a řekne, „tohle jsi posral.“ Cajk. Můžeme jít dál. :) Abyste mohli dosáhnou nirvány, potřebujete dobrý type systém, nebo Ruby. Všechno ostatní je jen nepovedené napodobování, jak Dan rád říká Cargo Cult.

Metafora s legem patří @renestein, já si jí jen drze ukradl.

Přepínání featur a větvení abstrakcí

09.01 - 11. června 2011 | Moje práce

Správa několika verzí aplikace vždy přináší komplexitu, která zvyšuje náklady na vývoj i na údržbu. Běžnou praktikou je, že se zdrojové kódy v repositoři větví (branching) v určitých Milestonech, které se potom stabilizují a po releasu udržují, zatím co v hlavní větvi (trunk, main) se pokračuje ve vývoji. S příchodem DVCS se rozmohlo lokální větvení po featurách, čímž se různorodost verzí aplikace ještě víc rozrostla.

Naproti tomu tu máme agilní praktiky jako je Continuous Integration a Continuous Delivery, které nám říkají, že všichni commitují často do hlavní větve, čímž se předchází integračním problémům. Continuous Delivery navíc říká, že „aplikace má pouze jedinou verzi – tu aktuální.“ Jak to ale udělat, když rozsah některých featur přesahuje rámec sprintu? Ano, můžeme udělat feature branch, ale tím porušujeme pravidlo, že všechno je v hlavní větvi! Poslední verze TeamCity sice podporuje workflow s personal buildy vůči feature branchi, ale to je integrační smell.

Branch by abstraction

Existuje lepší řešení! Proč nevyužít technik OOP jako je polymorfismus a IoC? Je to krapet složitější, než branchování v repositoři, ale zároveň nás to donutí zamyslet se nad architekturou aplikace a ve výsledku bude IMO lepší. V aplikaci můžeme mít nekolik implementací požadované funkcionality, které jsou následně injektovány na základě konfigurace. Některé mohou být stabilní a používat se v produkčním prostředí, zatímco u vývojáře poběží aktuální edge implementace, na které se pracuje.

Takže implementaci kódu máme vyřešenou, ale jak na to v uživatelském rozhraní?

Feature toggles

Pokud naše UI není přímo generováno aplikačním kódem, kde by se dala využít předchozí technika, musíme sáhnout po nečem trochu jiném – přepínačích featur. Začneme tím, že každá feature má svůj jedinečný identifikátor. Každá featura je zařazena do uživatelského rozhraní s tímto identifikátorem a na základě konfigurace je opět rozhodnuto, zda dojde k jejímu zobrazení uživateli nebo ne. Takto se dají nejen snadno odříznout nestabilní featury z produkčního prostředí, ale dá se to využít i k takovým věcem, jako je A/B testování, čí postupné spouštění featury do produkce, jak to znáte z oblíbených služeb jako je Facebook nebo Twitter. ;)

Service Locator

11.44 - 30. května 2009 | ASP.NET 2.0

Znáte vzor Service Locator? Pravděpodobně jste s ním setkali nespočetněkrát aniž byste o tom věděli.

Update

Service locator je code smell. Následující řádky jsou zajímavé jako programátorské cvičení, nedoporučuji ho však používat v praxi. Třída by měla mít co nejméně závislostí a pokud chceme zredukovat počet parametrů konstruktoru, není následující řešení řešením vhodným! Měli bychom přemýšlet o změně návrhu.


Service Locator je návrhový vzor, který je součástí techniky dependency injection / inversion of control. Jeho úkolem je dodat hotové instance služeb na vyžádání. Můžeme si ho představit nějak tak:

public interface IServiceLocator {
  TService GetService<TService>();
  IEnumerable<TService> GetServices<TService>();
}

Jeho úkolem je poskytnout službu daného typu nebo všechny služby, které splňují danný kontrakt. K čemu je to dobré? Dobré je to především pro konfigurovatelné a rozšiřitelné aplikace. Řekněme, že budu chtít do svého redakčního systému přidat možnost oznámení na e-mail, když někdo zadá komentář. První řešení bude nejspíš to přímočaré:

public class CommentsController : Controller {
  public ActionResult AddComment(Comment comment) {
    // validace a persistence komentáře

    var notificator = new EmailNotificator();
    notificator.send(new CommentNotification(comment));

    return Json(comment);
  }
}

Proč nepřidat možnost odesílání SMSek? Tak jo:

public class CommentsController : Controller {
  public ActionResult AddComment(Comment comment) {
    // validace a persistence komentáře

    var emailNotificator = new EmailNotificator();
    emailNotificator.send(new CommentNotification(comment));

    var smsNotificator = new SmsNotificator();
    smslNotificator.send(new CommentNotification(comment));

    return Json(comment);
  }
}

Skvěle, teď ještě posílání na Twitter nebo na FriendFeed a akce nám krásně roste… :) Takže se oprostíme od toho, že dopředu víme, kudy všudy se chceme nechat informovat o nových komentářích a využijeme Service Locator:

public class CommentsController : Controller {

  private readonly IServiceLocator _serviceLocator;

  public CommentsController(IServiceLocator serviceLocator) {
    _serviceLocator = serviceLocator;
  }

  public ActionResult AddComment(Comment comment) {
    // validace a persistence komentáře

    var newCommentNotification = new CommentNotification(comment);
    var notificators = _serviceLocator.GetServices<INotificator>();
    notificators.Each(notificator => notificator.send(newCommentNotification));

    return Json(comment);
  }
}

Tím jsme také vyřešili problém vznikající při constructor injection a to rostoucí počet parametrů konstruktoru s rostoucími závislostmi. Protože service locator nám dokáže obstarat potřebné služby, nemusíme je injektovat zvlášť.

Ještě se vrátím k poznámce v úvodu, kde jsem psal, že jste se určitě s tímto vzorem setkali, aniž byste si toho byli vědomi. O co jde? O Singleton! :) Singleton je speciální případ service locatoru, který vrací službu jediného typu s řízeným životním stylem jedináčka.

To je všechno pěkné, ale jak tedy service locator ví, jak ty služby získat a jakej mají životní styl? Pokud používáte IoC kontejner, odpověd je snadná: service locator si udělám jako fasádu nad kontejnerem a tu do něj zaregistrujeme. Pokud žádný IoC kontejner nepoužíváte, tak si honem nějaký sežeňte! :)

Užití repository v řadičích

10.18 - 25. ledna 2009 | ASP.NET 2.0

Dost už bylo infrastruktury, pojďme si ji trochu užít! :) Dneska si ukážeme jak používat naší infrastrukturu, která by nám měla spoustu věcí usnadnit. Proto jí vlastně tady tvořím.

Dependency injection a bindování modelu

Vytvoříme si řadič, který bude pracovat řekněme s uživateli – to je taková věc, která je nutná snad v každé aplikaci. Ruku na srdce, aplikace bez uživatelů je tak trochu k ničemu. Tak pojďme na to!

[HandleErrors]
public class UsersController : System.Web.Mvc.Controller {

  private IUsersRepository _usersRepository;

  public UsersController(IUsersRepository usersRepository) {
    _usersRepository = usersRepository;
  }

  [ActionName("Rest")]
  [AcceptVerbs(HttpVerbs.Post)]
  public ActionResult Create(FormCollection forms) {

    var user = new User();
    if (ModelState.IsValid == false) {
      throw new ValidationException();
    }

    UpdateModel(user, forms.ToValueProvider());
    _usersRepository.Save(user);

    return Json(user);
  }
}

Krátké a úderné. Jde o klasický řadič, který má závislost na repository uživatelů. Tuto závislost si nechává nainjektovat přes konstruktor. O její životní cyklus se nestará, to řeší IoC kontejner. Pak tu máme akci, která zpracovává POST dotazy řekněme na URL /Users, to teď není podstatné. Tato akce by měla vytvořit nového uživatele z dat, která přišla v POSTu (FormCollection).

Pokud nejsou data v pořádku, tak vyhodíme validační výjimku. O to, jak se tato data mapují na model uživatele, se opět řadič nestará, to je věc model binderu. Pokračujeme dále k uložení uživatele, někam. Kam a jakým způsobem je řadiči opět šumák, to je starost repository. Nakonec pošleme zpátky hotového uživatele jako JSON objekt.

A to je pro dnešek vše… :)

Model binders v ASP.NET MVC

20.15 - 13. prosince 2008 | ASP.NET 2.0

Jednou z pěkných vlastností ASP.NET MVC je, že data která posíláte akci, můžete v kódu získávat přes silně typové parametry metody (akce). Pokud dostatečně dodržujete dané konvence, můžete takto získat i třeba komplexní typy.

To je zařízeno něčím, co se jmenuje model binders. Základem je DefaultModelBinder, který implementuje rozhranní IModelBinder. Dále pak atributy BindAttribute a jeho bratříček k psaní vlastních atributů CustomModelBinderAttribute a v neposlední řadě metody řadiče UpdateModel a TryUpdateModel.

O tom jak používat DefaultModelBinder, si můžete přečíst třeba u ScottaGu. Já bych se spíš chtěl zaměřit na možnosti rozšíření, které nám infrastruktura model binders přináší.

Velkou slabinou konvenčního DefaultModelBinderu je, že ve view musíte dodržovat jmenné konvence vašeho modelu. Sice to může přinést spoustu benefitů, ale i problémů.

Možnosti

Možností, jak bindery používat, je mnoho. Můžete si třeba bindovat z AppSettings nebo z třeba z cookie. I to je pomocí model binders možné. Pojďme se ale podívat na to, jak bindovat váš model na data odeslaná z view s odlišnou jmennou konvencí…

Základy

Vlastní bindery můžeme vytvořit tak, že implementuje rozhraní IModelBinder. Hotovou implementaci pak musíme zaregistrovat při startu aplikace. Pro ukázku si uděláme velice jednoduchý příklad načítání adresy z FormsCollection. Mějme datovou třídu adresa:

public class Address {
    public string Street { get; set; }
    public string StreetNumber { get; set; }
    public string Town { get; set; }
    public string PostalCode { get; set; }
}

K ní si vytvoříme model binder:

public class AddressBinder : IModelBinder {
  public ModelBinderResult BindModel(ModelBindingContext bindingContext) {
    var address = new Address {
      Street = bindingContext.HttpContext.Request["address_street"].Trim(),
      StreetNumber = bindingContext.HttpContext.Request["address_street_number"].Trim(),
      PostalCode = bindingContext.HttpContext.Request["address_postal_code"].Trim(),
      Town = bindingContext.HttpContext.Request["address_town"].Trim(),
    };

    return new ModelBinderResult(address);
  }
}

Který nakonec zaregistrujeme v Global.asax:

protected void Application_Start() {
  ModelBinders.Binders[typeof(Address)] = new AddressBinder();
}

Když teď akci předáme parametr typu Address, bude automaticky vázán pomocí AddressBinderu. Stejně tak, použijeme-li metodu UpdateModel. V některých scénářích zjistíte, že výše uvedený binder, není úplně skvělý, ba co víc, že je v podstatě dost k ničemu. Navíc taky přijdete na to, že když váš model bude trochu bohatší, tak se váš Global.asax pěkně natáhne, navíc pokud budou mít vaše bindery závislosti na jiných službách, začne v tom být pěkný bordel…

Binsor na scénu

Minule jsme si ukázali, jak propojit ASP.NET MVC s IoC kontejnerem a dnes ho využijeme a trochu si ulehčíme práci… Jak jsem již psal, všechny model bindery implementují rozhranní IModelBinder a toho můžeme využít pro jejich registraci do kontejneru, přidáním následujících řádků do souboru Windsor.boo:

for binder in AllTypesBased of IModelBinder("<nazev assembly s Model Bindery>"):
  component binder.Name.ToLower(), IModelBinder, binder

Máme je zaregistrované v kontejneru, ale potřeby psát něco do Global.asax jsme se nezbavili. To je pravda, ale vzápětí to napravíme. Budeme ještě potřebovat generickou bázovou třídu. Proč gerenerickou? No, je v tom takovej fígl – ten prozradím až za chvíli. :) Teď k věci:

public abstract class ModelBinderBase<T> : IModelBinder {
  public Type ModelType {
    get { return typeof(T); }
  }
  public abstract ModelBinderResult BindModel(ModelBindingContext bindingContext);
}

V podstatě jsme rozšířili rozhraní IModelBinder o znalost typu datového objektu se kterým pracuje. Proč? Vzpomeňte si na registraci binderu, kde je klíčem ve slovníku binderů typ datového objektu. Ano, to je on!

Jěště trochu poupravíme náš AddressBinder:

public class AddressBinder : ModelBinderBase<Address> {
  public override ModelBinderResult BindModel(ModelBindingContext bindingContext) {
    var address = new Address {
      Street = bindingContext.HttpContext.Request["address_street"].Trim(),
      StreetNumber = bindingContext.HttpContext.Request["address_street_number"].Trim(),
      PostalCode = bindingContext.HttpContext.Request["address_postal_code"].Trim(),
      Town = bindingContext.HttpContext.Request["address_town"].Trim(),
    };

    return new ModelBinderResult(address);
  }
}

Pořád tu zůstává ta nepěkná závislost na HttpRequestu, je snadno řešitelná, ale našemu příkladu nevadí a vypořádáme se s ní někdy jindy… Takže vraťme se zase k Windsor.boo a na jeho konec přidejme následující řádky:

for modelBinder as duck in IoC.Container.ResolveAll of IModelBinder():
  ModelBinders.Binders[modelBinder.ModelType] = modelBinder

Upozorňuji, že tyto řádky musí být až na konci souboru. Musí se volat, až po tom, co se zaregistrují všechny komponenty, protože tady si vyzvedáváme již hotové bindery z kontejneru a registrujeme je do ASP.NET MVC.

Možná jste si povšimli formulky as duck. Boo je staticky typovaný jazyk, stejně jako C#, jen využívá implicitního typování. V tomto případě nám generická metoda ResolveAll vrací hotové instance, které implementují rozhranní IModelBinder a taky mají tento silný typ. A toto rozhraní neví nic o tom s jakým typem modelu je svázáno.

Naštěstí Boo podporuje duck typing, což nám přidává tak trochu dynamičnost – pozdní vazbu. Já vím, že všechny moje bindery dědí z bázové třídy, která má vlastnost ModelType a s použítím as duck jí můžu zavolat. Tohle je vlastnost, kterou bude C# umět až ve verzi 4.0, do té doby je v něm toto velice těžko řešitelné (osobně jsem se o to ani nepokoušel, ale nejspíš nějak přes reflexi by to jít mělo).

Tím jsme se zbavili nutnosti registrovat každý model binder zvlášť.

Inversion of Control v ASP.NET MVC

10.30 - 24. listopadu 2008 | ASP.NET 2.0

Dneska si ukážeme, jak využít modularity ASP.NET MVC k tomu, abychom mohli snadno integrovat IoC kontejner. Ten pak bude sloužit k tvorbě controllerů a injekci jejich závislostí (dependecy injection). Jako kontejner použijeme Castle Windsor a konfigurační DSL v jazyce Boo – Binsor.

ASP.NET MVC je framework, který si za cíl bere jasné rozdělení odpovědností, modularitu, snadnou rozšiřitelnost a snadnou testovatelnost aplikací nad ním postavených. Což jsou nejpalčivější neduhy „klasického ASP.NET.“

Inversion of Control a Dependency Injection

Inversion of Control (IoC) je tak trochu jiný pohled na programování. Když píšete aplikaci, nepíšete ji jako program, ale jako sadu komponent, kterým „vdechnete život“ pomocí konfiguračního skriptu. Každá komponenta má v systému svoji úlohu, a pokud nám přestane její funkcionalita vyhovovat, lze ji velice snadno nahradit zásahem na jednom místě. Aby to bylo opravdu tak snadné, musí nahrazovaná komponenta splňovat určitý kontrakt – implementovat rozhranní.

Přes tato rozhranní mezi sebou jednotlivé komponenty komunikují, využívají deklarované služby, posílají si data. Jenže, jak mají vědět, kterou komponentu mají za daným rozhranním hledat. Ony to vědět nemusejí, tedy, vlastně by vůbec neměly.

Tak potom kdo? IoC kontejner!

Kontejner je mozkem aplikace. Je to ten jediný, který ví, které komponenty splňují určité kontrakty. Jak se to doví? Při startu kontejner nacpeme komponentami a on nám je později na vyžádání vrací. Dokonce jde tak daleko, že pokud si vyžádáme komponentu, vrátí nám ji včetně všech jejích závislostí a závislosti závislostí. :) Dostanete kompletní graf objektů, které jsou pro danou chvíli potřeba k vykonání dané činnosti a jsou v kontejneru zaregistrované.

Kontejner se tedy stará o dependency injection. Ale není to jediná služba, kterou nám dokáže poskytnout. Jeho další schopností je řídit životnost komponent. Už nikdy více nemusíte psát singletony! Pokud potřebujete singleton, řeknete kontejneru a on se o to postará.

Castle Windsor

Windsor je jedním z takových kontejnerů určený pro dotnet. Jistě najdete spoustu alternativních jako Spring.Net, StructureMap, Unity nebo Ninject. Každý z nich má své výhody, jiné postupy, ale i omezení. Windsor jsem si vybral z několika důvodů:

  1. Je součástí opensource projektu Castle,
  2. tudíž má celkem velkou komunitu uživatelů i vývojářů
  3. a navíc dokáže s dalšími projekty z Castle spolupracovat.
  4. Má širokou škálu způsobů konfigurace: programově, programově přes fluent interface, XML a hlavně pomocí Binsor.

Na toto téma jsem měl menší vnitrofiremní prezentaci. Slajdy z ní si může také prohlédnout.

O Binsoru je ve slajdech také zmínka a jednou už jsem o něm psal.

Integrace s ASP.NET MVC

A konečně se dostávám k tomu, o čem tento spot vlastně je. Jak integrovat Windsor kontejner do naší webové aplikace?

Předpokládám, že máte ASP.NET MVC projekt již ve svém studiu. Pak je nutné mít binárky Windsoru. Pravděpodobně budou stačit ty z RC3. Osobně používám aktuální verzi z trunku. Přidejte si reference na knihovny Castle.Core, Castle.DynamicProxy, Castle.MicroKernel a Castle.Windsor. Pak si budeme muset vytvořit novou továrnu na výrobu controllerů. Vlastně nemusíme, již je součástí MVC Contrib, jen jsem jí trochu zjednodušil.

using System;
using System.Web.Mvc;
using System.Web.Routing;
using Castle.Windsor;

namespace BlogSpots.Infrastructure {
  public class WindsorControllerFactory : IControllerFactory {

    private IWindsorContainer _container;

    public WindsorControllerFactory(IWindsorContainer container) {
      if (container == null) {
        throw new ArgumentNullException("container");
      }
      _container = container;
    }

    public virtual IController CreateController(RequestContext context, string controllerName) {
      controllerName = controllerName.ToLower() + "controller";
      return (IController)_container.Resolve(controllerName);
    }

    public virtual void ReleaseController(IController controller) {
      var disposable = controller as IDisposable;
      if (disposable != null) {
        disposable.Dispose();
      }

      _container.Release(controller);
    }
  }
}

Tato továrna bude fungovat pro Windsor. Ještě ale chci přidat podporu pro Binsor, který je součástí Rhino.Commons. K těm se dostanete přes SVN a spolu s nimi dostanete i aktuální verzi Windsoru. Do projektu si ještě přidáme reference na Rhino.Commons a Rhino.Commons.Binsor. Pak si ještě vytvoříme statický helper pro práci s kontejnerem.

using System;
using System.Web.Mvc;
using Castle.Windsor;
using Rhino.Commons;

namespace BlogSpots.Infrastructure {
  public static class IoC {
    private static readonly IWindsorContainer _container;

    static IoC() {
      _container = new RhinoContainer("windsor.boo");
    }

    public static IWindsorContainer Container {
      get {
        return _container;
      }
    }
  }
}

Tím jsme si nainicializovali kontejner s konfigurací v souboru windsor.boo. Tak, a teď už nám zbývá jen zaregistrovat továrnu pro naši aplikaci a napsat konfigurační skript.

V Global.asax zaregistrujeme továrnu na controllery následovně:

protected void Application_Start() {
  ControllerBuilder.Current.SetControllerFactory(new WindsorControllerFactory(IoC.Container));
}

Do web.config přidáme registraci modulu, který nám umožní nastavit životnost objektu na jeden request:

<system.webServer>
  <modules>
    <add name="PerRequestLifestyle"
      type="Castle.MicroKernel.Lifestyle.PerWebRequestLifestyleModule, Castle.MicroKernel" />
  </modules>
</system.webServer>

Tato registrace je pouze pro IIS 7 integrated mode, je nutné ji ještě přidat do system.web\httpModules, aby vám fungovala i na DevServeru.

Boo na scénu

Tímto máme za sebou vše důležité, pro napsání konfiguračního skriptu a spuštění aplikace. Pokud chcete přidat do VisualStudia podporu pro Boo, ve kterém jsou Binsor konfigurační skripty psané, doporučuju stáhnout si a doinstalovat BooLang Studio.

Pojďme tedy k vytvoření konfiguračního souboru! V rootu aplikace si vytvořte soubor Windsor.boo a nastavíme mu vlastnost Build Action na Content, aby se nám pěkně kopíroval při publikování projektu. Do konfiguráku zadejte následující řádky:

import System.Web.Mvc from System.Web.Mvc

for controller in AllTypesBased of Controller("<nazev assembly MVC projektu>"):
  component controller.Name.ToLower(), controller:
    lifestyle PerWebRequest

Takto jsme do kontejneru zaregistrovali všechny controllery v našem MVC projektu. Postupně můžeme registrovat další komponenty a budovat naší aplikaci, ale o tom zas někdy jindy. Tedy snad…

Proč jsou statické helpery zlo?

10.53 - 22. listopadu 2008 | ASP.NET 2.0

Minulý týden psal Arthur spotík o takovém tom víkendovém programování, ve kterém šlo hlavně o to, že přešel na Vistu, vytvořil si gadget pro přehrávání ČRo a taky editor hosts souboru. Napsal ho v C# a zmínil se o tom, že použil techniku, se kterou nejsem s to…

Zkrátka podědil si generickou Dictionary<TKey, TValue> a rozšířil ji o serializaci. Dictionary není serializovatelná. Je to dáno jak její generickou povahou (constraints pro atributy neexistují), tak tím, že sídlí v knihovně, která nesmí mít závislost na jiné, která serializaci zajišťuje.

public class SerializableDictionary<TKey, TValue>
  : Dictionary<TKey, TValue>, ISerializable {
  // ...
}

To byla moje chvíle, vyčmuchal jsem zde codesmell (zbytečné dědění, které by šlo vyřešit kompozicí). Neomaleně jsem vstoupil do diskuse chválící gadget a sprostě Arthura pohanil. Omlouvám se za to. Mým úmyslem bylo ho postrčit o kus dál, možná rozšířit obzory. Prostě sprostá rada, která by mi v začátcích se C# pomohla nebloudit labyrintem macatých tříd se spoustou dovedností a košatou hierarchií předků a potomků.

Alternativní řešení

V diskusi, kterou jsem tímto vyvolal, padlo i to, že jde o porušení Single Responsibility Principle, které už rozebral Borek. Arthur tedy nabídl, alternativní řešení, jak se s problémem vypořádat:

Samozřejmě jsem si mohl napsat i kašpárkovskou třídu, co má dvě statické metody (Serializuj a Deserializuj) a veřejný parametr „Dictionary“… šlo by to vyřešit mnoha způsoby, lišícími se co do pracnosti, elegance a efektivity, a já se rozhodl nedělat „composition blackbox“, ale podědit třídu – především proto, že bych musel zpřístupňovat spoustu veřejných metod od původní generiky, nebo deklarovat vložený objekt jako veřejný.

public static class DictionarySerializer<TKey, TValue> {
  public static byte[] Serialize(Dictionary<TKey, TValue> dictionary) {
    // ...
  }
  public static Dictionary<TKey, TValue> Deserialize(byte[] data) {
    // ...
  }
}

Přejdu teď composition blackbox – vrátím se snad k němu někdy jindy, ale zastavím se u statických metod. Myslím si totiž, že to je další cesta do pekel… :)

Staticky vs. Instančně

Na toto téma se vedlo jedno z vláken té jinak pěkné diskuse o šikovném gadgetu. (Platíte koncesionářské poplatky?) Osobně zastávám názor, že je lepší se statickým helperům vyhýbat. Jednak ze zkušenosti a druhak z některých signálů a pouček správného OO návrhu.

V objektových jazycích odvozených od C jsme si zvykli používat třídy. Třída je předpis, podle kterého se vyrábějí objekty. Objekty jsou přenašeči dat nebo službami. Mohou být tedy obdařeny rozhraním, přes které s ním komunikují jiné objekty. Jenže ve světě Céčkových jazyků, můžeme mít tzv. statické členy. Statický člen existuje jen jednou, po celou dobu života aplikace.

Jenže, co je to život aplikace? Nevím jak je to přesně v PHP, ale myslím, že tam je to cca jeden požadavek. Statické proměnné tu slouží jako šikovná náhrada globálních proměnných a statické metody dovolují volat akce bez nudného vytváření instance třídy. To je jistě super. Ve světě dotnetu je to však mnohem složitější. V ASP.NET žije aplikace dokud ji někdo nevypne, tj. neresetuje aplikační pool, nezmění konfiguraci nebo neaktualizuje binárky. Takže u statického členu nikdy netušíte, jak dlouho bude žít.

Dalším problémem je konkurence. V PHP se aplikace spouští při každém požadavku a po jeho skončení opět vyhnije a to vše se děje v rámci jednoho vlákna. V ASP.NET žije aplikace mnoho požadavků, které jsou zpracovány samostatnými vlákny. Vzhledem k tomu, že statické členy jsou v aplikaci jen jednou a to pro více vláken, musíte najednou začít řešit konkurenční přístup, k těmto členům.

Další z problémů statických členů je ten, že třída není objekt, tudíž by se s členy třídy nemělo počítat v objektovém návrhu. :) Statické metody ani vlastnosti nelze popsat rozhraním, při jejich užití vzniká úzká vazba. Takovéto členy se také krásně vyhýbají dědičnosti. Nelze je přepsat tradičními postupy.

Takhle bych ve zkratce shrnul hlavní nevýhody statického přístupu. Platí jen pro určité případy. Nemusíte se s nimi nutně setkat a pro váš konkrétní případ mohou být i výhodné. Pokud však navrhujete API, které má být znovupoužitelné (hlavní výhoda OOP), nikdy nevíte, v jakém prostředí poběží. A když už píšeme objektově, tak proč rovnou nepsat tak, aby naše třídy znovupoužitel­né byly?

Jak tedy s těmi helpery?

Jak říká Michal Bláha, „statický helpery jsou výbornej syntax sugar.“ Jasně, proč ne? Ve skriptech může být statický helper jasným přínosem. Je ale třeba myslet na některá pravidla.

Základním pravidlem, pokud chci vytvořit statický helper, je vytvořit instanční implementaci. Dobrou ukázkou tohoto přístupu je například System.IO.File, který je statickou fasádou nad System.IO.FileInfo. Podobně funguje např. i metoda Rarous.TexyNet.Texy.Process, která slouží k rychlému převodu textu do HTML, ale je tu i silné instanční API, které tato metoda volá.

Závěr

Statické helpery jsou zlo, když jsou primárním návrhem a taky jediným. Pokud dosahujete kompozice objektů pomocí dependency injection, jsou pro vás statické helpery nedosažitelné (bez napsání instančního wraperu). Když píšete unit testy, nemůžete snadno statický helper nahradit. Pokud máte statické vlastnosti, musíte myslet na konkurenční přístup a životnost zdrojů, které takto zapouzdřujete.

public interface IDictionarySerializer<TKey, TValue, TData> {
  TData Serialize(IDictionary<TKey, TValue> dictionary);
  IDictionary<TKey, TValue> Deserialize(TData data);
}

Přece jen existuje více cest k cíli a je možné, že se někde šeredně pletu. Jaký je váš pohled na věc?

Binsor – síla konfigurace

15.18 - 25. srpna 2008 | ASP.NET 2.0

Pokud se dostanete do stádia, kdy potřebujete takovou architekturu, kde je potřeba používat plug-iny třetí strany, kde je potřeba snadno vyměnit komponentu za jinou, přichází ke slovu nějaký druh konfigurace.

Velice často se můžeme setkat s konfigurací komponent v XML. Ať už je to Provider pattern známý z ASP.NET, či vlastní konfigurační sekce, nebo konfigurace Windsor kontejneru, pořád tu máme XML. Jeho ohromnou výhodou je, že se dají systémové komponenty snadno překonfigurovat bez nutnosti celý systém překompilovat. Stačí pouhý restart aplikace. Navíc nástrojů na editaci a validaci XML máme nepočítaně, takže změnu konfigurace zvládne téměř každý.

Velkou nevýhodou XML, je jeho omezené výrazivo. Špatně se v něm píše foreach nebo if. Prostě na složitější konstrukce je potřeba programovací jazyk. A nebo skriptovací!

Boo na scénu

Boo je staticky typovaný objektový jazyk nad CLI (dot net) inspirovaný syntaxí Pythonu. Jeho hlavní silou je metaprogramování. Na úrovni kompilátoru se snadno dají psát makra. Takových maker lze využít k tvorbě přehledných DSL a jednou z takových je i Binsor.

Windsor + Boo = Binsor

Windsor je IoC kontejner na platformě dot net a je součástí opensource projektu Castle. Windsor má v základu dvě možnosti jak konfigurovat komponenty.

  1. Programově na úrovni mikrokernelu.
  2. XML konfigurací.

XML konfigurace je nejčastěji užívanou možností, kvůli výše popsaným výhodám. V trunku se nedávnou objevila i konfigurace pomocí DSL na principu fluent interface přímo v C#. Taková konfigurace je velice pěkná, ale je tu pořád nutnost při každé změně znova celý projekt kompilovat. Proto je tu Binsor, který si bere sílu programovacího jazyka, efektnost XML konfigurace a navíc jednoduchost a přímočarost syntaxe.

Konfigurace komponent

Nejčastější aktivitou s Binsorem nejspíš bude registrace komponent. :)

component 'my_component', IServiceContract, ServiceImplementation:
    # nastavime parametr konstruktoru
    constructorParameter = 10
    # nastavime vlastnost sluzby
    SomeProperty = "Hello Word!"

Kód je v celku jednoduchý. Na prvním řádku začínamé klíčovým slovem component, které říká, že registrujeme komponentu. Prvním parametrem je název komponenty, přes který se na ní můžeme odkazovat. Druhý parametr je rozhranní služby a třetí je konkrétní implementace, která bude při rozpoznání kontejnerem vrácena, např. IoC.Container.Resolve<IServiceContract>(). Za dvojtečkou pokračuje výčet nastavovaných vlastností a parametrů. Můžeme takto nastavovat i speciální vlastnost ovlivňující životnost objektu.

component HttpRequest:
    lifestyle Singleton

Všimněte si, že se ani nemusí při registraci uvádět referenční název nebo abstraktní typ. Může se klidně rovnou registrovat typ konkrétní, který se bude v tomto případě chovat jako singleton. Takovéto ukázky jsou fešné, ale nic, co bychom nezvládli pomocí XML. Pojďme trochu dál.

for type in AllTypesBased of Controller("MyApplication.Web"):
    component type

Tento kód zaregistruje všechny controllery (třídy, které jsou potomky třídy Controller) z assembly MyApplication.Web. Krom generické metody AllTypesBased, je tu ještě generická metoda AllTypesWithAttribute a negenerická AllTypes. Všechny mají jako parametr název assembly, jejíž typy procházejí.

Facility

Neméně důležitou součástí konfigurace jsou facility, které dodávají kontejneru nové možnosti a zapouzdřují větší sady komponent do logických jednotek. Pro ukázku konfigurace ActiveRecord komponent pomocí Binsoru:

facility ActiveRecordFacility:
    configuration:
        @isWeb = true, isDebug = true
        assemblies = [ Assembly.Load("MyApplication.Entities") ]
        config(keyvalues, item: add):
            show_sql = true
            command_timeout = 5000
            cache.foo.use_query_cache = false
            dialect = 'NHibernate.Dialect.MsSql2005Dialect'
            connection.provider = 'NHibernate.Connection.DriverConnectionProvider'
            connection.driver_class = 'NHibernate.Driver.SqlClientDriver'
            connection.connection_string = 'connectionString1'

Shrnutí

Osobně se mi možnosti konfigurace přes Binsor velice líbí, ale je celkem možné, že ne každému to může vyhovovat. Pokud ale používáte Windsor a přijde vám XML konfigurace nepřehledná, je toto možná cesta, jak z toho ven. Pokud chcete nějaké lepší příklady, doporučuju si stáhnout SVN repository https://rhino-tools.svn.sourceforge.net/svnroot/rhino-tools/trunk/rhino-commons a projít si testy. Jako editor se mi osvědčil SharpDevelop, který má podporu pro Boo přímo v základu, nebo doinstalovat BooLang Studio do Visual Studia.

Tagy: , ,

Castle RC3 a Microsoft MVC

07.48 - 9. října 2007 | Webdesign

Vůbec jsem si toho nevšiml, ale RC3 verze Castle je již několik týdnů venku. Stáhnout si jí můžete ze SourceForge.

Další zajímavou zprávou je. že Microsoft pracuje na vlastním ASP.NET MVC frameworku, který bude umožňovat plnou testovatelnost, IoC kontejnery, ASPX jako view bez ViewState a Page lifecyklu a spoustu zajímavých vychytávek využívajících generika a nové možnosti C#3. První CTP by se měla objevit do konce roku a releas někdy na jaře roku příštího. Release by měl být podobný jako AJAX Extensions, časem se pak zařadí přímo do .net frameworku.

Zajímavý je pohled vývojářů MonoRailu. Vítají zdravou konkurenci a podle všeho se budou snažit využít všech výhod ASP.NET MVC a MonoRail budovat dále nad ním.

Související