Obiekty #1Część NR.1Oto mały eksperyment. Postanowiłem stworzyć serię  wpisów, w których będą uczył programowania, równocześnie opowiadając historię. To nie są normalne wpisy objaśniające, jak coś działa krok po kroku. Witam więc w tej emocjonalnej  wędrówce, która być może czegoś ciebie nauczy. 

Kiedyś ktoś mnie zapytał dlaczego lubię programować?

Ja odpowiedziałem, że lubię programować ponieważ kod ma logicznych sens w przeciwieństwie do ludzi i do rzeczywistego świata. Kod mnie rozumie i o nic mnie nie pyta?

Myślenie w ten sposób dawało wiele swobody. Wszystko jest logiczne, więc wszystko jest bezbłędne. Jeśli coś podam do systemu ,to wiem co z niego wyjdzie. Jestem perfekcyjny

Góry 1

Ludzie jednak są istotami emocji i nie mogłem skłamać, że też taki jestem.

Nasze sny i myśli istnieją poza granicą logiki, a później istnieje wspaniała zabawa, gdy próbujemy je wyjaśniać. Są one źródłem prawdziwej poezji naszych emocji. Co jeśli jest ich zbyt dużo? Koszmary też nie mają logicznego sensu, podobnie jak horrory. W historii horroru ofiara zadaje sobie pytanie “dlaczego?”.

Nie ma jednak wtedy wyjaśnienia i nie powinno go być. Ta tajemniczość zostaje w nas najdłużej i pamiętamy ją do końca.

Góry 2

W programowaniu jednak nie ma takiej tajemniczości, przynajmniej od strony kodu. Chociaż z wielkim ubolewaniem myślę o takiej sytuacji, gdy będzie błąd w aplikacji na serwerze produkcyjnym , a ja nie będę znał odpowiedzi dlaczego?

Zapomniałem, że wszystko jest tworem ludzkim. Emocje też biorą wielki udział w tym zawodzie. Co zatem idzie koszmary w nich też się pojawiają.

Emaile. Spotkania. Zarządzanie ludźmi. Błędy interpunkcyjnie w plikach konfiguracyjnych.

Błędy ze scalaniem kodu z serwera produktowego na serwer deweloperski.

Góry 3

Programowanie dla niektórych to marzenie życia, a dla niektórych pułapka zawinięta pomiędzy rzeczywistością stawianych przez klientów wymagań, a koszmarem technologicznym.

Brak wzorców projektowych. Pliki klas po cztery tysiące linijek kodu. Mieszanie logiki, która operuje widokiem z logiką, która wyciąga dane z baz danych lub plików XML. Chaos i niepotrzebna komplikacja kodu. Nazywanie zmiennych imionami postaci ulicy sezamkowej.

Czy fascynacja technologiczna może wystarczyć do przetrwania? Czy programowanie może być fajnie? Czy może stać się pasją życia?

Góry 4

Nie wiem.

Wierzę, że każdy w życiu musi zaimplementować te interfejsy.  Każdy musi znaleźć swoje szczęście i swój plan.

public interface IFindYourHappiness
{
    void FindYourHappiness();
}

public interface IFindYourPurposeThePlan
{
    void FindYourPurposeThePlan();
}

Interfejsy dają gwarancje, że dany obiekt wykona w sobie odpowiednie metody.

image_thumb27

Niestety jako ludzkie istoty ogłupione przez wszystko ,może co najwyżej wyrzucić wyjątek informujący o nie zaimplementowaniu metody.

public class Human : IFindYourHappiness,IFindYourPurposeThePlan
{

    public void FindYourHappiness()
    {
        throw new NotImplementedException();
    }

    public void FindYourPurposeThePlan()
    {
        throw new NotImplementedException();
    }
}

Cześć jestem Alan Wake i jestem programistą.

Prolog : Czym jest obiekt i klasa

Zawsze miałem bujną wyobraźnię, ale ten sen mnie zaniepokoił. Był on dziki, czarny i dziwny nawet dla moich standardów.

Tak. Moja historia zaczęła się od snu.

Samochód 1

Śledząc typowy wzorzec projektowy koszmaru. Było ciemno i była noc. Dziś był dzień wdrożenia naszej aplikacji z serwera testowego na serwer produkcyjny i coś nie działało z nieokreślonych przyczyn.

Samochód 2

Trzeba było naprawić ten błąd przed świtem – więcej szczegółów nie mogłem sobie przypomnieć. Czy moje życie osobiste jest warte tego poświęcenia. Nie wiem. Wiem, że byłem już w samochodzie i chciałem jak najszybciej dojechać do miejsca pracy.

Aby zabić czas i upewnić się, że nie zasnę nad kierownicą. Zacząłem myśleć o programowaniu.

Prowadzę samochód. A czym jest samochód, jest on obiektem.

Jak każdy obiekt został  utworzony na podstawie specyfikacji. A jego specyfikacją jest klasa.

Konstruktor tworzy obiekt samochodu.

public class Car
{
    public Car()
    {
        //tutaj tworze byt samochodu
    }
}

Kolor i model nie reprezentują typu prostego jak liczby, napisu, czy wartości logicznej (prawda/fałsz). Dlatego są one kolejną klasą.

int liczbaCalkowita = 10;
double ulamek = 0.5;
bool wartoscLogiczna = true;
char znak = 'z';
//String jest klasą
string napis = "Napis";

Dlaczego te pola są prywatne. Dlaczego mogę uzyskać do nich dostęp tylko poprzez metody.

W specyfikacji samochodu, czyli w klasie mogę określić jakiego koloru i modelu on będzie. W konstruktorze mogę przesłać te parametry. Nie będzie można ich zmienić, gdyż pola prywatne określające model i kolor są tylko do odczytu.

public class Car
{
    public Car(CarColor carColor, Model model)
    {
        //tutaj tworze byt samochodu
        _carColor = carColor;
        _model = model;
    }

    private readonly CarColor _carColor;
    private readonly Model _model;

    public Model GetCarModel()
    {
        return _model;
    }

    public CarColor GetCarColor()
    {
        return _carColor;
    }
}

public class Model
{}

public class CarColor
{}

Klasę powinno się traktować jak czarną skrzynkę.  Nie wszystko w klasie powinno być dostępne. W przypadku samochodu, gdyby tak było, mógłbym zmienić kolor samochodu i jego modelu w trakcie jego egzystencji. A ja takiego zachowania nie chcę.

Moja czarna skrzynka określająca samochód.

Czarna skrzynka

Spełniam w ten sposób zasadę hermetyzacji. Stosowanie tej zasady pozwala mi na łatwiejsze zarządzanie obiektami i ich różnymi specyfikacjami, czyli klasami. Dlaczego? Z każdego obiektu wiem, co może wyjść i wejść. Inne klasy nie powinny wnikać w wewnętrzną mechanikę (w tym przykładzie samochodu).

Hermetyzacja

Gdyby każda klasa wnikała w wewnętrzny mechanizm każdej klasy zrobiłby się wielki bałagan. Zarządzanie takim kodem spowoduje u nas  wielki ból głowy.

Warto też dodać, że w C# , w przeciwieństwie do Javy, mamy mechanizm właściwości. Nie muszę więc pisać metody GET/SET ustawiającej dane pole prywatne.

Mogę w C# skorzystać z właściwości. Mogę w niej określić jak parametr będzie ustawiany. Mogę też tego nie zrobić. Korzystają z mechanizmu automatycznej właściwości.

Kolor i model samochodu nie powinien być ustawiany. Mogę więc określić, że te pola będą dostępne tylko na poziomie klasy. Dodaję do niej po prostu modyfikator dostępu “private”.

Właściwości są tak naprawdę metodami. Mogą one być wiec w interfejsie. Właściwości skracają  kod. Poniższy zapis robi to samo, co  definicja klasy samochodu powyżej.

public class Car
{
    public Car(CarColor carColor, Model model)
    {
        //tutaj tworze byt samochodu
        CarColor = carColor;
        Model = model;
    }

    public CarColor CarColor { get; private set; }
    public Model Model { get; private set; }
}

Było to proste nieprawdaż.

Myśląc o tych podstawach programowania dodałem sobie odwagi. Jestem mądry. Wiem, co to obiektowość i hermetyzacja. Jestem więc mądry. Mogę sobie poradzić z każdym błędem w aplikacji na serwerze produktowym.

Nie wiedziałem jednak, że byłem w koszmarze.

Tunel

Nie kwestionowałem także logiki koszmaru, że moje miejsce pracy było w pobliżu latarni morskiej. Popełniłem taki sam błąd jak programista tworzący rozwiązanie, bez  konsultacji z głównym  programistą systemu. Trzeba było tutaj zadać dużo pytań. A ja siedziałem cicho.

Jechałem zdecydowanie za szybko. Pośpiech i brak koncentracji ,nie tylko w programowaniu rodzi błędy.

Potrącenie

Zobaczyłem autostopowicza , o dużo za późno.

Nie kwestionowałem logiki koszmaru. Zacząłem się zastanawiać, co tak naprawdę ja reprezentuję  w tym rzeczywistym świecie oraz ten autostopowicz, którego potrąciłem.

Każdy z nas jest osobą.

public abstract class Person
{
    protected Person(int lifePoints)
    {
        _lifePoints = lifePoints;
    }

    private readonly int _lifePoints;

    public bool IsALife()
    {
        return _lifePoints <= 0;
    }
}

Osoba mówiąc po angielsku “Person” nie reprezentuje niczego konkretnego. Określa ona tylko zbiór pewnych obiektów, które powinny mieć cechy wspólne.

Nie powinienem móc utworzyć bytu czegoś, co reprezentuje tylko kategorię. Kategorię mogę ustalić na dwa sposoby. Mogę zrobić to poprzez interfejs albo poprzez klasę abstrakcyjną.

Interfejs gwarantuje mi, że dana klasa ma pewne metody. Jest ona kontraktem. Nie mogę utworzyć bytu interfejsu, gdyż on nie definiuje jak coś ma działać, tylko co ma działać w danej klasie.

public interface IEat
{
    void Eat();
}

public class Human : IEat
{
    public void Eat()
    {
        //Jem na swój ludzki sposób
    }
}

public class Cat : IEat
{
    public void Eat()
    {
        //Jem jak kot
    }
}

Interfejs jednak tutaj nie wchodzi w grę. Potrzebuje czegoś, co pozwoli mi określić wspólne zachowanie dla wszystkich klas z tej kategorii. Normalnie wystarczyłoby mi normalne dziedziczenie klasa po klasie.

Przykładowo nie przeszkadzałoby mi to, że mogę utworzyć byt i Pizzy, i Pizzy z szynką. Każda by pachniała tak samo używając swojej metody SendBeautifulSmell() .

public class Pizza
{
    public void SendBeautifulSmell()
    {}
}

public class PizzaWithHam : Pizza
{}

Osoba jednak reprezentuje kategorię. Sama w sobie nie powinna  tworzyć bytu. Chciałbym więc mieć coś, co działa jak interfejs i klasa równocześnie.

Klasa abstrakcyjna daje mi takie zachowanie. Nie mogę utworzyć instancji klasy abstrakcyjnej.  Mogę w niej stworzyć metody, które będą działały tak samo dla każdej osoby. Mogę też utworzyć metody abstrakcyjne, które będą musiał mieć swoją definicję w klasie dziedziczącej po niej.

Klasa abstrakcyjna

Jestem więc bohater tej historii.

Ja i potrącony autostopowicz mamy jednak coś wspólnego, obaj jesteśmy osobami. Każdy z nas ma punkty życia. Są one uzupełniane w konstruktorze rodzica. Do bazowego konstruktora klasy abstrakcyjnej odwołuję się poprzez słowo kluczowe base.

public class Hero : Person
{
    public Hero(int lifePoints)
        : base(lifePoints)
    {

    }
}

public class Hitchhiker : Person
{
    public Hitchhiker(int lifePoints)
        : base(lifePoints)
    {

    }
}

Siedząc jeszcze w samochodzie potrzebowałem  więcej odwagi, by wyjść. Zapomnieć na chwile, że potrąciłem pieszego.

Powoli się uspokoiłem. Nie mogłem jednak otworzyć drzwi.

PIeszy

W  myślach powędrowałem gdzieś indziej…zauważyłem, że ciało leżące na ziemi na pewno jest płci męskiej.

Jak już każdy z nas jest osobą. To każdy z nas ma jakąś płeć. Jak jednak powinniśmy ją ustalić?

public abstract class Person
{
    protected Person(int lifePoints,string gender)
    {
        _lifePoints = lifePoints;
        _gender = gender;
    }

    private readonly int _lifePoints;
    private string _gender;

    public bool IsALife()
    {
        return _lifePoints <= 0;
    }
}

Może pod postacią napisu określającego typ “string”.

Niezbyt dobry pomysł. Gdyż istnieje wiele określeń dla płci męskiej.

Person person = new Hero(100,"Facet");
Person secondPerson = new Hitchhiker(100,"Menżczyzna");

Co gorsza sam napis może tworzyć wiele błędów. Co jeśli ktoś nie wie jak napisać słowo “Mężczyzna”.  Wujek GOOGLE nie zawsze może pomóc. W swojej aplikacji ciężko też o taki algorytm wychwytujący te nieścisłości w jakości danych, w postaci napisów.

Google tłumaczenie

Zmora dla każdego, kto zarządza system bazodanowym, który przetrzymuje wartości wpisywane przez użytkowników. A sami użytkownicy określają ten sam przedmiot 30 różnymi wyrazami ,dodając w tym ich błędy językowe.

Na ratunek jednak przychodzi typ wyliczeniowy, który potrafi przetrzymywać tylko określone wartości.

Każda wartość określona jest po przecinku.

public enum Gender
{
    Men,
    Woman
}

Każda wartość jest numerowana. Pierwsza wartość ma wartość liczbową zero, druga ma wartość liczbową jeden.

Jednak jeśli zaczniemy je numerować po swojemu, warto wtedy ponumerować wszystkie wartości.

Wartość dla męskiej płci domyślnie równa się zero bo jest pierwsza. Ustawiłem, że wartość płci żeńskiej też ma liczbę zero.

Enum

Później w aplikacji mogę się dziwić, że  dla wartości reprezentującej kobietę mam wartość faceta. Prawdziwy  Gender.

public enum Gender
{
    Men = 0,
    Woman = 1
}

Wykorzystajmy nasz typ wyliczeniowy w klasie abstrakcyjnej Person.  Zmieniając konstruktor klasy abstrakcyjnej zmienić muszę także  konstruktory klas dziedziczącej po niej.

public abstract class Person
{
    protected Person(int lifePoints,Gender gender)
    {
        _lifePoints = lifePoints;
        _gender = gender;
    }

    private readonly int _lifePoints;
    private Gender _gender;

    public bool IsALife()
    {
        return _lifePoints > 0;
    }
}

public class Hero : Person
{
    public Hero(int lifePoints,Gender gender)
        : base(lifePoints,gender)
    {}
}

public class Hitchhiker : Person
{
    public Hitchhiker(int lifePoints, Gender gender)
        : base(lifePoints, gender)
    {}
}

Każda osoba  jak widzimy ma także metodę sprawdzającą, czy jest ona obecnie żywa. Jeśli punkty życia spadły do zera ,to uznajemy, że dana osoba umarła.

Wysiadłem z samochodu. Nie wiedziałem ile czasu już minęło.

Skończyły mi się pomysły na wywody programistyczne. Nie mogłem od tego uciec. Musiałem sprawdzić, czy on żyje?

Sprawdzenie życia

Postanowiłem do swojej definicji klasy dodać metodę sprawdzającej życie innej osoby.

public class Hero : Person
{
    public Hero(int lifePoints,Gender gender)
        : base(lifePoints,gender)
    {}

    public bool CheckOthersPersonLife(Person person)
    {
        return person.IsALife();
    }
}

Pomyślałem sobie, to zły pomysł. Z poziomu klasy osoby “Person” nie mogę wywołać tej metody. Mogę tylko wywołać metodę sprawdzającą moje własne życie.

Użycie klasy

Nie widziałem powodu, by nie umieścić tej metody do klasy abstrakcyjnej Person . Każda osoba powinna móc sprawdzić życie innej osoby.

public abstract class Person
{
    protected Person(int lifePoints,Gender gender)
    {
        _lifePoints = lifePoints;
        _gender = gender;
    }

    private readonly int _lifePoints;
    private Gender _gender;

    public bool IsALife()
    {
        return _lifePoints > 0;
    }

    public bool CheckOthersPersonLife(Person person)
    {
        return person.IsALife();
    }
}

Tak więc, gdy wszystkie parametry programu zostały określone pozostało mi sprawdzić, czy autostopowicz żyje.

Jeśli będzie on żył to program przejdzie do linki kodu pod wyrażeniu “if”. Wyświetli  napis “Żyje”.

Jeśli tak nie jest, to wykona mi się linijka kodu pod wyrażeniem “else”. Wyświetli mi się napis “Nie żyje”.

Person hero = new Hero(100,Gender.Men);
Person hitchhiker = new Hitchhiker(0, Gender.Men);

bool alife = hero.CheckOthersPersonLife(hitchhiker);

if (alife)
    Console.WriteLine("Żyje");
else
    Console.WriteLine("Nie żyje");

Zbliżałem się do autostopowicza. Dotknąłem go lekko dłonią.

Alan Wake Sprawdzenie życia

Nie żył.

Byłem przekonany, że wrzucą mnie do więzienia i już nigdy nie zobaczę Alicji.

Alan Wake Sprawdzenie życia 2

Nagle światły w samochodzie zgasły. Robiąc wielki dźwięk “THUMB!!!”. Odwróciłem się w kierunku samochodu.

Alan Wake Sprawdzenie życia 3

Nie mogłem uwierzyć własnym oczom. Autostopowicz zaczął się trząść.

Alan Wake Sprawdzenie życia 4

Ze strachu szybko przywróciłem się do pozycji stojącej.

Alan Wake Sprawdzenie życia 4

Nagle jego ciało zniknęło.