Pokazywanie postów oznaczonych etykietą wzorce projektowe. Pokaż wszystkie posty
Pokazywanie postów oznaczonych etykietą wzorce projektowe. Pokaż wszystkie posty

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

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 :)

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

wtorek, 22 kwietnia 2008

[wzorce projektowe] Factory Method (Metoda Wytwórcza/Fabrykująca). Przykładowa aplikacja cz. 1

Dziś zajmiemy się zrozumieniem wzorca projektowego factory method [metody fabrykującej/wytwórczej].

Do czego może się nam przydać:
-do zastąpienia warunkowych wyrażeń
-do uzyskania kodu otwartego na rozbudowę a zamkniętego na modyfikację (o zasadzie OCP będzie przeznaczony osobny post, ale już tutaj postaram się pokazać to na przykładzie)
-zgodne zasadą DIP (ale o tym też później)

Gdzie są koszty?:
-stosowanie wzorca factory method zawsze niesie za sobą większą złożoność kodu
-tworzenie za każdym razem obiektu co może wpłynąć nieznacznie na wydajność, lecz tym bym się (póki co) nie przejmował.

Przychodzi klient:
Potrzebujemy utworzyć małą aplikację, która na podstawie wybranego zawodu wyliczy nam miesięczne wynagrodzenie. Do tego wynagrodzenie zależy od ilości przepracowanych godzin oraz stawki.

Wydaję mi się, że część osób stworzyłaby podobną klase jak tutaj ja zrobiłem:



class Wynagrodzenie
{
private const UInt16 STAWKA_GODZINOWA_PIEKARZA = 10;
private const UInt16 STAWKA_GODZINOWA_NAUCZYCIELA = 21;
private const UInt16 STAWKA_GODZINOWA_KIEROWNIKA = 50;

public static float ObliczWynagrodzenia(TypZawodu zawod, UInt16 iloscPrzepracowanychGodzin)
{
switch (zawod)
{
case TypZawodu.PIEKARZ:
return STAWKA_GODZINOWA_PIEKARZA * iloscPrzepracowanychGodzin;
case TypZawodu.NAUCZYCIEL:
return STAWKA_GODZINOWA_NAUCZYCIELA * iloscPrzepracowanychGodzin;
case TypZawodu.KIEROWNIK:
return STAWKA_GODZINOWA_KIEROWNIKA * iloscPrzepracowanychGodzin;
default:
return 0;
}
}
}


użycie:



UInt16 wyplata = Wynagrodzenie.ObliczWynagrodzenia(TypZawodu.NAUCZYCIEL, 120);

Niby wszystko jest ok, klasa będzie dobrze działać, lecz co z tego jeśli dodanie nowego stanowiska wiąże się z poważną zmianą klasy Wynagrodzenie. Nieumyślnie możemy zrobić jakiś błąd podczas modyfikacji i wszystko będzie się źle liczyło. Oczywiście tutaj, to nie jest AŻ tak dobrze widoczne, lecz wyobraźcie sobie rozbudowaną klasę napisaną w taki sposób, która oblicza jakieś poważne wartości, 1 pomyłka i leżymy.
I tutaj przychodzi nam z pomocą wzorzec Factory Method (oczywiście nie jest to jedyny sposób na dobre rozwiązanie problemu).

zaczynamy od utworzenia interface'u:


interface IPracownik
{
float ObliczWynagrodzenie(UInt16 iloscPrzepracowanychGodzin);
}

teraz każdy pracownik musi implementować metodę ObliczWynagrodzenie


class Piekarz : IPracownik
{
private const UInt16 STAWKA_GODZINOWA = 10;

public float ObliczWynagrodzenie(UInt16 iloscPrzepracowanychGodzin)
{
return STAWKA_GODZINOWA * iloscPrzepracowanychGodzin;
}
}

class Nauczyciel : IPracownik
{
private const UInt16 STAWKA_GODZINOWA = 21;

public float ObliczWynagrodzenie(UInt16 iloscPrzepracowanychGodzin)
{
return STAWKA_GODZINOWA * iloscPrzepracowanychGodzin;
}
}

class Kierownik : IPracownik
{
private const UInt16 STAWKA_GODZINOWA = 50;

public float ObliczWynagrodzenie(UInt16 iloscPrzepracowanychGodzin)
{
return STAWKA_GODZINOWA * iloscPrzepracowanychGodzin;
}
}


użycie:


IPracownik pracownik = WynagrodzenieFactoryMethod.MakePracownik(TypZawodu.NAUCZYCIEL);

pracownik.ObliczWynagrodzenie(120);


Okey, i teraz korzyści ze stosowania factory, jeśli będziemy chcieli dodać kolejny zawód, to musimy tylko utworzyć nowa klasę, która implementuje interface IPracownik oraz dodać do metody MakePracownik kolejnego case'a. Tyle. Dzięki temu nie narażamy inne klasy na modyfikację, a w szczególności algorytm wyliczający wynagrodzenie (który akurat tutaj jest jednakowy i niezwykle prosty). W kolejnych postach postaram się przedstawić zastosowanie innych wzorców na tym przykładzie, lecz będą dochodziły coraz to nowe wymagania "klienta" czyli mnie ;)


I taka porada dnia: nie nadużywajmy tego wzorca, pomyślmy czy pierwsze nad problemem, czy nie da się go rozwiązać w inny sposób, trzeba pamiętać, że wzorce nie należy upychać gdzie się da. Wg mnie powinniśmy dążyć do wzorca projetowego a nie odrazu do jego implementacji. Niech to sie dzieje ewolucyjnie a nie rewolucyjnie.


Zapraszam do ściągnięcia źródeł przykładu