poniedziałek, 8 września 2008

Active Record - Aktywny Rekord

Dziś weźmiemy na warsztat - Active Record, który jest stosunkowo bardzo prostym wzorcem. Chodzi w nim o to, że tworzymy obiekt złożony z danych zawierający zachowanie (metody [w tym biznesowe]).

Przypuśćmy, że mamy do napisania obiekt Active Record - Owoc, który ma zawierać -metody CRUD,
-i jakieś 2 metody biznesowe

Mapujemy sobie tabelę Owoc:



   1:  class Owoc

   2:  {

   3:    // "Dane" owocu

   4:    public int Id { get; set; }

   5:    public int Nazwa { get; set; }

   6:    public decimal Cena { get; set; }

   7:    public decimal Rabat { get; set; }

   8:  }



Dopiszmy metody CRUD na samym początku:



   1:  class Owoc

   2:  {

   3:    public Owoc() {}

   4:    

   5:    public Owoc(int id)

   6:    {

   7:      Owoc o = Find(id);

   8:      this.Id = o.Id;

   9:      this.Nazwa = o.Nazwa;

  10:      this.Cena = o.Cena;

  11:      this.Rabat = o.Rabat;

  12:    }

  13:   

  14:    public static Find(int id)

  15:    {

  16:      Owoc o = // Wyciagniecie owocu z bazy i zmapowanie go

  17:      return o;

  18:    }

  19:   

  20:    public Owoc Save()

  21:    {

  22:      // Zapis

  23:    }

  24:    

  25:    public void Update()

  26:    {

  27:      // Update

  28:    } 

  29:    

  30:    public void Delete()

  31:    {

  32:      // Usunięcie

  33:    }

  34:  }



Tutaj trzeba zwrócić uwagę na metodę Find, która musi być statyczna.
Jeśliby nie była, to nie moglibyśmy w stanie jej użyć do konstrukcji obiektu.

Rzućmy okiem na użycie Rekordu Aktywnego:


   1:  // Wyciagamy owoc o id 4

   2:  Owoc o = new Owoc(4);

   3:  Owoc o = Owoc.Find(4);

   4:   

   5:  // Zapisujemy nowy owoc do bazy

   6:  Owoc o = new Owoc() { Nazwa = "jablko", Cena = 20, Rabat = 50 };

   7:  o.Save();

   8:   

   9:  // Updatujemy dane owocu

  10:  o.Nazwa = "Gruszka";

  11:  o.Update();

  12:   

  13:  // Usuwamy go

  14:  o.Delete();



Bardzo wygodne, prawda? ;)

Teraz podepnijmy getter biznesowy obliczający cenę jabłka z rabatem:


   1:  class Jablko

   2:  {

   3:    // ...

   4:    public decimal CenaZrabatem

   5:    {

   6:      get

   7:      {

   8:        return cena - rabat;

   9:      }

  10:    }

  11:  }



No to jak nam tak dobrze idzie, to dopiszmy kolejny getter biznesowy
określający czy jabłko jest na promocji:


   1:  class Jablko

   2:  {

   3:    // ...

   4:    public bool CzyNaPromocji

   5:    {

   6:      get

   7:      {

   8:        return rabat > 0;

   9:      }

  10:    }

  11:  }



Szybki przykład użycia (trochę bezsensowny, no ale trzeba z obrazować problem ;) ):



   1:  decimal cenaOwocu = 0;

   2:   

   3:  Owoc jablko = new Owoc(5);

   4:   

   5:  cenaOwocu = jablko.Cena;

   6:   

   7:  if ( jablko.CzyNaPromocji )

   8:  {

   9:    cenaOwocu = jablko.CenaZrabatem;

  10:  }



No i git, wszystko ładnie i pięknie... czy aby do końca jest to prawda?
Jak to zwyklę bywa... nie.
-Active Record nie powinniśmy stosować gdy mamy do czynienia ze złozoną logiką
biznesową
-AC wiąże się z konkretną bazą (np. CRUD zawiera SQL dla konkretnej bazy, nie jesteśmy go w stanie podmienić)

Niby tylko dwa minusy, ale za to jakie ;)

sobota, 6 września 2008

Videocasts

Coś na noc jak komuś się nudzi w sobotę wieczorem (e jasne ;) ), to może sobie pooglądać bardzo fajne podcasty

DSL - Martin Fowler


RailsConf - Martin Fowler
(ten link coś mi teraz nie działa ale może zacznie)

Piękno Ruby'iego


na deser polecam coś na rozluźnienie http://pl.youtube.com/user/envyads, na prawdę polecam jak chce się pośmiać pozostając w klimatach prograMistycznych.

Link do wzorców projektowych

Na prośbę BielszegoNizBlask publikuje linka do na prawdę dobrego artykułu o wzorcach projektowych http://www.go4expert.com/forums/showthread.php?t=5127 dziękuje za linka i zapraszam wszystkich do lektury :)

Sposób mapowania tabeli - Foreing Key Mapping

[Szybki post]

Odwzorowanie do klucza obcego "Odwzorowuje asocjacje między obiektami jako klucz obcy tabeli", hmm a po ludzku można to przedstawić tak, że:

Jeśli mamy tabele:

Owoc
id : int
nazwa : varchar

i

Koszyk
id : int
owoc_id : int
nazwa : varchar

Oczywiście tutaj występuje asocjacja "Owoc znajduje się w koszyku"

Aby to odwzorować Foreign Key tworzymy 2 klasy:

class Owoc
{
public int Id { get; set; }
public string Nazwa { get; set; }
}


i

class Koszyk
{
public int Id { get; set; }
public int OwocId { get; set; }
public string Nazwa { get; set; }
}


Jak widać teraz asocjacja jest "wykonywana" przez Owoc.Id --- Koszyk.OwocId, czyli tak na prawdę przez klucz obcy.

Wsadzenie owoca do koszyka będzie wyglądało w taki sposób:
Owoc jablko = new Owoc() { Id = 1, Nazwa = "Jabłko" };
Koszyk koszyk = new Koszyk() { Id = 1, OwocId = jablko.Id, Nazwa = jablko.Nazwa };


Oczywiście jest to przypadek kiedy koszyk jest na prawdę mały ;) i można do niego wsadzić tylko JEDEN owoc :)

II System płacowy (cz. 3) - Model dziedziny (Mapowanie)

Skoro mamy już warstwę dostępu do danych, to przyszedł czas na zrobienie modelu dziedziny.

Najpierw musimy się zastanowić jakie tabele w bazie będziemy mieli hmm

1. Zawód - będą przechowywane tutaj nazwy zawodów
2. Pracownik - jak się nie trudno domyśleć będą tutaj pracownicy
3. Przepracowane godziny- ilość przepracowanych godzin

To chyba tyle (przynajmniej na teraz).

Bierzmy się zatem do stworzenie modelu dziedziny.

A tak w ogóle to co to jest ten cały model? Można skrócić wywód ;) i zapisać go w jednym zdaniu: Model dziedziny są to obiekty, które modelują jakąś dziedzinę (wow odkrycie ;)). U nas będą to po prostu zmapowane (jeden do jednego) tabele.

Okey skoro już wiemy co to jest, zatem przejdźmy do akcji. Ale pierwsze... czas na obiad ;)

[po 30 min. przerwy obiadowej nasz zespół pełny (jedzenia ;)) zapału wkracza do pokoju by kontynuować pracę]


Na czym to skończyliśmy hmm... coś tam poudawaliśmy, pogadaliśmy sobie potem była przerwa... a tak! mapowanie tabel :)

Użyjemy sposobu mapowania: Foreign Key Mapping

Ale zaraz na razie nie mamy czego mapować, musimy utworzyć tabele:

zawod
id : int,
nazwa : varchar

pracownik
id : int,
imie : varchar
zawod_id : int

przepracowane_godziny
id : int,
pracownik_id : int
ilosc_godzin : int
data : dateTime

Mapnijmy to ;)

Zawód:
public class Zawod
{
public int Id { get; set; }
public string Nazwa { get; set; }

public Zawod(int id, string nazwa)
{
this.id = id;
this.Nazwa = nazwa;
}
}


Pracownik:
public class Pracownik
{
public int Id { get; set; }
public string Imie { get; set; }
public int ZawodId { get; set; }

public Pracownik (int id, string imie, int zawodId)
{
this.Id = id;
this.Imie = imie;
this.ZawodId = zawodId;
}
}


Przepracowane godziny:
public class PrzepracowaneGodziny
{
public int Id { get; set; }
public int PracownikId { get; set; }
public int IloscGodzin { get; set; }
public DateTime Data { get; set; }

public PrzepracowaneGodziny(int id, int pracownikId, int iloscGodzin, DateTime data)
{
this.Id = id;
this.PracownikId = pracownikId;
this.IloscGodzin = iloscGodzin;
this.Data = data;
}
}


Job done...

piątek, 5 września 2008

Nowy portal skupiający społeczność NHibernate

Jak w temacie, link znajduje się tutaj http://www.nhforge.org/. Widać, że dopiero się rozwija, no ale poczekamy i zobaczymy co z tego się wykluje.

A co do projektu Systemu Płacowego to mam nadzieję, że mi się uda w przyszłym tygodniu zamieścić jakieś notki.

czwartek, 28 sierpnia 2008

Interview Questions

Może kogoś to zainteresuje:

ADO.NET part 1
ADO.NET part 2

ASP.NET

Jak dla mnie, autor odwalił kawał dobrej roboty.

wtorek, 12 sierpnia 2008

niedziela, 3 sierpnia 2008

Panowanie nad połączeniem bazy danych przez AOP

[Wymagania: postSharp, najlepiej sobie zainstalować przez "Windows Installer"]

AOP (Aspect Oriented Programming), czyli Programowanie Zorientowane Apektowo, więcej informacji co to jest i z czym to się je możecie znaleźć sobie w googlach ;), lecz całość opiera się na czymś takim:


// Pseudokod
main()
{
Wypisz("Cześć Wszystkim! :)");
}

Wypisz(string text)
{
Console.Writeln(text);
}

Wynik: Cześć Wszystkim! :)

Teraz chcemy by zaraz np. przed wywołaniem metody Wypisz() coś się wydarzyło czyli:

AspektPrzed
{
Console.Writeln("AOP mówi:");
}


main()
{
Wypisz();
}

[AspektPrzed]
Wypisz(string text)
{
Console.Writeln(text);
}

Wynik: AOP mówi: Cześć Wszystkim! :)

Tak czy siak zapraszam na:
http://www.postsharp.org/about/documentation/
http://www.postsharp.org/about/video/default.aspx <-- obowiązkowo! :)

Okey, teraz przejdźmy do naszego głównego problemu. Bardzo często nam się zdarza, że podczas korzystania z DAO, ręcznie tworzymy połączenia, zamykamy je, commitujemy transakcję, etc. Z odsieczą przychodzi właśnie AOP. Spójrzmy na przykład:


   1:  using (ConnectionManager cm = ConnectionManager.Create)

   2:  {

   3:      osoba.Save(cm);

   4:   

   5:      cm.CommitTransaction();

   6:  }


Wstawiliśmy rekord do tabeli, wszystko ok, lecz musieliśmy utworzyć połączenie i dać commita, a takiego kodu możemy mieć baaardzo dużo. Jeszcze możemy pokusić się o inne rozwiązanie projektu:

W klasie Osoba piszemy metodę Save(), lecz tak czy tak w niej musimy otworzyć to połączenie i zatwierdzić transakcję.

Zobaczmy przykład przy użyciu AOP, trochę będzie tego kodu ;):


   1:  osoba.Save(null);


Gotowe :)

A teraz krótkie wyjaśnienie jak to działa. Na samym początku tworzymy aspekt ConnectionAttribute : PostSharp.Laos.OnMethodInvocationAspect, OnMethodInvocationAspect znaczy, że będziemy "panować" nad metodami. Przeciążamy metodę OnInvocation i piszemy coś takiego:
public override void OnInvocation(PostSharp.Laos.MethodInvocationEventArgs eventArgs)
{
// Przechwytujemy wpisane parametry metody
object[] args = eventArgs.GetArgumentArray();

// Na pierwszym miejscu tablicy oczekujemy ConnectionManager
if (args[0] != null)
{
// Jesli istnieje, to wywolujemy metode z wczesniej wpisanymi parametrami
eventArgs.ReturnValue = eventArgs.Delegate.DynamicInvoke(args);

return;
}

using (ConnectionManager cm = ConnectionManager.Create)
{
// Jesli nie istniaje w args[0] ConnectioManager, to go wstawiamy
args[0] = cm;

// Wywolujemy metode (args[0] ma juz ConnectionManager'a)
eventArgs.ReturnValue = eventArgs.Delegate.DynamicInvoke(args);

// Commitujemy transakcje
cm.CommitTransaction();
}
}

Myślę, że tutaj dodatkowy komentarz jest zbędny ;)

Aby dana metoda "korzystała" z aspektu musimy dodać do niej atrybut:


   1:  [Connection] // Jest to nasz ConnectionAttribute, można podać jego całą nazwę jeśli chcemy

   2:  public void Save(ConnectionManager cm)



W zasadzie to by było na tyle, zapraszam do ściągnięcia pełnego przykładu, są tam umieszczone przeze mnie komentarze by można się było w tym połapać, ale na samym początku polecam użycie debugera i obserwowanie krok po kroku co się dzieje na ekranie.

Co do tego rozwiązania to nie wiem czy czasem nie da się go ulepszyć (zresztą znając życie na pewno się da ;)). Szukałem po sieci jakiegoś przykładu/wzorca AOP, który rozwiązuje ten problem, lecz nie znalazłem. Dlatego chętnie wysłucham krytykę (konstruktywną ;)) oraz to w jaki sposób można ulepszyć ten Aspekcik (i czy w ogóle to dobry kierunek). Jak macie jakieś uwagi zapraszam do wypowiedzi.

piątek, 1 sierpnia 2008

I SystemPlacowy.DataAccessLayer (cz. 2)

[Ja zamiast tworzyć w Visual Studio Express wole pisać w SharpDevelop 3.0. Ma wbudowane narzędzia do testów, pokrycia kodu etc., lecz niestety brakuje mu paru rzeczy z VS, no ale to zawsze jakaś miła alternatywa ;)]

Dość gadania, bierzemy się za stworzenie warstwy dostępu do danych.
Projekty:



Z racji tego, że wszystko robimy w TDD (test driven development) zaczynamy od utworzenia testu połączenia z bazą.

[Test]
public void TestConnection()
{

}


Świetnie już prawie gotowe! To tyle ... żartowałem oczywiście ;)

Wiemy że dostęp będzie się odbywać przez klasę DataAccess. Skoro to wszystko musi być elastyczne, czyli DataAccess nie może być uzależniony od konkretnej bazy danych musimy stworzyć interface IDataProvider. Każdy dostawca danych będzie implementował ten interface:

public interface IDataProvider
{
IDbConnection Connection { get; }
}


Okey, rozszerzmy nasz test o dodatkową metodę:

[SetUp]
public void SetUp()
{
dataAccess = new DataAccess();
}


Atrybut [SetUp] uruchamia metodę JEDEN RAZ PRZED TESTEM.


Świetnie, lecz czegoś nam tu brakuje. Pasowałoby jakoś przekazać do DataAccess konkretnego providera. Może po prostu go wstrzykniemy przez konstruktor :)?

[SetUp]
public void SetUp()
{
dataAccess = new DataAccess(new MySqlDataProvider());
}


Kompilujemy projekt i proszę, niespodzianka! Nie chce się nam projekt kompilować. Brakuje tej klasy. Niezwłocznie stwórzmy ją w katalogu SystemPlacowy.DataAccessLayer.DataProvider:


public class MySqlDataProvider : IDataProvider
{
public MySqlDataProvider()
{
}

public IDbConnection Connection
{
get
{
return new MySqlConnection();
}
}
}


Teraz uruchamiamy test:


SharpDevelop 3.0

Zgodnie z oczekiwaniami test kończy się niepowodzeniem, ciekawe dlaczego ;) Lecimy do klasy MySqlDataProvider, po czym dopisujemy w getterze otwarcie połączenia.

public IDbConnection Connection
{
get
{
try
{
IDbConnection connection = new MySqlConnection(connectionString);
connection.Open();

return connection;
}
catch (MySqlException ex)
{
Console.WriteLine(ex.Message + " " + ex.StackTrace);

return new MySqlConnection();
}
}
}


Teraz test przechodzi :)



Podobnie robimy tak ze wszystkimi metodami jakie będziemy potrzebować (wszystko znajdziecie w kodzie źródłowym w 3 częsci).

Do tej pory wszystko mamy dobrze popisane, lecz dochodzimy do wniosku, że każde użycie DataAccess wiąże się z utworzeniem obiektu i wstrzyknięciem do niego MySqlDataProvider. Już to sobie wyobrażamy, że w każdymi miejscu w DAO tworzymy taki obiekt, bleeee. Na pomoc przychodzi nam (nieustraszony) Ninject, (podobnie jak Autofac jest kontenerem IoC).



Tworzymy klasę SystemPlacowy.DataAccessLayer.DataAccessModule : StandardModule. Przeciążamy metodę Load() i w jej ciele definiujemy moduł:

public class DataAccessModule : StandardModule
{
public override void Load()
{
Bind<DataAccess>().ToSelf();
Bind<IDataProvider>().To<MySqlDataProvider>();
}
}


W skrócie można to co się znajduje w Load() przetłumaczyć jako: wszędzie tam gdzie spotkasz IDataProvider to zastąp go MySqlDataProvider. Dzięki zastosowaniu takiego rozwiązania, w każdej chwili możemy sobie zmienić w tym JEDNYM I JEDYNYM miejscu w projekcie z MySqlDataProvider na inny np MsSqlDataProvider.

Zmieńmy nasz test:
[TestFixture]
public class TestDataAccess
{
private IKernel kernel;

[SetUp]
public void SetUp()
{
kernel = new StandardKernel(new DataAccessModule());
}

[Test]
public void TestConnection()
{
Assert.IsTrue(kernel.Get<DataAccess>().Connection.State == ConnectionState.Open);
}
}

Jest okey :) Yeah! Dobra robota... Nigdzie nawet nie użyliśmy "new" przy tworzeniu DataAccess, czy można chcieć od życia czegoś więcej? ;)

cdn...

NHibernate - materiały szkoleniowe

Bardzo ciekawa propozycja dla osób chcących się wdrożyć w NHibernate http://www.urlfan.com/site/summerofnhibernate_com/2800273.html. Nam się bardzo przyda w dalszych częściach tworzenia Systemu Płac i nie tylko.

czwartek, 31 lipca 2008

Podpięcie projektu do Subversion (SNV) - cz. 1

Kontrolę wersji w Visual Studio można zrealizować przy użyciu (niezbyt udanego) M$ produktu - Source Safe. Niestety z tego co wiem, to za Source Safe'a trzeba słono zapłacić oraz nie da się go spiąć z wersją Visual Studio Express. Więc co maja zrobić "biedni" użytkownicy, którzy chcą mieć kontrolę wersji we własnym projekcie? Odpowiedź jest prosta, powinni się zainteresować Open Sourceowym SVN'em. Bez zbędnych przedmów weźmy się za instalację.

Na początek przyjdzie nam ściągnąć pliki:
- http://www.visualsvn.com/server/ - zawiera niemal wszystko czego potrzebujemy ("It includes Subversion, Apache and a management console. ")
- http://tortoisesvn.tigris.org/ - klient do SVN'a

Instalacja.
Najzwyczajniej w świecie instalujemy VisualSvn oraz TortoiseSvn. Nie ma co tu opisywać, stara dobra metoda przy instalacji "dalej, dalej, dalej ... " raczej się sprawdzi ;)

Po instalacji musimy zresetować komputer, gdyż muszą zostać wprowadzone zmiany.

....

Okey, jesteśmy po resecie :)

Klikamy sobie np. na pulpicie prawym klawiszem i pojawiają nam się 2 dodatkowe opcje:


Zatem teoretycznie wszystko jak na razie idzie dobrze. Zapewne serwer SVN działa, więc przejdźmy do utworzenia repozytorium plików. W tym celu uruchamiamy "VisualSVN Server Manager".



Tworzymy nowe repozytorium:


Nadajemy nazwę:


Gotowe, repozytorium TestoweRepo już sobie stoi:


Zajmiemy się teraz wrzuceniem do naszego repozytorium przykładowego projektu.
Przyjmijmy, że wchodzimy do katalogu E:\Projekty\ gdzie znajdują się ważne projekty. Prawy klawisz na katalogu E:\Projekty -> SVN CheckOut pojawia się nam okno:



Urla musimy pobrać z VisualSVN:


Następnie go wklejamy i akceptujemy:


Na katalogu E:\Projekty powinna się pojawiać zielona ikonka



Teraz na wszystkim co się znajduje w E:\Projekty pojawiła się ikonka z pytajnikiem, znaczy to, że plik jest w katalogu zespolonym z SVN, lecz on sam nie jest. Musimy ręcznie go dodać:



Dodajemy do SVN:


Akceptujemy wszystko i pojawia się nam okno:


Odświeżamy nasz katalog. Powinna się na nim pojawić ikonka z plusem, co znaczy, że "już prawie dodaliśmy katalog" ;) do repozytorium:


Musimy potwierdzić przez wybranie Commit'a:


Zgadzamy się na wszystko i po skończonym commitowaniu na katalogu powinna się pojawić znajoma już ikonka:



Ufff :) mamy już to co chcieliśmy w repozytorium. W następnej części zajmiemy się integracją z Visual Studio Express.

niedziela, 27 lipca 2008

Dependency Injection + Autofac + mały przykład

[Wymagania: .NET 3.5]
Załóżmy że mamy zrealizować bardzo proste zadanie: zmienić wszystkim osobom o jakimś wieku ich imię. Więc...

Mapujemy osobę:




class Osoba
{
public int Id { get; set; }
public string Imie { get; set; }
public int Wiek { get; set; }

public override string ToString()
{
return string.Format("{0} {1} {2}", Id, Imie, Wiek);
}
}

Symulujemy działanie Data Access Layer (zwracającego wszystkie osoby)

interface IDal
{
IEnumerable<Osoba> Osoby { get; }
}

class Dal : IDal
{
private IEnumerable<Osoba> osoby = new List<Osoba>()
{
new Osoba() { Id = 1, Imie = "osoba1", Wiek = 23},
new Osoba() { Id = 2, Imie = "osoba2", Wiek = 23},
new Osoba() { Id = 3, Imie = "osoba3", Wiek = 32}
};

public IEnumerable<Osoba> Osoby { get { return osoby; } }
}

Tworzymy Data Access Object (LINQ wyciągający osoby przy użyciu IDal)
interface IDaoOsoby
{
IEnumerable<Osoba> FindByWiek(int wiek);
}

class DaoOsoby : IDaoOsoby
{
private IDal dal;

public DaoOsoby(IDal dal)
{
this.dal = dal;
}

public IEnumerable<Osoba> FindByWiek(int wiek)
{
return from osoba in dal.Osoby
where osoba.Wiek == wiek
select new Osoba() { Id = osoba.Id, Imie = osoba.Imie, Wiek = osoba.Wiek };
}
}


Serwis odpowiedzialny za zmianie imion (główna logika)
interface IServiceOsoby
{
void ZmienImie(int wiek);
}

class ServiceOsoby : IServiceOsoby
{
private IDaoOsoby daoOsoby;

public ServiceOsoby(IDaoOsoby daoOsoby)
{
this.daoOsoby = daoOsoby;
}

public void ZmienImie(int wiek)
{
IEnumerable<Osoba> osoby = daoOsoby.FindByWiek(wiek);

foreach (var osoba in osoby)
{
osoba.Imie = "zmienione imie";
}
}
}

Dzięki temu, że wszystko oparte zostało na interface'ach uzyskaliśmy bardzo elastyczną
i niepowiązaną żadnymi zależnościami strukturę
. Świetnie, o to właśnie nam chodziło :)
Teraz wystarczy określić zachowanie przez "wstrzykniecie" konkretnych klas do konstruktorów.

Jeśli chodzi o testy jednostkowe, to stworzenie ich nie będzie żadnym problemem.
Dzięki użyciu Dependency Injection możemy testować jedną warstwę niezależnie od drugiej.
Tutaj przyjdą nam z pomocą Mock'i, lecz zostawmy sobie to na później :)

Rzućmy okiem w jaki sposób możemy się tym wszystkim posługiwać.


class Program
{
static void Main(string[] args)
{
// "Normalne" użycie
IDal dal = new Dal();
IDaoOsoby daoOsoby = new DaoOsoby(dal);
IServiceOsoby service = new ServiceOsoby(daoOsoby);

service.ZmienImie(23);

// Autofac
ContainerBuilder cb = new ContainerBuilder();

cb.Register<Dal>().As<IDal>().SingletonScoped();
cb.Register(v => new DaoOsoby(v.Resolve<IDal>())).As<IDaoOsoby>();
cb.Register(v => new ServiceOsoby(v.Resolve<IDaoOsoby>())).As<IServiceOsoby>();

Container container = new Container();
cb.Build(container);

container.Resolve<IServiceOsoby>().ZmienImie(23);
}
}




Jest to tylko bardzo "drobny i ogólny" przedsmak tego co będzie działo w kolejnych postach :)

kod źródłowy

środa, 23 lipca 2008

TDD. Przykładowa aplikacja cz. 5 / System płacowy cz. 1

Hmm klient się do nas wciąż nie odzywa. W sumie to i dobrze, bo ciągle robimy sobie wakacje ale dość tego! Czas się zająć refaktoryzacją naszej aplikacji. Podczas trwania urlopu na obozie programistycznym (jasne ;)), dowiedzieliśmy się o istnieniu całkiem ciekawych projektów: Autofac oraz PostSharp. Tak bardzo nas zainteresowały, że postanowiliśmy je wdrożyć do całej aplikacji. Ba! co tam chodźmy za ciosem, przebudujemy całą i DAJMY JEJ W KOŃCU JAKĄŚ nazwę ;)!.. póki klient milczy. Zacznijmy więc od początku.

Podzielmy naszą aplikację na warstwy.

I. Warstwa dostępu do danych. Tutaj stworzymy mechanizm odpowiedzialny za dostęp/komunikację z bazą, zupełnie elastyczny, niezależny od rodzaju bazy.

II. Domain Model (Model dziedziny). Zmapowane tabele bazy danych, DAO, reguły biznesowe.

III. Services (Serwisy). Ta warstwa będzie udostępniać funkcje aplikacji (coś na kształt uporządkowanego API systemu).

IV. Interface Klienta. Czyli po prostu w skrócie... formatki ;)


Cały model będzie tak wyglądać:



Nie noo.. wszystko super, zobaczymy jak to wyjdzie w praktyce.

Strukturę już mamy, wiemy co mamy robić, toteż przyszedł czas na nadanie nazwy dla programu... może po prostu... System Płacowy...? Mało oryginalny :| lecz nasz zlasowany mózg po urlopie na razie nie jest w stanie wymyśleć czegoś chwytliwego ;)

Bierzmy się do roboty.

Ściągamy projekty:
-NAnt
-NHibernate (później nam się przyda)
-NUnit
-NMock
-Ninject
-PostSharp

Na pierwszy rzut idzie I warstwa, dostęp do danych.

cdn.

niedziela, 29 czerwca 2008

Przerwa

Niestety trwa przerwa w pisaniu postów, jest to spowodowane nauką do egzaminu: 70-536, lecz postaram się coś niedługo wrzucić.

czwartek, 1 maja 2008

[brzydkie zapachy kodu] Jeden z Twoich najwiekszych wrogów: DRY! cz. 1

DRY (DON'T REPEAT YOURSELF), czyli: NIE POWTARZAJ SIĘ

Chodzi tu po prostu o powtórzenia kodu. KAŻDE powtórzenie prowadzi do tego, iż cały system coraz trudniej się konserwuje. Wyobraźmy sobie sytuację: mamy metodę, która wylicza składkę ubezpieczenia. Do tego zaraz tworzymy identyczną metodę (w tym samym miejscu lub gdzieś w innej klasie). Musimy pamiętać o tym, że posiadamy dwie takie same metody i jeśli chcemy coś zmienić to musimy to zrobić tu i tu. Przeważnie wystarczy usunąć 1 metodę i korzystać z jednej. To oczywiście najprostszy przypadek. W części drugiej dotyczącej DRY zajmiemy się usuwaniem powielonych części kodu przez stosowanie: metody szablonowej, zastąpieniem algorytmu, przemieszczenie pola w górę etc.

Krótka historyjka wyjaśni nam jak mogą powstawać DRY.

1. Złe zarządzanie projektem
Agnieszka: Kasia, przeglądałam wczoraj Twój kod i natchnęłam się na niemal identyczną klasę, którą w zeszłym miesiącu zrobił Tomek.
Kasia: Co? Ech, gdybym wiedziała, to użyłabym jego klasy a nie traciła czasu na coś co istnieje.

Mogło dojść do tej sytuacji, ze względu:
-na zły podział zadań w projekcie. Widocznie Kasia dostała do zrobienia coś podobnego co już robił Tomek
-nieznajomości struktury aplikacji Kasi (gdyby np. kod był tworzony w parach, to raczej by nie doszło do takiej sytuacji)
-pewność Kasi, że taka klasa nie istniała w systemie i przez to nie zapytała się członków zespołu czy już coś takiego zostało stworzone
-brak (dobrze) rozrysowanej struktury aplikacji, z której można byłoby wyczytać z jakich klas oraz relacji składa się system

2. Głupota (czy jak kto woli brak doświadczenia ;))
Kasia: Tomek, potrzebuje klasy, która służy do obliczania wynagrodzenia na koniec roku, muszę trochę zmienić jej funkcjonalność pod siebie.
Tomek: Okey, tylko nic nie zepsuj. Znajduje się ona w Wyliczenie.Wynagrodzen.
Kasia: Dzięki.
[Kasia bierze całą klasę i kopiuje ją do swojego modułu nad którym pracuje.]

Mogło dojść do tej sytuacji, ze względu:
-brak doświadczenia/zdrowego rozsądku ;)
-obawa przed wprowadzeniem błędu do klasy, co spowodowałoby katastroficzne skutki w całym systemie

To są tylko oczywiście takie dwa przypadki, które powinny Was uczulić na to co robią członkowie zespołu, w którym pracujemy. Sprawdzajmy i pilnujmy się nawzajem ;)

[brzydkie zapachy kodu] Długa metoda

Jak nie trudno się domyśleć zapach - długa metoda, polega na tym, iż metoda jest bardzo ... długa ;) Ale co to oznacza w praktyce? Otóż to, iż np. w takiej metodzie znajdziemy pętle, różnego rodzaju obliczenia, wyświetlenia treści etc. Przykład takiej metody:


class Przyklad
{
public void DlugaMetoda()
{
Console.WriteLine("Naglowek");
Console.WriteLine("---------------------------");

int wynik = 3 * 5;

int a = 0;

for (int i = 0; i < wynik; i++)
{
a += wynik;
}

if (wynik < a)
{
wynik = 123 + 123;
}
else
{
a = wynik;
}


Console.WriteLine("Wyniki:");
Console.WriteLine("a: " + a);
Console.WriteLine("wynik: " + wynik);

Console.WriteLine("---------------------------");
Console.WriteLine("Stopka");
}
}

Rozwiązanie tego problemu jest banalne. Wystarczy stosować "wydzielenie metody". Czyli tą długa metodę dzielimy na mniejsze:


class Przyklad
{
public void DlugaMetoda()
{
DrawNaglowek();

int wynik = ObliczWynik(3, 5);

int a = 0;

a = ZrobCos(wynik, a);

ZrobCosInnego(ref wynik, ref a);

DrawWyniki(wynik, a);

DrawStopka();
}

private static void ZrobCosInnego(ref int wynik, ref int a)
{
if (wynik < a)
{
wynik = 123 + 123;
}
else
{
a = wynik;
}
}

private static int ZrobCos(int wynik, int a)
{
for (int i = 0; i < wynik; i++)
{
a += wynik;
}
return a;
}

private static void DrawStopka()
{
Console.WriteLine("---------------------------");
Console.WriteLine("Stopka");
}

private static void DrawWyniki(int wynik, int a)
{
Console.WriteLine("Wyniki:");
Console.WriteLine("a: " + a);
Console.WriteLine("wynik: " + wynik);
}

private static void DrawNaglowek()
{
Console.WriteLine("Naglowek");
Console.WriteLine("---------------------------");
}

private static int ObliczWynik(int a, int b)
{
return a * b;
}
}

Gdzie mamy zysk?
-wprowadzanie zmian w takiej metodzie jest łatwiejsze
-ciało metody jest CZYTELNIEJSZE
-jesteśmy w stanie objąć całą metodę wzrokiem
-jeśli dobrze nazwiemy te inne metody powstałe przy "wydzieleniu", to nie będziemy musieli zaglądać do ich kodu, gdyż ich nazwa określi nam co robią

[refaktoryzacja] Tworzenie obiektów dla prostych typów danych

Często programiści boją się tworzyć (przynajmniej ja takich spotkałem) małe klasy do obsługi np. kodu pocztowego etc. Niepotrzebnie. Pokażemy to na przykładzie numeru telefonu. Jeśli logika jego obsługi będzie rozmieszczona w różnych klasach, będziemy mieć nie lada problem z zarządzaniem/obsługą/modyfikacją/pielęgnowaniem. Zobrazujmy ten problem:


class Osoba
{
public string NumerTelefonu
{
get
{
// zwraca numer telefonu
}
}
}

class Centrala
{
public string NumerTelefonuBezNumeruKierunkowego
{
get
{
// zwraca numer telefonu bez numeru kierunkowego
}
}
}

class Miejscowosc
{
private Osoba osoba;

public string NumerKierunkowy
{
get
{
// wyciagamy numer kierunkowy z numeru telefonu osoba.NumerTelefonu
}
}
}


Piękny syf w kodzie ;) Ciekawe gdzie teraz byśmy dodali metodę sprawdzającą poprawność numeru telefonu? Żeby uniknąć takich rzeczy wystarczy stworzyć drobną klasę zajmującą się tylko i wyłącznie obsługą tego numeru telefonu. Czyli wszystkie metody dotyczące numeru telefony przemieszczamy do klasy NumerTelefonu:


class NumerTelefonu
{
public NumerTelefonu(string numer)
{
this.numer = numer;
}

private string numer;
public string Numer
{
get
{
// zwraca numer telefonu
}
}

public string NumerTelefonuBezNumeruKierunkowego
{
get
{
// zwraca numer telefonu bez numeru kierunkowego
}
}

public string NumerKierunkowy
{
get
{
// wyciagamy numer kierunkowy z numeru telefonu osoba.NumerTelefonu
}
}

public bool CzyPoprawny
{
get
{
// sprawdzenie czy numer jest poprawny
}
}
}
Teraz mamy wszystko w jednym miejscu. Nie musimy się zastanawiać gdzie umieścić metodę walidującą numer telefonu, oczywiście w ... NumerTelefonu, ale odkrycie ;)

wtorek, 29 kwietnia 2008

Zasada Pojedynczej Odpowiedzialności / Single-Responsibility Principle

"Żadna klasa nie może być modyfikowana z więcej niż jednego powodu".

Tylko, że co to dokładnie oznacza?
Jeśli klasa odpowiada za więcej niż jeden obszar jednocześnie, wtedy może istnieć więcej powodów do zmian niż jeden. Też można się w tym trochę pogubić ;) zatem przejdźmy do przykładu, który nakreśli problem.

Mamy sobie klasę, która odpowiada za dodanie dwóch cyfr do siebie oraz wypisaniu tego na ekran.



class Matematyka
{
private int wynik;

public void Dodaj(int a, int b)
{
wynik = a + b;
}

public void Wypisz()
{
Console.WriteLine(wynik);
}
}


Tutaj jest zachwiana zasada SRP. Dlaczego? Ponieważ ta klasa odpowiada za dwie rzeczy:
1. dodawanie
2. wypisywanie/rysowanie na ekran

w myśl SRP powinna ciążyć na niej 1 odpowiedzialność, czyli albo dodawanie albo rysowanie. Poprawne rozwiązanie problemu:



interface IWypisz
{
void Wypisz(int wynik);
}

class Matematyka
{
private int wynik;
public int Wynik
{
get
{
return wynik;
}
}

public void Dodaj(int a, int b)
{
wynik = a + b;
}
}

class MatematykaUI : IWypisz
{
public void Wypisz(int wynik)
{
Console.WriteLine(wynik);
}
}

class Program
{
static void Main(string[] args)
{
Matematyka m = new Matematyka();
m.Dodaj(2,3);

IWypisz w = new MatematykaUI();
w.Wypisz(m.Wynik);
}
}


Teraz jak chcemy zmodyfikować nasz bardzo skomplikowany ;) algorytm dodawania, to mamy pewność, że nic nie schrzanimy w wyświetlaniu wyniku i odwrotnie. Oczywiście, trzeba się nauczyć stosować SRP z wyczuciem. Przyjdzie to z praktyką, lecz nie ukrywam, że jest to trudne, by na prawdę dobrze stosować SRP'a. W "przykładowej aplikacji" jaką tworzymy na pewno pojawią się dylematy z rozwiązaniem tego problemu, wtedy przypomnę o Zasadzie Pojedynczej Odpowiedzialności.

sobota, 26 kwietnia 2008

[zasada] DIP - Dependency Inversion Principle / Zasada Odwracania Zależności

1. Moduły wysokopoziomowe nie powinny zależeć od modułów niskopoziomowych. Obie grupy modułów powinny zależeć od abstrakcji.

2. Abstrakcje nie powinny zależeć od szczegółowych rozwiązań. To szczegółowe rozwiązania powinny zależeć od abstrakcji.


O co w tym chodzi?
Posłużmy się przykładem. Każde użycie operatora new jest naruszeniem zasady DIP, lecz nie należy popadać w panikę. Wszystko trzeba robić z głową. Jeśli nasza klasa rzadko ulega zmianie, bądź przewidujemy, że nie będzie często się zmieniać, to nie powinniśmy mieć problemów wynikających ze złamania zasady DIP.
Stosowanie np. wzorca projektowego factory method , strategii, metody szablonowej powoduje odwrócenie zależności.
Skupmy się teraz na przykładzie:



class B
{
public void ZrobCos() { }
}

class A
{
public void Metoda()
{
B b = new B();

b.ZrobCos();
}
}

Jakakolwiek zmiana w klasie B może mieć wpływ na zachowanie klasy A. Poza tym, klasa A może tylko sterować zachowaniem B. Jak to zmienić? Oczywiście ABSTRAKCJĄ. Stosujemy w tym przypadku interface dla klasy B, popatrzmy:


interface IB
{
void ZrobCos() { }
}

class B : IB
{
public void ZrobCos() { }
}

class A
{
public void Metoda()
{
IB b = new B();

b.ZrobCos();
}
}


Co osiągnęliśmy?
-odwróciliśmy zależności (wyeliminowaliśmy zależność między abstrakcją a szczegółami implementacji)
-klasa A może sterować dowolnymi klasami implementującymi interface IB

Również zasada DIP przydaje się bardzo podczas tworzenia struktury projektu do wyeliminowania zależności między modułami.



Obrazek przedstawia jak bardzo moduł Presenter zależy od modułu Gateway. Poza tym każda nowa kompilacja Presentera wiąże się z ponowną kompilacją Gateway (powstaje nowe wydanie, a co za tym idzie trzeba puścić testy jednostkowe dla tych 2óch modułów). Stosując abstrakcję odwracamy zależność:



Teraz interface IGateway jest jakby "barierą" odgradzająca szczegółową implementację Gatewaya. Kompilacja Presentera w żadnym stopniu nie wpłynie na Gateway. Należy jeszcze dodać, iż IGateway również jest tutaj osobnym modułem, bądź znajduje się w module Presenter.

czwartek, 24 kwietnia 2008

[tdd] TDD. Przykładowa aplikacja cz. 4

Na razie udało nam się spławić klienta i nie będzie nam zawracał gitary, póki co oczywiście. Możemy coś sobie porobić w wolnym czasie. Zamiast chodzić po stronach i popijać piwko, ulepszymy kod aplikacji dla naszego klienta, ponieważ wiemy, że to się na OPŁACI. Na fantastycznym blogu znaleźliśmy krótkie wprowadzenie do TDD. Z wypiekami na twarzy rzucamy się na zastosowanie w praktyce tej metodyki.

Wiemy, że nasz kod nie jest jednak tak doskonały jak się nam wydawało. Czeka nas w zasadzie całkiem spora refaktoryzacja, która później zaowocuje.

Zajmijmy się najpierw uporządkowaniem naszego pliku, przeniesiemy klasy do nowych plików. Tak więc, jeśli mamy klasę piekarz, to tworzymy plik Piekarz.cs i tam przenosimy całą klasę. Robimy to zarówno dla enum, klas, interfaceów etc.



No niby jest fajnie, lecz nie do końca, jeśli będziemy pracować nad tą aplikacją i będą dochodzić kolejne klasy to zrobi nam się bałagan. Zatem zakładamy katalogi i staramy się w nich umieścić, klasy mające "coś" wspólnego ze sobą. Nasza propozycja wygląda tak:



Okey, na razie tak zostawimy.

Przed przystąpieniem do refaktoryzacji postanawiamy pokryć nasz kod testami, aby klient się nie zdziwił, że poprzednio co działało nagle przestało.

Zaczynamy od napisania testów dla obliczania wynagrodzeń. (Kto nie wie o co chodzi, zapraszam do zapoznania się z tym postem)

Utwórzmy może osobną dll'ke, która będzie zawierała testy. Nazwijmy ją PrzykladowaAplikacja.Tests. Jej struktura będzie identyczna jak struktura PrzykladowaAplikacja, czyli:



Bierzemy się za pisanie testów. Zaczniemy od napisania testów dla WynagrodzenieFactoryMethod. Tutaj będziemy sprawdzać czy metoda zwraca nam odpowiednie typy.



using System;
using System.Collections.Generic;
using System.Text;
using NUnit.Framework;
using PrzykladowaAplikacja.Global;
using PrzykladowaAplikacja.Pracownicy;

namespace PrzykladowaAplikacja.Tests.Pracownicy
{
[TestFixture]
public class TestWynagrodzenieFactoryMethod
{
[Test]
public void TestMakePracownik()
{
Assert.AreEqual(typeof(Kierownik),
WynagrodzenieFactoryMethod.MakePracownik(Enums.TypZawodu.KIEROWNIK));
}
}
}


Testujemy... i zaskoczenie, test nie przeszedł.
PrzykladowaAplikacja.Tests.Pracownicy.TestWynagrodzenieFactoryMethod.TestMakePracownik : Expected: PrzykladowaAplikacja.Pracownicy.Kierownik
But was: PrzykladowaAplikacja.Pracownicy.Kierownik


dziwny komunikat, ok to zróbmy taką "sztuczkę":



Assert.AreEqual(typeof(Kierownik).ToString(),
WynagrodzenieFactoryMethod.MakePracownik(Enums.TypZawodu.KIEROWNIK).ToString());


Testy przechodzą. No to teraz dopisujemy dla każdej klasy odpowiednie sprawdzenie. Dzięki temu klasa WynagrodzenieFactoryMethod jest już otestowana. Hmmm... ale w zasadzie poco w taki sposób napisaliśmy ten test, przecież upubliczniliśmy klasy Piekarz, Nauczyciel, Kierownik, a nie chcemy tego robić. Nie po to tworzymy obiekty przez metodę fabrykującą żeby udostępniać te klasy. Wiec jak możemy zmienić test aby te 3 klasy nie były upublicznione? Otóż bardzo prosto :). Usuńmy public w tych 3 klasach, po czym zmieńmy trochę nasz przypadek testowy:



[Test]
public void TestMakePracownik()
{
Assert.AreEqual("PrzykladowaAplikacja.Pracownicy.Kierownik",
WynagrodzenieFactoryMethod.MakePracownik(Enums.TypZawodu.KIEROWNIK).ToString());
Assert.AreEqual("PrzykladowaAplikacja.Pracownicy.Nauczyciel",
WynagrodzenieFactoryMethod.MakePracownik(Enums.TypZawodu.NAUCZYCIEL).ToString());
Assert.AreEqual("PrzykladowaAplikacja.Pracownicy.Piekarz",
WynagrodzenieFactoryMethod.MakePracownik(Enums.TypZawodu.PIEKARZ).ToString());
}


Teraz testy również przechodzą a na dodatek mamy ukryte klasy :) O to nam chodziło.

Weźmiemy się teraz za testy naszych metod obliczających. Napiszmy test dla najprostrzej metody wyliczającej wynagrodzenie. Niewątpliwie znajduje się ona w klasie Nauczyciel. Ciało testu:



[TestFixture]
public class TestNauczyciel
{
[Test]
public void TestObliczWynagrodzenie()
{

}
}


Dobra, to tak. Zawartość metody ObliczWynagrodzenie


return STAWKA_GODZINOWA * przepracowaneGodziny.Dzienne;


stawka godzinowa wynosi 21. Dajmy na to ze przepracowane godzinny dzienne wynoszą 120, 21 * 120 = 2520. Napiszmy zatem test:



[Test]
public void TestObliczWynagrodzenie()
{
PrzepracowaneGodziny pg = new PrzepracowaneGodziny(120, 0, 0);

Assert.AreEqual(2520,
WynagrodzenieFactoryMethod.MakePracownik(Enums.TypZawodu.NAUCZYCIEL).ObliczWynagrodzenie(pg));
}


świetnie, test przechodzi. Spróbujmy teraz udowodnić, że 120 * 21 != 2520 (taki przypadek również nam się przyda do testu) użyjemy do tego AreNotEqual.



Assert.AreNotEqual(2520,
WynagrodzenieFactoryMethod.MakePracownik(Enums.TypZawodu.NAUCZYCIEL).ObliczWynagrodzenie(pg));


testy znów przechodzą, więc tą klasę mamy już pokrytą testami. Po zmianach w tej metodzie będziemy od razu wiedzieli czy coś namieszaliśmy czy też nie.

Tworząc testy dla Kierownika odkrywamy, że... metoda ObliczWynagrodzenie źle działa! No to klient się wkurzy a nam się dostanie. Gdybyśmy pierwsze napisali test a potem dopisali do niego kod, to z pewnością uniknęlibyśmy tego problemu. Gdzie znajduje się błąd? Zacznijmy od wymagań jakie były: Jeśli kierownicy przepracują więcej niż 160, to ich stawka od tej chwili rośnie 2x.
Napiszmy do tego test.



[Test]
public void TestObliczWynagrodzenie()
{
pg = new PrzepracowaneGodziny(161, 0, 0);
Assert.AreEqual(8100, WynagrodzenieFactoryMethod.MakePracownik(Enums.TypZawodu.KIEROWNIK).ObliczWynagrodzenie(pg));
}

PrzykladowaAplikacja.Tests.Pracownicy.TestKierownik.TestObliczWynagrodzenie : Expected: 8100
But was: 161.0f


rozjazd jest baardzo duży, miejmy nadzieję, że klient się zorientuje i przestanie używać naszego programu. Błąd znajduje się w metodzie ObliczGodzinyNadliczbowe



return iloscPrzepracowanychGodzin - PODSTAWOWY_CZAS_PRACY * STAWKA_GODZINOWA;


161 - 160 * 50 = -7839, hehe Kierownik jeszcze musi dopłacić jeśli będzie miał nadgodziny ;) Dlatego test nie przechodzi. Poprawmy metodę ObliczGodzinyNadliczbowe


return (iloscPrzepracowanychGodzin - PODSTAWOWY_CZAS_PRACY) * STAWKA_GODZINOWA * 2;


Teraz jest wszystko dobrze. Udało nam się wyłapać i wyeliminować błąd podczas pisania dla niego testu. Jeśli będziemy tworzyć test PRZED pisaniem unikniemy takich głupot.

Zadzwońmy do klienta i powiedzmy, żeby nie używał tej wersji programu, może będzie wyrozymiały... okazało się, że nie było prądu u niego w firmie więc nie zdarzyli wdrożyć nowej wersji :))) (nie liczmy w prawdziwym życiu na takie farty ;))

Podobne testy piszemy dla Piekarza. Pozostaje pytanie, co w takim razie z metodami prywatnymi, czy je też poddajemy testom. Oczywiście jak zawsze "ZALEŻY". Wg mnie, jeśli mamy jakieś drobne metody prywatne, które nie są krytyczne (w miarę proste), to nie testujemy ich, bowiem tak czy tak jak coś będzie źle, to test wykaże błąd w publicznej metodzie używającej tej prywatnej. Lecz jeśli chcemy to przetestować a poza tym MUSIMY, to zawsze można zmienić z private na public. Lepiej mieć przetestowany i sprawdzony kod. Jeszcze jedną metodą na przetestowanie metod prywatnych jest zmienienie modyfikatora dostępu na protected, wtedy test dziedziczy nasza klasę i dzięki temu mamy dostęp do metody "prywatnej". Krótki przykładzik:




public class A
{
protected void Dodaj(int a, int b)
{
return a + b;
}
}



[TestFixture]
public class TestA : A
{
[Test]
public void TestDodaj()
{
Assert.AreEqual(3, this.Dodaj(1, 2));
}
}



W następnym poście zajmiemy się refaktoryzacją w kierunku wzorca strategia.

Kod źródłowy