czwartek, 24 kwietnia 2008

[tdd] TDD - Test-Driven Development / NUnit. Krótkie wprowadzenie

Gdzieś w internecie znaleźliśmy coś co nas zainteresowało. Jest to dziwny skrót TDD. Poszperajmy trochę w internecie, by dowiedzieć się co to tak na prawdę jest. Pierwsza pozycja: tdd, hmm chyba nie o to nam chodziło, druga pozycja też nie, co jest? nie ma polskich stron o tym? Przechodzimy na szukanie zagranicznych materiałów tdd od razu lepiej, aż za dobrze. Nie chce nam się czytać tego, za długie! Przelećmy wzrokiem po tym. Wiemy już co oznacza skrót - Test-Driven Development, hmm bardzo ciekawe, czyli jest to wytwarzanie oprogramowania sterowane testami, bełkot ;) Przejdźmy może do przykładu.
Acha tutaj najważniejsza rzecz: przed przystąpieniem do pisania właściwego kodu, musimy zacząć od napisania testu, czyli jak chcemy sprawdzić czy metoda Dodaj jest dobrze napisana:


class Matematyka
{
public static int Dodaj(int a, int b)
{
return a + b;
}
}


a + b = c, prawda? czyli 2 + 3 = 5.

Ok, teraz napiszmy odejmowanie wykorzystując TDD.

(Ściągamy sobie z internetu NUnita, podpinamy dll'ke: nunit.framework.dll do naszego projektu)

Tworzymy nowy plik/klasę o nazwie: TestMatematyka

Świetnie! teraz możemy przygotować naszą klasę do testów.


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NUnit.Framework;

namespace AplikacjaMatematyka
{
[TestFixture] // Informuje nunita, że w tej klasie znajdują się przypadki testowe
class TestMatematyka
{
[Test] // Informujemy nunita, że to jest klasa testowa
public void TestOdejmij()
{
}
}
}

No to teraz sedno: przewidujemy co dana metoda zwróci, czyli wiemy, że Odejmij będzie (uwaga!) odejmować 2 liczby od siebie ;) czyli jak mamy 3 - 2 = 1, no spoko żadne odkrycie.

Teraz musimy zapisać 3 - 2 = 1 w formie testu, ponieważ to wyrażenie będzie sprawdzać czy nasza metoda działa dobrze.

W NUnitcie, porównujemy wartości poprzez użycie metod z klasy Assert. By porównać 2 wartości posłużymy się metodą: Assert.AreEqual(); Napiszmy zatem test sprawdzający poprawność działania metody Odejmij (pamiętajmy, że jeszcze jej nie mamy zaimplementowanej! tym zajmiemy się później, trochę to dziwne na 1st rzut oka)


[Test]
public void TestOdejmij()
{
Assert.AreEqual(1, Matematyka.Odejmij(3, 2));
}

Oznacza to: oczekujemy wartości 1, odejmij 3 od 2

Skompilujmy projekt. BUM! Metoda Odejmij nie istnieje :) zatem, napiszmy ja, wiemy już z góry, ze potrzebujemy 2óch parametrów/zmiennych a i b, na wejściu mamy 3 i 2, musimy tak zrobić by wyszło 1. Nic trudnego prawda?


public static int Odejmij(int a, int b)
{
return a - b;
}

Kompilujemy. Udało się. Otwieramy NUnita, tworzymy nowy projekt, zapisujemy go KONIECZNIE tam gdzie się znajdują nasze dll'ki, bądz exe. Dołączamy exe bądź dll, gdzie znajduje się nasz test do naszego projektu i odpalamy. Wszystko jest ok, testy przeszły. Zróbmy teraz mały błąd w naszej metodzie: return a - b - 1; Kompilacja i testowanie. Test nie przeszedł. Co to oznacza? Oczywiście, to że wprowadziliśmy błąd do wyliczania odejmowania. Błąd był świadomy i wiemy o nim, lecz co jeśli błąd zrobimy NIEŚWIADOMIE? W jakimś miejscu w projekcie? Nie będziemy wiedzieli na dodatek w którym miejscu. Tutaj testy nam przychodzą z pomocą. Jeśli mamy otestowane metody w projekcie wtedy możemy mieć pewność, że wydanie nowej wersji nie będzie zawierało błędów. Brzmi fantastycznie. Lecz przecież wykonanie testu wiąże się z CZASEM. Hehe, ok, ale nie prościej napisać raz test a potem wywoływać go już ile razy chcemy i kiedy chcemy? bez ingerencji "ręcznego" testowania przez użytkownika? "Chyba" testowanie ręczne nam więcej zajmie czasu niż napisanie testu. Zatem mamy zysk czasu a nie stratę.

Ale poco pisaliśmy test na samym początku? Przecież można było zrobić to po napisaniu metody. Otóż właśnie że nie, w tym jest sęk. Jeśli ustalimy co mamy na wejściu a co chcemy mieć na wyjściu, to już będziemy wiedzieć jak sie zabrać do napisania interesującej nas metody. Przypadek z odejmowaniem był banalny, lecz co jeśli będziemy wyliczać jakieś skomplikowane biznesowe wskaźniki czy kto wie co jeszcze. Mamy dzięki temu wszystko sprecyzowane. Do tego tworzymy przy okazji bardzo dobrą dokumentację.

Korzyści z TDD:
-jest duża szansa, że kod będzie poprawny, mimo iż będziemy wprowadzać modyfikacje do kodu
-nie pozwalamy na powstanie CODE LEGACY
-bardzo szybko jesteśmy w stanie zdiagnozować błąd

Koszty... czy w ogóle są?
Ja korzystam dość długo już z TDD i cóż mogę powiedzieć o kosztach. "Jedynie" tyle, że one również muszą być poddawane refaktoryzacji, zmianom wraz gdy zmieniają się wymagania co dana metoda ma robić, trzeba je również konserwować i dbać o nie jak o normalny kod. Tym bardziej, że rozmiar kodu testowego będzie tak długi jak "normalny" kod.

Pytanie: Czy wszystkie metody muszę testować?

i tak i nie, zależy jak leży ;) A tak na serio to ZALEŻY od pewnych czynników, ale to już temat na inny post. Wg statystyk, jakie gdzieś wyczytałem, to jeśli kod jest pokryty testami w 60-80% to jest dobrze, ale nie daje głowy, że dobrze pamiętam. Z praktyki mogę powiedzieć jedno: im więcej tym szczelniej ale myślę, że to żadne odkrycie.

Więcej na ten temat w tutaj.

Kod źródłowy

środa, 23 kwietnia 2008

[refaktoryzacja] Parametryzowanie metod. - Przykładowa aplikacja cz. 3

Ostatnio przyszedł do nas klient i nagle zmienił nam wymagania. Chciał żeby wynagrodzenie piekarza było obliczane na podstawie 2óch parametrów, które by reprezentowały stawki godzinowe nocne ale również i dzienne. Do stawek nocnych doliczany jest bonus w wysokości 5 zł. Pierwsze co nam przyszło do głowy to dodanie nowej zmiennej do metody ObliczWynagrodzenie(UInt16 iloscPrzepracowanychGodzinDziennych, UInt16 iloscPrzepracowanychGodzinNocnych);w interface IPracownik. Jeślibyśmy to zastosowali, to zmiany w kodzie byłyby baaardzo duże. Trzeba by było zmienić wszystkie klasy implementujące IPracownik, lecz nie tylko, każde dodanie nowej zmiennej kończyłoby się ze zmianami w całej strukturze klas a na dodatek inne klasy np Nauczyciel nie wykorzystują tych zmiennych.

Czyżby nie było dobrego rozwiązania tej metody? Przecież musi istnieć. Może po prostu powinniśmy zrobić po prostu "paczkę" zmiennych. Utworzylibyśmy klasę PrzepracowaneGodziny, która by się składała z 2óch zmiennych, dzienne i nocne. Zróbmy to:


class PrzepracowaneGodziny
{
private UInt16 dzienne;
public UInt16 Dzienne
{
get { return dzienne; }
}

private UInt16 nocne;
public UInt16 Nocne
{
get { return nocne; }
}

public PrzepracowaneGodziny(UInt16 dzienne, UInt16 nocne)
{
this.dzienne = dzienne;
this.nocne = nocne;
}
}

Dobrze skoro mamy już klasę "paczkę", to podepnijmy ją do naszego systemu.

Musimy zmienić IPracownik.ObliczWynagrodzenie, teraz nie będzie przyjmować zmiennej iloscPrzepracowanychGodzin, lecz naszą klasę PrzepracowaneGodziny



interface IPracownik
{
float ObliczWynagrodzenie(PrzepracowaneGodziny przepracowaneGodziny);
}


no to skoro zdecydowaliśmy się na taki krok, to musimy pozmieniać we wszystkich klasach implementujących interface IPracownik.



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

public float ObliczWynagrodzenie(PrzepracowaneGodziny przepracowaneGodziny)
{
return STAWKA_GODZINOWA * przepracowaneGodziny.Dzienne;
}
}


wygląda nieźle, ale zobaczymy dalej czy to się sprawdzi.


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

private const UInt16 PODSTAWOWY_CZAS_PRACY = 160;

private static bool CzyPodstawowyCzasPracyZostalPrzekroczony(UInt16 iloscPrzepracowanychGodzin)
{
return iloscPrzepracowanychGodzin > PODSTAWOWY_CZAS_PRACY;
}

private static int ObliczGodzinyNadliczbowe(UInt16 iloscPrzepracowanychGodzin)
{
return iloscPrzepracowanychGodzin - PODSTAWOWY_CZAS_PRACY * STAWKA_GODZINOWA;
}

private static int ObliczPodstawoweWynagrodzenie()
{
return STAWKA_GODZINOWA * PODSTAWOWY_CZAS_PRACY;
}

public float ObliczWynagrodzenie(PrzepracowaneGodziny przepracowaneGodziny)
{
if (CzyPodstawowyCzasPracyZostalPrzekroczony(przepracowaneGodziny.Dzienne))
{
return ObliczPodstawoweWynagrodzenie() +
ObliczGodzinyNadliczbowe(przepracowaneGodziny.Dzienne);
}

return STAWKA_GODZINOWA * przepracowaneGodziny.Dzienne;
}
}

tutaj też niczego sobie. Okey, to brnijmy w to dalej. Teraz na warsztat bierzemy klasę Piekarz:



public float ObliczWynagrodzenie(PrzepracowaneGodziny przepracowaneGodziny)
{
return ((STAWKA_GODZINOWA + BONUS_ZA_PRACE_W_NOCY) * przepracowaneGodziny.Nocne) +
(STAWKA_GODZINOWA * przepracowaneGodziny.Dzienne);
}


i znów miłe zaskoczenie, gładko, rzekłbym zbyt łatwo ;). Uporządkujmy ciało tej metody, znów tak jakoś skomplikowanie jest. Zastosujmy wydzielenie metod poznane w 2 części



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

private const UInt16 BONUS_ZA_PRACE_W_NOCY = 5;

private static int ObliczWynagrodzenieNocne(UInt16 przepracowaneGodzinyNocne)
{
return (STAWKA_GODZINOWA + BONUS_ZA_PRACE_W_NOCY) * przepracowaneGodzinyNocne;
}

private static int ObliczWynagrodzenieDzienne(UInt16 przepracowaneGodzinyDzienne)
{
return STAWKA_GODZINOWA * przepracowaneGodzinyDzienne;
}

public float ObliczWynagrodzenie(PrzepracowaneGodziny przepracowaneGodziny)
{
return ObliczWynagrodzenieNocne(przepracowaneGodziny.Nocne) +
ObliczWynagrodzenieDzienne(przepracowaneGodziny.Dzienne);
}
}


wow i w sadzie zakończyliśmy zlecenie.

Wciągnięci pracą nie słyszeliśmy jak dzwonił do nas klient. Nagrał się na automatyczną sekretarkę:
Właśnie jadę do was by obejrzeć gotowy produkt. Mam nadzieję że pamiętaliście o tym, że piekarz w święta za przepracowane godziny otrzymuje podwójną stawkę.


Cholera! Zapomnieliśmy o tym. Ale zaraz! Przecież nasza aplikacja jest wspaniale napisania. Drobna zmiana powinna załatwić sprawę. Pomyślmy, potrzebujemy jeszcze zmienną: swiateczne, w klasie PrzepracowaneGodziny:


class PrzepracowaneGodziny
{
private UInt16 dzienne;
public UInt16 Dzienne
{
get { return dzienne; }
}

private UInt16 nocne;
public UInt16 Nocne
{
get { return nocne; }
}

private UInt16 swiateczne;
public UInt16 Swiateczne
{
get { return swiateczne; }
}

public PrzepracowaneGodziny(UInt16 dzienne, UInt16 nocne, UInt16 swiateczne)
{
this.dzienne = dzienne;
this.nocne = nocne;
this.swiateczne = swiateczne;
}
}

teraz musimy zmienić Piekarz.ObliczWynagrodzenie


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

private const UInt16 BONUS_ZA_PRACE_W_NOCY = 5;

private const UInt16 MNOZNIK_STAWKI_SWIATECZNEJ = 2;

private static int ObliczWynagrodzenieNocne(UInt16 przepracowaneGodzinyNocne)
{
return (STAWKA_GODZINOWA + BONUS_ZA_PRACE_W_NOCY) * przepracowaneGodzinyNocne;
}

private static int ObliczWynagrodzenieDzienne(UInt16 przepracowaneGodzinyDzienne)
{
return STAWKA_GODZINOWA * przepracowaneGodzinyDzienne;
}

private static int ObliczWynagrodzenieZaSwieta(UInt16 przepracowaneGodzinySwiateczne)
{
return STAWKA_GODZINOWA * MNOZNIK_STAWKI_SWIATECZNEJ * przepracowaneGodzinySwiateczne;
}

public float ObliczWynagrodzenie(PrzepracowaneGodziny przepracowaneGodziny)
{
return ObliczWynagrodzenieNocne(przepracowaneGodziny.Nocne) +
ObliczWynagrodzenieDzienne(przepracowaneGodziny.Dzienne) +
ObliczWynagrodzenieZaSwieta(przepracowaneGodziny.Swiateczne);
}
}


TYLE! W sam raz, bo właśnie przyszedł klient. Ufff udało nam się, tym razem, lecz coś nam chodzi po głowie, że ten kod, który stworzyliśmy wcale nie jest taki dobry... cdn.

Kod źródłowy

wtorek, 22 kwietnia 2008

[refaktoryzacja] Tworzenie małych pomocniczych metod. Przykładowa aplikacja cz. 2

W poprzedniej części naszej aplikacji poradziliśmy sobie z problemem narażenia kodu na awarie związaną z dodaniem nowych pracowników, teraz zajmiemy się jego dalsza rozbudową.

Przychodzi do nas 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. Jeśli kierownicy przepracują więcej niż 160, to ich stawka od tej chwili rośnie 2x.

Pierwsze co musimy zrobić, to otworzyć sobie kod źródłowy z naszego poprzedniego przykładu.
Weźmy się może za dodanie nowej funkcjonalności dla kierownika, czyli jeśli jego ilość godzin będzie większa od 160, to jego stawka sie podwaja:



public float ObliczWynagrodzenie(UInt16 iloscPrzepracowanychGodzin)
{
if (iloscPrzepracowanychGodzin > 160)
{
return (STAWKA_GODZINOWA * 160) + (iloscPrzepracowanychGodzin - 160 * STAWKA_GODZINOWA);
}

return STAWKA_GODZINOWA * iloscPrzepracowanychGodzin;
}

Dość szybko nam poszło, lecz hola amigo!

Nagle przychodzi klient:
Witam, rozmyśliliśmy się co do podstawowego wymiaru pracy kierownika, nie chcemy by wynosił 160, lecz 170.

Extra, teraz musimy dokonać zmian w 3 miejscach, a na dodatek jeśli zrobi to obca osoba, to może nawet się nie zorientować co oznacza cyfra 160. Nie możemy tak tego zostawić. Musimy poddać kod drobnej refaktoryzacji. Tworzymy stałą PODSTAWOWY_WYMIAR_PRACY i przypisujemy jej wartość 160, następnie wszędzie w klasie 160 zamieniamy na tą stała. Proszę!


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

private const UInt16 PODSTAWOWY_CZAS_PRACY = 160;

public float ObliczWynagrodzenie(UInt16 iloscPrzepracowanychGodzin)
{
if (iloscPrzepracowanychGodzin > PODSTAWOWY_CZAS_PRACY)
{
return (STAWKA_GODZINOWA * PODSTAWOWY_CZAS_PRACY) + (iloscPrzepracowanychGodzin - PODSTAWOWY_CZAS_PRACY * STAWKA_GODZINOWA);
}

return STAWKA_GODZINOWA * iloscPrzepracowanychGodzin;
}
}

Teraz kod jest czytelniejszy i bardziej elastyczny. Jak przyjdzie klient i powie, że jednak podstawowy czas pracy dla kierownika ma wynosić 180, to nikt nie będzie miał problemów z tą zmianą. Lecz metoda ObliczWynagrodzenie wydaje sie bardzo skomplikowana/rozciągnięta, musimy ją jakoś uprościć.

warunek:
if (iloscPrzepracowanychGodzin > PODSTAWOWY_CZAS_PRACY)

zamienimy na statyczna metodę:


private static bool CzyPodstawowyCzasPracyZostalPrzekroczony(UInt16 iloscPrzepracowanychGodzin)
{
return iloscPrzepracowanychGodzin > PODSTAWOWY_CZAS_PRACY;
}

dzięki temu po przeczytaniu warunku od razu się zorientujemy co ulega sprawdzeniu.

ta linijka jest jeszcze trochę zagmatwana:


return (STAWKA_GODZINOWA * PODSTAWOWY_CZAS_PRACY) + (iloscPrzepracowanychGodzin - PODSTAWOWY_CZAS_PRACY * STAWKA_GODZINOWA);


stwórzmy z niej 2 metody:

wyrażenie:
(STAWKA_GODZINOWA * PODSTAWOWY_CZAS_PRACY)

zamienimy na:


private static int ObliczPodstawoweWynagrodzenie()
{
return STAWKA_GODZINOWA * PODSTAWOWY_CZAS_PRACY;
}

natomiast:
(iloscPrzepracowanychGodzin - PODSTAWOWY_CZAS_PRACY * STAWKA_GODZINOWA)

na:


private static int ObliczGodzinyNadliczbowe(UInt16 iloscPrzepracowanychGodzin)
{
return iloscPrzepracowanychGodzin - PODSTAWOWY_CZAS_PRACY * STAWKA_GODZINOWA;
}


Proszę, z linijki niemal tak długiej jak "spaghetti" udało nam się utworzyć całkiem zgrabny kod:



public float ObliczWynagrodzenie(UInt16 iloscPrzepracowanychGodzin)
{
if (CzyPodstawowyCzasPracyZostalPrzekroczony(iloscPrzepracowanychGodzin))
{
return ObliczPodstawoweWynagrodzenie() +
ObliczGodzinyNadliczbowe(iloscPrzepracowanychGodzin);
}

return STAWKA_GODZINOWA * iloscPrzepracowanychGodzin;
}

Jak widać refaktoryzacja działa cuda.

Uff uporaliśmy sie z wymogiem naszego klienta, jesteśmy bardzo z siebie zadowoleni i już wiemy, że nic nam dobrego humoru nie popsuje.
Nagle dzwoni klient:

Witam! Zapomniałem powiedzieć, ze piekarze muszą mieć inaczej liczoną stawkę w nocy.

Hmm co by tu wykombinować? Żeby to zrobić to dla piekarzy gdzieś trzeba byłoby utworzyć zmienne, które by reprezentowały ilość przepracowanych godzin w dzień oraz w nocy. Jeśli tak zrobimy, musielibyśmy naruszyć nasza metodę ObliczWynagrodzenie przyjmującą wyłączenie 1 zmienną a nie 2! Jeśli to zrobimy będziemy musieli dokonać zmian we wszystkich klasach implementujących IPracownik. Nie możemy na to sobie pozwolić!

Telefon od klienta:
Witam, jeszcze o jednej zmianie zapomniałem wam powiedzieć, jeśli piekarz wyrobi w święta jakieś godziny to musimy mu je pomnożyć przez 2.

istny horror... cdn.

a tym czasem tutaj znajdują się źródła

[refaktoryzacja] Zasada. Dbajmy o nazwy klas i zmiennych

Niezwykle ważną sprawą przy wytwarzaniu DOBREJ jakości kodu są poprawnie dobrane nazwy: metod, klas, zmiennych etc. Może się oczywiście tak zdarzyć, iż w pewnym momencie podczas pracy nad projektem, dojdziemy do wniosku, że taka a taka zmienna powinna sie tak i tak nazywać. Więc przystępujemy do refaktoryzacji. Nawet stosowanie tak prostej zasady przyczynia się do tworzenia na prawdę dobrego i przejrzystego kodu. Może się to na pierwszy rzut oka wydawać śmieszne, lecz czy nie zdarzyło się Wam popatrzeć do kodu i rozmyślać o tym co dana zmienna robi? A gdyby dobrze była nazwana, to już po tym byśmy wiedzieli do czego służy.

Korzyści:
-czytelny kod
-zaoszczędzony CZAS

[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

poniedziałek, 21 kwietnia 2008

[c#] nowa linia w string - Environment.NewLine

Może temat i błahy, lecz nie każdy wie jak dobrze robić nową linie.

Całą sprawę załatwia nam: Environment.NewLine

zamiast pisać:

string s = "1linia\n2linia";

string s = @"1linia
2linia";


można po prostu użyć:

string s = "1linia" + Environment.NewLine + "2linia";


jaki z tego zysk? Przede wszystkim - Przenośność, jeśli używamy Environment.NewLine, to spod widnowsa/linuxa/PDA będziemy mieć ten sam efekt. Nie musimy pamietać czy na linuxie/windzie jest \n\r czy \r\n czy \n?

[ASP.NET] Literal Multiline, ale bez tagow HTML'owych

Dajac uzytkownikom dostep np. do pisania postow na stronie, nieraz nie chcemy zeby byly interpretowane przez przegladarke tagi html'owe [z np. wzgledow bezpieczenstwa]. Bierzemy sobie kontrolke literal, badz label i przypisujemy do wlasciwosci Text naszego string'a:

Literal1.Text = "ala ma\nkota";


Wgrywamy strone, patrzymy a tu literal wyglada tak:

ala ma kota


Co jest? Nie ma "entera"? Wlasnie i tu jest problem, ktory zostaje rozwiazany przez ten jakze magiczny ;) kod:

Literal1.Text = Server.HtmlEncode("ala ma\nkota).Replace("\n", "<br />");

Teraz nasz literal wyglada tak jak powinien, czyli:
ala ma
kota