niedziela, 5 sierpnia 2012

[C#|Visual Studio] Generowanie kodu za pomocą plików T4

Pliki T4 (Text Template Transformation Toolkit) służą do generacji plików o dowolnym rozszerzeniu z bloków tekstu połączonych z blokami kodu pisanych w C# lub Visual Basic. Obowiązuje pewna prosta do opanowania składnia umożliwiająca oddzielenie kodu od tekstu oraz obliczanie wartości które chcemy wstawić w danym miejscu. Transformacja pliku następuje w momencie, gdy zapiszemy plik o rozszerzeniu .tt.

Aby dodać nowy plik, wybieramy Add...New Item... lub ctrl + shift + A. Z listy wybieramy Text Template:


Wygenerowane zostaną dwa pliki : .tt i .txt. Rozszerzenie możemy zmienić w nagłówku pliku .tt, podobnie jak język, w którym chcemy pisać. Kod od tekstu i ustawień oddziela się różnymi rodzajami tagów:
Tag otwierający ustawienia : <#@
Tag otwierający kod : <#
Zwykły tekst nie jest zamykany w żadnych tagach.

Poniższy przykład pokazuje możliwości szablonów T4.
Załóżmy, że chcemy na podstawie pewnej tablicy wygenerować klasy z konstruktorami. Poniższy kod pokazuje, jak to uczynić.


<#@ template debug="false" hostspecific="false" language="C#" #>
<#@ output extension=".cs" #>

namespace MyNamespace 
{

<#  var Countries = new string[]{"Poland","England","Ukraine"};
 foreach (var country in Countries)
    {#>
 public class <#=country#>
 {
  public <#=country#>()
  {
  }
 }
                 
<#}#>
}

W wyniku otrzymamy plik .cs o następującej treści :

namespace MyNamespace 
{

 public class Poland
 {
  public Poland()
  {
  }
 }
                 
 public class England
 {
  public England()
  {
  }
 }
                 
 public class Ukraine
 {
  public Ukraine()
  {
  }
 }
                 
}

niedziela, 29 lipca 2012

Wzorce projektowe

Spis postów na temat wzorców projektowych:

SOLID:



Wzorce:


[Wzorce projektowe] State

Analogia z życia:

Chcąc pobrać pieniądze idziemy do bankomatu. Po włożeniu karty i podaniu PINu możemy zgłosić żądanie pobrania pieniędzy. Jeśli spełnione jest kilka warunków, to maszyna wyda nam kwotę jakiej żądamy. Może się jednak zdarzyć tak, że nie mamy środków na koncie, albo nasze konto zostało zablokowane. Inną możliwością jest brak funduszy w maszynie. Każda z tych sytuacji jest osobnym stanem, który musi być rozpoznany i obsługiwany przez bankomat.





Zastosowanie:

W każdej sytuacji, gdy mamy do czynienia z obiektem, którego akcje zależą od stanu w jakim się znajduje. Stan obiektu jest zmieniany w czasie gdy aplikacja jest uruchomiona. Przejścia pomiędzy stanami niekonieczną muszą być dozwolone w pewnym kontekście, co też należy obsłużyć.

Zasada działania:

Głównym rdzeniem programu jest obiekt zmieniający stany dynamicznie. Stan reprezentuje się np. za pomocą enuma. Obiekt udostępnia metodę do zmiany stanu i sprawdzenia, czy dany stan można w danej chwili osiągnąć. Każdy stan implementuje jeden wspólny interfejs i udostępnia metody reprezentujące akcje.

Przykład implementacyjny:

    public interface ITvActions
    {
        void TurnOn();
        void TurnOff();
        void ChangeChannel(int n);
    }
public abstract class TVState
    {
        public Tv Owner { get; set; }

        protected TVState(Tv @object)
        {
            Owner = @object;
        }
    }
 public class RunningState : TVState, ITvActions
    {
        public RunningState(Tv @object) : base(@object)
        {
        }

        public void TurnOn()
        {
            Console.WriteLine("TV already running");
        }

        public void TurnOff()
        {
            Owner.IsRunning = false;
            Owner.SetState("Off");
            Console.WriteLine("Turned Off");
        }

        public void ChangeChannel(int n)
        {
            Owner.CurrentChannel = n;
            Console.WriteLine("Switched to " + n);
        }
    }
class TurnedOffState : TVState, ITvActions
    {
        public TurnedOffState(Tv @object) : base(@object)
        {
        }

        public void TurnOn()
        {
            Owner.SetState("Running");
            Owner.IsRunning = true;
            Console.WriteLine("Succesfully turned on");
        }

        public void TurnOff()
        {
            Console.WriteLine("Already turned off");
        }

        public void ChangeChannel(int n)
        {
            Console.WriteLine("Unable to change");
        }
    }
public class Tv : ITvActions
    {
        public ITvActions State { get; set; }
        public int CurrentChannel { get; set; }
        public bool IsRunning { get; set; }

        public Tv()
        {
            IsRunning = true;
            CurrentChannel = 1;
            State = new RunningState(this);
        }

        public void TurnOn()
        {
            State.TurnOn();
        }

        public void TurnOff()
        {
            State.TurnOff();
        }

        public void ChangeChannel(int n)
        {
            State.ChangeChannel(n);
        }

        public void SetState(string state)
        {
            switch (state)
            {
                case "Running":
                    State = new RunningState(this);
                    break;
                case "Off":
                    State = new TurnedOffState(this);
                    break;
            }
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            Tv tv = new Tv();
            tv.TurnOff();
            tv.ChangeChannel(4);
            tv.TurnOn();
            tv.ChangeChannel(1);
            Console.ReadKey();
        }
    }

sobota, 28 lipca 2012

[Wzorce projektowe] Service Locator

Analogia z życia:

Dzisiejsze telefony komórkowe oferuję wiele funkcjonalności. Możemy na przykład napisać wiadomość tekstową lub do kogoś zadzwonić. Z punktu widzenia użytkownika, wysłanie takiej wiadomości jest wykorzystaniem pewnej usługi. Piszemy wiadomość, podajemy numer i klikamy wyślij. To w jaki sposób wiadomość zostanie przekazana i ile za nią zapłacimy zależy między innymi od operatora sieci. Wkładając kartę SIM, możemy na starcie określić wspomniane warunki, aby później po uruchomieniu telefonu w każdej chwili korzystać z usługi w wybranej wersji.


Zastosowanie:

Wzorzec lokatora usług znajduje zastosowanie w sytuacji, gdy chcemy odseparować obiekty, od usług z których korzystają. Zwiększy to niezależność komponentów i ułatwi przyszłe wprowadzanie modyfikacji. Dodatkowo mając kilka podobnych serwisów, można wybierać któryś z nich przed uruchomieniem aplikacji przez odpowiednie ustawienia konfiguracyjne. Rozwiązanie takie wspiera testowalność.

Zasada działania:

Wprowadza się klasę lokatora usług, zawierającą statyczne pole typu HashTable z usługami. Kluczem do usługi w tablicy może być typ usługi lub podana nazwa. Dodatkowo klasa lokatora usług zawiera statyczne metody do pobrania i dodania nowego serwisu. Serwisy można dodawać na starcie aplikacji, np. w WPF czy Silverlight w metodzie OnStartup.

Przykład implementacyjny:

 public interface ILogger
    {
        void Write(string msg);
    }

    public class ConsoleLogger : ILogger
    {
        public void Write(string msg)
        {
            Console.WriteLine(msg);
        }
    }
internal class ServiceLocator
    {
        private static readonly Hashtable Services 
            = new Hashtable();

        public static void AddService<T>(T service)
        {
            Services.Add(typeof(T),service);
        }

        public static void AddService<T>(string name, T service)
        {
            Services.Add(name, service);
        }

        public static T GetService<T>()
        {
            return (T)Services[typeof(T)];
        }

        public static T GetService<T>(string name)
        {
            return (T)Services[name];
        }

    }
public class Fibonnaci
    {
        public int Number { get; set; }
        public int LastNum { get; set; }

        public Fibonnaci()
        {
            LastNum = 0;
            Number = 1;
        }

        public int Next()
        {
            var n = LastNum + Number;
            LastNum = Number;
            Number = n;
            IsDivisibleBy4();
            return n;
        }

        public void IsDivisibleBy4()
        {
            if (Number%4 != 0) return;
            var logger = ServiceLocator.GetService<ILogger>();
            logger.Write(String.Format
                             ("Number {0}, last : {1}",Number, LastNum));
        }
    }
class Program
    {
        static Program()
        {
            ServiceLocator.AddService<ILogger>(new ConsoleLogger());
        }

        static void Main(string[] args)
        {
            Fibonnaci fibonnaci = new Fibonnaci();
            for (int i = 0; i < 100; i++)
            {
                fibonnaci.Next();
            }
            Console.ReadKey();
        }
    }

[Wzorce projektowe] Repository

Analogia z życia:

Chcąc wypożyczyć książki udajemy się do biblioteki. Osoby pracujące tam pomagają nam znaleźć interesujące nas pozycje, podają z magazynu egzemplarze, które nas interesują oraz wstawiają te, które chcemy oddać. W przypadku bibliotek samoobsługowych musielibyśmy wiedzieć o tym, w jaki sposób wyszukiwać i gdzie fizycznie szukać konkretnych egzemplarzy.



Zastosowanie:

Wszędzie tam, gdzie mamy do czynienia z dostępem do danych : połączenia SQL, pobieranie danych z plików, czy webserwisów. Wzorzec repozytorium zapewnia enkapsulację kodu odpowiadającego za połączenie ze źródłem danych w taki sposób. Jest to zatem warstwa pośrednia pomiędzy buissness logic, a źródłem danych, co ułatwia np. testowanie. Wzorzec często występuję w parze z wzorcem Unit of Work odpowiadającym za zatwierdzanie lub wycofywanie zmian.

Zasada działania:

Wprowadza się obiekt repozytorium udostępniający pewne API. W skład API wchodzą takie metody, jak dodawanie encji, usuwanie czy przeszukiwanie kolekcji encji. Bazowe API może być generycznym interfejsem, z którego dziedziczą konkretne repozytoria.

Przykład implementacyjny:

public interface IEntity
    {
        int Id { get; set; }
    }

    public class Customer : IEntity 
    {
        public string Name { get; set; }
        public string Mail { get; set; }
        public string Address { get; set; }
        public string City { get; set; }
        public int Id { get; set; }
    }
public interface IRepository<T>
        where T : IEntity
    {
        void Add(T instance);
        void Remove(T instance);
        IEnumerable<T> Find(Predicate<T> predicate);
    }

    public class CustomerRepository : IRepository<Customer>
    {
        private List<Customer> _entities;

        private int _count;

        public CustomerRepository()
        {
            try
            {
               LoadDataFromXml();
            }
            catch (Exception)
            {
               Console.WriteLine("Unable to load data");
            }
        }

        public void LoadDataFromXml()
        {
            _entities = new List<Customer>();
            XDocument doc = XDocument.Load("Records.xml");
            var list = doc.Elements("records").Elements("record");
            foreach (var element in list)
            {
                IEnumerable<XElement> properties = element.Elements();
                var customer = new Customer();
                customer.Id = _count++;
                customer.Name = properties.ElementAt(0).Value;
                customer.Mail = properties.ElementAt(1).Value;
                customer.Address = properties.ElementAt(2).Value;
                customer.City = properties.ElementAt(3).Value;
                _entities.Add(customer);
                }
        }

        public void Add(Customer instance)
        {
            instance.Id = _count++;
            _entities.Add(instance);
        }

        public void Remove(Customer instance)
        {
            _entities.Remove(instance);
        }

        public IEnumerable<Customer> Find(Predicate<Customer> predicate)
        {
            return _entities.Where(customer => predicate(customer));
        }
    }
class Program
    {
        static void Main(string[] args)
        {
            IRepository<Customer> repository = new CustomerRepository();

            var results = repository.Find(c => c.City.StartsWith("C"));
            foreach (Customer customer in results)
            {
                Console.WriteLine(customer.City);
            }

            repository.Add(new Customer()
                               {
                                   Name = "Jonnie Walker",
                                   Address = "Elm Street 12/9",
                                   Mail = "jwalker@mit.edu",
                                   City = "NY"
                               });

            Console.ReadKey();
        }
    }

wtorek, 24 lipca 2012

[Wzorce projektowe] Prototype

Analogia z życia:

Podróżując lubimy robić zdjęcia. Są one pamiątką z miejsc, które odwiedziliśmy i chwil, które przeżyliśmy. Może się tak zdarzyć, że wywołamy pewne zdjęcie, na którym jest grupa osób. Gdy każda z tych osób obejrzy zdjęcie, dojdzie do wniosku, że chce takie samo. Możemy oczywiście zebrać całą grupę, pojechać w to samo miejsce i zrobić jeszcze zdjęcie kilka razy. Ale na 100% będzie ono inne, a jego zrobienie będzie nas kosztowało sporo wysiłku, czasu a może i pieniędzy. Można także zrobić kopię u fotografa, przez co zaoszczędzimy sobie wymienionych kłopotów.

Zastosowanie:

Prototyp jest jednym z najprostszych, o ile nie najprostszym wzorcem projektowym. Stosuje się go wszędzie tam, gdzie jest potrzebna kopia pewnego obiektu, a jej utworzenie w sposób tradycyjny wydaje się bezsensowne. Proces tworzenia może być np. bardzo długi (pobieranie z bazy danych, pobieranie pliku), W innym przypadku odtworzenie obiektu może wymagać wywołania szeregu metod lub też stan obiektu może być w pewnym sensie niestabilny i trudny do odtworzenie w ogóle.


Zasada działania:


Używając języka C# wystarczy zaimplementować interfejs ICloneable dla obiektu, który chcemy klonować, pamiętając o zasadach tworzenia kopii : Deep vs Shallow Copy. Dla obiektów z propercjami prostych typów wystarczy metoda MemberwiseClone().

Przykład implementacyjny:


public class RandomMelody : ICloneable
    {
        public List<int> Frequencies { get; set; }
        public List<int> Durations { get; set; }

        internal RandomMelody(){}

        public RandomMelody(int n)
        {
            Frequencies = new List<int>();
            Durations = new List<int>();

            Random r = new Random();

            for (int i = 0; i < n; i++)
            {
                Frequencies.Add((r.Next()%300) + 400);
                Durations.Add((r.Next() % 25) + 100);
            }
        }

        public void Play()
        {
            for (int i = 0; i < Frequencies.Count; i++)
            {
                Console.Beep(Frequencies[i], Durations[i]);
            }
        }

        public object Clone()
        {
            var rm = new RandomMelody();
            rm.Durations = new List<int>();
            rm.Frequencies = new List<int>();
            for (int i = 0; i < Durations.Count; i++)
            {
                rm.Durations.Add(Durations[i]);
                rm.Frequencies.Add(Frequencies[i]);
            }
            return rm;
        }
 class Program
    {
        static void Main(string[] args)
        {
            var s1 = new RandomMelody(10);
            var s2 = new RandomMelody(10);
            var s3 = s1.Clone() as RandomMelody;

            Console.WriteLine("Original song");
            s1.Play();
            Console.WriteLine("New song");
            s2.Play();
            Console.WriteLine("Original song");
            s1.Play();
            Console.WriteLine("Copy");
            s3.Play();
        }
    }

poniedziałek, 23 lipca 2012

[Wzorce projektowe] Null Object

Analogia z życia:

Każdy użytkownik komunikatora gadu - gadu ma do dyspozycji kilka statusów. Może być np. dostępny lub niewidoczny. Mając na liście znajomych, nie możemy w prosty sposób rozróżnić kto z nich jest niewidoczny, a kogo nie ma w danej chwili przy komputerze. Komunikator zapewnia jednak, że możemy wysłać wiadomość do każdego z nich : jeśli użytkownik jest przy komputerze, to może nam błyskawicznie odpisać, natomiast gdy go nie ma, wiadomość nie zaginie, lecz zostanie mu dostarczona po zalogowaniu do komunikatora.



Zastosowanie:

Wzorca Null Object można użyć, gdy mamy do czynienia z dużą ilością obiektów, które w pewnym etapie programu mają wartość null. Aby uniknąć wyjątku NullReference oraz sprawdzeń, czy dany obiekt jest nullem, można wprowadzić dodatkowy obiekt bez jakiejkolwiek funkcjonalności, np. zawierający puste metody tego samego interfejsu co zwykły obiekt. Kolejnym powodem, dla którego warto zastosować ten wzorzec jest sytuacja, w której klient pobiera obiekty i nie chcemy, aby zajmował się sprawdzaniem, czy ich wartość jest równa null.

Zasada działania:

Obiekt klienta przechowuje referencję do obiektu typu pewnej klasy abstrakcyjnej. Z tej klasy dziedziczą dwa typy : rzeczywisty obiekt i obiekt - null. Można także wprowadzić kilka obiektów null jako reprezentację różnych stanów "nieaktywności". W niektórych przypadkach warto rozważyć zastosowanie singletonu, jako obiektu - null.

Przykład implementacyjny:

public class Fire
    {
        public int Power { get; set; }
    }

    public abstract class FirefighterBase
    {
        public abstract void Distinguish(Fire fire);

        static InactiveFirefighter _inactive = new InactiveFirefighter();

        public static FirefighterBase NULL
        {
            get { return _inactive; }
        }

        public class InactiveFirefighter : FirefighterBase
        {
            public override void Distinguish(Fire fire)
            {
                Console.WriteLine("Cannot help you");
            }
        }
    }

    public class ActiveFirefighter : FirefighterBase
    {
        private int _strength;

        public ActiveFirefighter()
        {
            Random r = new Random();
            _strength = r.Next() % 25;
        }

        public override void Distinguish(Fire fire)
        {
            fire.Power -= _strength;
            Console.WriteLine("Distinguishing...");
        }
    }
 public static class FireBrigade
    {
        public static FirefighterBase GetFireFighter(int id)
        {
            if(id % 2 ==0)
            {
                return FirefighterBase.NULL;
            }
            else
            {
                return new ActiveFirefighter();
            }
        }
    }
class Program
    {
        static void Main(string[] args)
        {
            Fire f = new Fire() {Power = 300};
            List<FirefighterBase> fighters = new List<FirefighterBase>();
            for (int i = 0; i < 15; i++)
            {
                fighters.Add(FireBrigade.GetFireFighter(i));
            }
            foreach (FirefighterBase firefighterBase in fighters)
            {
                firefighterBase.Distinguish(f);
            }

            Console.WriteLine(String.Format("Fire power is now... {0}",f.Power));

            Console.ReadKey();
        }
    }