czwartek, 31 lipca 2014

Quick: #CodeCleanup: alternatywa dla if_else, czyli ?:

Zapewne często zdarza Wam się tworzyć zmienne i przypisywać im pewne, uwarunkowane "od czegoś" wartości. Czasami są to skomplikowane uwarunkowania, jednak bardzo często warunkowanie jest proste: "jeśli coś, to X, w przeciwnym wypadku Y". Ale do rzeczy:

Jak to działa?

Znając warunkowanie if...else wiemy, że pojawia się warunek sprawdzający. Jeśli warunek zostanie spełniony, wykonywana jest część zaraz pod warunkiem.
Opcjonalnie możemy skorzystać z:
  • else if (jeśli pierwszy warunek nie został spełniony, sprawdzamy inny warunek)
  • else ("w przeciwnym wypadku, jeśli pozostałe warunki nie zostały spełnione").
C# umożliwia zapisanie naszego warunku w prostrzej formie, z wykorzystaniem ? i :

[warunek] ? [jeśli prawda] : [jeśli fałsz]

Kto używa funkcji w popularnym arkuszu kalkulacyjnym, od razu dopatrzy się podobieństwa funkcji "Jeżeli" (If) w tymże programie :)

Oczywiście możemy stworzyć bardziej złożone warunkowanie,np

[warunek] && [warunek2] || [warunek3] ? [jeśli prawda] : [jeśli fałsz]

Możemy też zagnieździć warunki:

[warunek1] ? [warunek2] ? [jeśli spełniony warunek1 i warunek2] : [jeśli spełniony pierwszy warunek] : [jeśli fałsz]

Przykład


Załóżmy, że chcemy sprawdzić, czy jakaś liczba jest podzielna przez 2, a jeśli tak, to zapiszemy sobie do naszej zmiennej odpowiednią wartość:

              int liczba = 23;


1:          bool czyPodzielnaPrzezZero;
2:            if (liczba % 2 == 0)
3:            {
4:                czyPodzielnaPrzezZero = true;
5:            }
6:            else
7:            {
8:                czyPodzielnaPrzezZero = false;
9:            }


Ilość linii do weryfikacji: 9 (lub 7, zależnie od przyjętego standardu). sporo.

oszczędzania czas rozpocząć:

            int liczba = 23;
1:            bool czyPodzielnaPrzezZero = true;
2:            if (liczba % 2 == 0)
3:            {
4:                czyPodzielnaPrzezZero = true;
5:            }


Wynik: 5(4) linii. Nieźle. Ale co jeśli podobnych prostych warunków byłoby więcej? Spoko :) Oszczędzimy jeszcze trochę...  wykorzystamy cudowny ? oraz :


      int liczba = 23;
1:      bool czyPodzielnaPrzezZero = liczba%2==0 ? true : false;


W rezultacie, naszą prostą weryfikację wykonaliśmy w jednej linii, a w dodatku od razu przypisaliśmy do naszej zmiennej wartość (o korzyściach płynących z przypisania od razu wartości do zmiennej napiszę w jednym z kolejnych postów).


Kiedy stosować, kiedy unikać?

Wspomniany "skrót" możemy śmiało wykorzystać przy prostych warunkach, zdecydowanie wpłynie na poprawę czytelności kodu. Przy odrobinie wprawy możliwe będzie znaczne skrócenie tworzonego kodu.

Nie polecam natomiast stosowania bardziej zagnieżdżonego warunkowania. 
Co prawda nic nie stoi na przeszkodzie, abyśmy sprawdzali kilka warunków w jednej linijce, np w Norwegii mocne trunki (wódka, wino) można nabyć dopiero po ukończeniu 21 lat:

            int wiek = 21;
            string alkohol = wiek<18? "Nie sprzedajemy" : wiek<21? "Co najwyżej piwo" : "A pij Pan co chcesz, starość nie radość";



...tylko pytanie, czy taki kod będzie czytelny. Osobiście - odradzam.

Ponadto, jeśli zastosowaliśmy już switch...case, to raczej dobrze zrobiliśmy i nie warto przesiadać się na ? : .

środa, 23 lipca 2014

O stateczności słów kilka, czyli static (zmienne, metody, klasy)

Static - o co chodzi?

Programowanie obiektowe przyniosło pojęcie klas i obiektów. W odróżnieniu od programowania strukturalnego (np C) nie musimy już tworzyć często powielających się zmiennych, tylko wystarczy stworzyć instancję danej klasy (zwaną dalej obiektem).

Czasami jednak możemy odnieść wrażenie, że tworzymy nadmiarowo pewne obiekty tylko po to, żeby raz ich użyć.

Definicja

 Klasa statyczna jest swojego rodzaju odpowiednikiem obiektu - określamy pewną stałą strukturę metod lub zmiennych (stąd nazwa), które dostępne są globalnie, bez konieczności tworzenia obiektów danej klasy.

Zamiast tworzyć nowy obiekt, wystarczy wówczas odwołać się do klasy jak do obiektu,

np
public static class Test
    {

        public static int n = 50;
        public static void Sprawdz()
        {
            //rób coś
        }
    }


Później, w obrębie naszej przestrzeni nazw (namespace) metodę możemy wywołać w następujący sposób (bez konieczności deklarowania obiektu):

...
            Test.Sprawdz();
...


Widoczną zmienną również możemy modyfikować w dokładnie ten sam sposób:

...
            Test.n = 100;
...


Co więcej, jeśli zmienimy wartość zmiennej, zmiana będzie widoczna w obrębie działania naszego programu globalnie.

Przykład - klasa nie statyczna

Załóżmy, że tworzymy prostą aplikację obliczającą pole i obwód figur geometrycznych na potrzeby szkoły podstawowej. Nasz kod może wyglądać mniej więcej tak:

namespace matematykaDlaNajmlodszych
{
    class Program
    {
        public class Mojeliczenie
        {
           
        }

        public class Prostokat
        {

            public Prostokat(int A, int B)
            {
                a = A;
                b = B;
            }


            public int a;
            public int b;


            public int pole(int a, int b)
            {
                return a * b;
            }

            public int obwod(int a, int b)
            {
                return (2 * a) + (2 * b);
            }
        }


        static void Main(string[] args)
        {
            Console.WriteLine("a: {0}\nb: {1}", a, b);
           
           
            Prostokat p = new Prostokat();


            p.a = 2;
            p.b = 3;
 


            int pole = p.pole(p.a, p.b);
            Console.WriteLine("Pole prostokąta (a * b): {0}", pole);
            //Ok, ale co jeśli będę musiał częściej, w innych klasach obliczać pole prostokąta?
            //Przecież obliczenie pola prostokąta się nie zmieni!

            Console.WriteLine("Naciśnij dowolny przycisk aby kontynuować...");
            Console.ReadKey(false);     
        }
    }

}


Jak widzimy, wszystko jest ok, działa. Jest tylko jedna mała kwestia...
Jeśli w innej części  programu będziemy chcieli ponownie obliczyć pole prostokąta (np w obrębie innej klasy), znowu będziemy musieli zadeklarować obiekt klasy Prostokat...


Na szczęście ktoś wpadł na pomysł: Stwórzmy Static.

Zmieńmy zatem metody naszej klasy na statyczne...

        public class Prostokat
        {
            public static int pole(int a, int b)
            {
                return a * b;
            }

            public static int obwod(int a, int b)
            {
                return (2 * a) + (2 * b);
            }
        }


...i mała zmiana w metodzie Main...
        static void Main(string[] args)
        {
            int a = 2;
            int b = 3;
            Console.WriteLine("a: {0}\nb: {1}", a, b);
            //Tym razem metoda jest startyczna,
            //nie ma potrzeby deklarowania obiektu klasy Prostokat do obliczenia pola!
            int pole = Prostokat.pole(a, b);

            Console.WriteLine("Pole prostokąta (a * b): {0}", pole);
            Console.WriteLine("Naciśnij dowolny przycisk aby kontynuować...");
            Console.ReadKey(false);                 
        }


... i świat stał się piękniejszy!

Jak widać w przykładzie, metody statyczne wcale nie muszą znajdować się w klasie statycznej, w naszym przykładzie była to klasa publiczna.

Uwaga: 
Ponieważ nasza klasa Prostokat posiada już statyczną metodę pole(a,b), nie możemy wywołać tej metody z obiektu:

Prostokat p = new Prostokat(2,4);
int pole = p.pole(p.a, p.b);

Od tej pory do metody na obliczenie pola odwołujemy się tak:

            Prostokat p = new Prostokat(2,3);
            int pole = Prostokat.pole(p.a, p.b);


Uwaga #2:
Jeśli cała klasa będzie statyczna, wszystkie jej atrybuty (zmienne) i operatory (funkcje, metody) muszą być statyczne.
Nadal możemy określać poziom dostępu do elementów klasy za pomocą modyfikatorów public i private.

Uwaga #3, #4:
Metody statyczne nie mogą być wirtualne (w końcu jak statyczna to statyczna!)
Klasa statyczna nie może być klasą bazową (

Podsumowanie

Słowo Static wprowadza wiele dobrego, ponieważ dzięki temu nie zawsze musimy tworzyć instancję naszych klas, aby wykonać pewne działania.

Możemy tworzyć uniwersalne klasy, metody lub zmienne, które będą dostępne "od ręki" bez konieczności tworzenia "na chwilę" obiektów. 


wtorek, 22 lipca 2014

O Interfejsach w C# - na chłopski rozum


Programowanie obiektowe przyniosło wiele nowych wynalazków, najbardziej docenionym są oczywiście klasy i obiekty. Zapewne wielu z Was zadaje sobie pytanie:

"Ok, skoro mamy klasy, z których możemy tworzyć obiekty, możemy sobie dziedziczyć itd, to co to są te interfejsy i po co właściwie musiałbym ich używać?"

Postaram się odpowiedzieć na to pytanie.


O Interfejsach w C# słów kilka

Pamiętam jeszcze podczas studiów, jak zdarzało się kiedy studenci interfejsem określali "okienko z przyciskami i polami, nazywanymi fachowo kontrolkami" :) Oczywiście winforms czy WPF jest "interfejsem użytkownika", natomiast służy do czegoś innego niż "interfejs dla programisty" :)

Interfejs jest czymś w rodzaju klasy. Wewnątrz interfejsu znajdują się metody (ale bez ich oprogramowania!) jakie obsługuje.

Niewątpliwie zaletą interfejsów jest fakt, że klasa może dziedziczyć po wielu interfejsach.

Interfejsy implementujemy podobnie jak klasy, zastępując słowo kluczowe class słowem interface.

Jak to bez implementacji metod? W takim razie po co? są od tego metody w klasie...

Jak już wcześniej wspomniałem, interfejs zawiera wyłącznie nazwy metod, których nie implementuje. Sama implementacja metody odbywa się po stronie klasy, która dziedziczy po interfejsie/interfejsach.

Najprościej mówiąc:
Interfejs mówi, CO na pewno ma robić  klasa (jakie czynności powinna realizować, jakie metody udostępniać). 
Metoda mówi JAK daną czynność wykonać.

Interfejs jest swojego rodzaju inspektorem, który mówi programiście: "Hola, nie oprogramowałeś metody w klasie, którą powinna realizować!"

A teraz trochę praktyki.

Tworzymy grę, czyli praktyczne wykorzystanie interfejsów


Załóżmy, że tworzymy prostą grę strategiczną, w której występują:
  1. Ludzie
    1. Żołnierze
    2. Inżynierowie
  2. Budynki
    1. obronne
    2. pozostałe


Żołnierze, pojazdy oraz inżynierowie mogą się poruszać w czterech kierunkach mapy: góra, dół, lewo, prawo.
Przyjmijmy, że Budynki raczej się nie poruszają :) W dodatku część budynków może strzelać (np działka antyrakietowe).


Teraz określimy nasze interfejsy: jeden do poruszania, drugi do strzelania:

        interface IMovement
        {
            void goUP();
            void goDown();
            void goLeft();
            void goRight();
        }

        interface IShoot
        {
            void Shoot(Unit who);
        }




W najprostrzym modelu, możemy podzielić jednostki na ludzi, pojazdy i budynki.
Stwórzmy zatem początkowe: Human, Building, oraz Unit:

        abstract class Unit
        {
            public int Health = 100;
        }

        class Human : Unit
        {
            //...
        }

        class Building : Unit
        {
            //...
        }

do szczęścia przydałoby się podzielić jeszcze ludzi na żołnierzy i inżynierów, oraz rozszerzyć budynki na działka:

        class Soldier : Human
        {
        }

        class Engineer : Human
        {
        }

        class Cannon : Building
        {
        }


jak już ustaliliśmy, ludzie mogą się poruszać. podłączmy zatem interfejs IMovement do klasy Human:

        class Human : Unit, IMovement
        {
        }

Jeśli spróbujemy teraz skompilować nasz kod, otrzymamy informację iż nie zimplementowaliśmy metod do poruszania się naszych jednostek!

Zatem skorygujmy nasz kod

        class Human : Unit, IMovement
        {
            //...
            public void goUP()
            {
                //idź do góry
            }
            public void goDown()
            {
                //idź w dół
            }
            public void goLeft()
            {
                //idź w lewo
            }
            public void goRight()
            {
                //idź w prawo
            }
        }

Teraz kompilacja przejdzie bez problemu.

zmodyfikujmy teraz naszą klasę Cannon:

class Cannon : Building, IShoot
        {
            void Shoot(Unit who)
            {
                //strzelaj do wskazanej jednostki!
            }
        }


i kompilacja... niestety znowu problem. Musimy pamiętać o tym, że metody oparte o interfejs zawsze muszą być publiczne! W końcu jest to funkcjonalność "widoczna" przez programistów, którzy później stworzą obiekt naszej przykładowej klasy Cannon.

poprawmy zatem:

class Cannon : Building, IShoot
        {
            public void Shoot(Unit who)
            {
                //strzelaj do wskazanej jednostki!
            }
        }


i teraz jest dobrze :)


pełny kod:

///////////////////////////////////////////////

abstract class Unit
        {
            public int Health = 100;
        }

        class Human : Unit, IMovement
        {
            //...
            public void goUP()
            {
                //idź do góry  
            }
            public void goDown()
            {
                //idź w dół
            }
            public void goLeft()
            {
                //idź w lewo
            }
            public void goRight()
            {
                //idź w prawo
            }
        }

        class Building : Unit
        {
            //...
        }


        class Soldier : Human, IShoot
        {
            public void Shoot(Unit who)
            {
            }

        }

        class Engineer : Human
        {
        }

        class Cannon : Building, IShoot
        {
            public void Shoot(Unit who)
            {
                //strzelaj do wskazanej jednostki!
            }
        }

        interface IMovement
        {
            void goUP();
            void goDown();
            void goLeft();
            void goRight();
        }

        interface IShoot
        {
            void Shoot( Unit who);
        }

///////////////////////////////////////////////

Teraz zobacz, jak to działa. stwórz obiekty utworzonych klas....

            Soldier soldier1 = new Soldier();
            Building armory = new Building();
            Engineer engineer1 = new Engineer();


... i zobacz, jakie nasze obiekty mogą wykonywać operacje.



Podsumowanie

Interfejsy umożliwiają dokładne określenie funkcji, jakie powinny realizować klasy (jakie powinny posiadać metody).
Jeśli do klasy podłączymy interfejsy, kompilator nie przepuści nas dalej, dopóki nie oprogramujemy naszych klas zdefiniowanymi przez interfejs metodami (które muszą być publiczne).

Bez interfejsów można oczywiście żyć, jednak pozwalają w sposób ładny uporządkować nasz kod. Gdybyśmy na przykład do naszej klasy IShoot dodali metodę ShootAlternative, kompilator nie przepuściłby nas momentu, kiedy wszystkie klasy dziedziczące po IShoot posiadałyby implementację wspomnianej metody.

Oczywiście interfejsy można wykorzystać nie tylko przy tworzeniu gier, ale wszędzie tam, gdzie ważne jest opanowanie tworzonego oprogramowania.

Nie ma sensu stosować interfejsów w przypadku gdy ten pojawi się w najwyżej jednej klasie skromnej aplikacji. Przy większych systemach warto jednak zadać sobie pytanie:

Czy z interfejsami nie będzie bezpieczniej i wygodniej?
 X3me