sobota, 10 września 2011

4 podstawowe założenia programowania obiektowego.

Wielu początkujących programistów tworzy kod zupełnie ignorując niektóre z nich; inni stosują je, nie wiedząc tak naprawdę po co. Kluczem do produkowania kodu zdatnego do dalszego przetwarzania jest jednak ich głębsze zrozumienie.


Czy można pisać programy zupełnie ignorując te 4 podstawowe zasady? Można, program skompiluje się, zadziała, może nawet wykona się szybciej niż przy uwzględnianiu tych zalożeń. Problem, jak to zwykle w programowaniu bywa, pojawia się później, gdy dojdą nowe założenia programu. Wówczas modyfikacja kodu niespełniającego ww. założeń często trwa więcej, niż napisanie całego kodu od początku.


Zasady programowania obiektowego (w kolejności alfabetycznej) :


- Abstrakcja
- Dziedziczenie
- Hermetyzacja
- Polimorfizm


Początkowo rozpatrzmy dziedziczenie, abstrakcję oraz polimorfizm, gdyż są one ze sobą ściśle powiązane. Hermetyzację zostawimy sobie na koniec.


Rozpatrywane przykłady będę pisać w języku C++.






Abstrakcja, dziedziczenie i polimorfizm:
Początkowo zdefniujmy pojęcia:
Abstrakcja to uogólnienie pewnych działań, aby umożliwić lepszą implementację zbliżonych założeń.
Dziedziczenie to relacja między dwoma klasami. Może to być relacja jest-czymś, ma-coś.
Polimorfizm jest to przyjmowanie przez obiekt różnych postaci.


W zadaniu będziemy mieli do czynienia z polimorficznym dziedziczeniem publicznym, abstrakcyjną klasą bazową oraz polimorficznym rozpatrywaniem argumentu funkcji.


Rozpatrzmy przykład:


Dane są 2 klasy instrumentów: Piano oraz Guitar. 
class Piano
{
//(...)
  public:
  void NacisnijKlawisz();
//(...)
};


class Guitar
{
//(...)
  public:
  void SzarpnijStrune();
//(...)


};


Napisz zewnętrzną funkcję, której uruchomienie spowoduje wydanie dźwięku instrumentu. Do funkcji przekazywane będą różne instrumenty.

Można podejść do rozwiązania tego problemu przeciążoną funkcję, jedną dla gitary, drugą dla pianina:


void WydajDzwiek(const Piano &p)
{
  p.NacisnijKlawisz();
}


void WydajDzwiek(const Guitar &g)
{
  g.SzarpnijStrune();
}


Oczywiście program będzie działał prawidłowo, programista będzie zadowolony. Wtedy jednak okaże się, że oprócz rzeczonego pianina i gitary, zmuszony będzie dodać obsługę skrzypiec, klarnetu i kontrabasu. W tym momencie dla prostej funkcji wydania dźwięku, trzeba tworzyć 5 różnych implementacji dla jednej tylko przeciążonej funkcji.
NIE O TO CHODZI W PROGRAMOWANIU OBIEKTOWYM.


W jaki sposób zatem rozwiązać problem zgodnie z duchem programowania obiektowego i przygotować go na dalszy rozwój? Tu wchodzi właśnie abstrakcja. 


Zadeklarujmy czysto abstrakcyjną  klasę bazową:


class Instrument
{
//(...)
  protected:
  virtual void WydajDzwiek()=0; //funkcja czysto wirtualna
//(...)
};


Teraz wystarczy dodać dziedziczenie publiczne każdego instrumentu z klasy bazowej oraz przesłonić funkcję mającą wydać dźwięk:


class Piano : public Instrument
{
//(...)
  public:
  virtual void WydajDzwiek()
    {
      NacisnijKlawisz();
    }


  void NacisnijKlawisz();
  
//(...)
};

class Guitar : public Instrument
{
//(...)
  public:
  virtual void WydajDzwiek()
    {
      SzarpnijStrune();
    }


  void SzarpnijStrune();
  
//(...)
};


Teraz już implementacja naszej funkcji będzie tylko jedna:


void WydajDzwiek(Instrument *Instr)
{
  Instr->WydajDzwiek();
}


UWAGA! Należy pamiętać, że nie można tworzyć instancji klasy abstrakcyjnej, wobec tego należy wykorzystać wskaźniki.


Nawet po dodaniu 15 dodatkowych instrumentów, wystarczy jedynie w każdym przesłonić funkcję wirtualną klasy bazowej, a funkcją, którą mieliśmy za zadanie zaprojektować, nie będzie wymagała żadnych zmian.


OSTATECZNIE:
Przykładowy program rozwiazujacy poczatkowy problem: 




#include <iostream>


using std::cout;
using std::endl;


//Dla przejrzystości kodu nie deklaruję jawnych konstruktorów ani destruktorów


class Instrument
{
  public:
  
  virtual void WydajDzwiek() =0; //funkcja czysto wirtualna


};


class Piano : public Instrument
{
  public:
  virtual void WydajDzwiek()
    {
      NacisnijKlawisz();
    }


  void NacisnijKlawisz() const {cout << "Piano\n";}
  
};


class Guitar : public Instrument
{
  public:
  virtual void WydajDzwiek()
    {
      SzarpnijStrune();
    }
  void SzarpnijStrune() const {cout << "Gitara\n";}
};


void WydajDzwiek(Instrument *Instr)
{
  Instr->WydajDzwiek();
}


int main()
{
  Instrument *INSTRUMENTY[4];  //tablica z 4 adresami instrumentów
  Piano Steinway;
  Piano Yamaha;
  Guitar Gibson;
  Guitar Fender;
  
  INSTRUMENTY[0]=&Steinway;
  INSTRUMENTY[1]=&Gibson;
  INSTRUMENTY[2]=&Yamaha;
  INSTRUMENTY[3]=&Fender;   


  
  for(int i=0;i<4;i++) 
  {   
    WydajDzwiek(INSTRUMENTY[i]);
  }
  
  cout << endl;  
  system("PAUSE");
  return 0;
}






Hermetyzacja:

Najbardziej chyba zaniedbywany przez niedoświadczonych programistów element. 
Dodatkowo wiele osób stosujących hermetyzację robi to, nie potrafiąc wyjaśnić, jaki jest tego cel.
Idea hermetyzacja mówi, że klasa powinna ukrywać swoje dane, a ujawniać jedynie część metod. Dane są prywatne i żaden obiekt nie może bezpośrednio zmienić danych innego obiektu.

Rozpatrzmy przykład:

Zaprogramuj klasę, która przechowuje jedną liczbę i pozwala ją modyfikować oraz odczytać.

UWAGA! Dla prostoty w rozwiązaniach pominiemy konstruktory i destruktory, zakładamy, że zmienne w klasie są na starcie równe 0.

Rozwiążmy jednak ten problem nie stosując zasady hermetyzacji:

class PrzykladowaKlasa
{
  public:
  int Liczba;
};

Oraz przykładowy program:

#include <iostream>

using std::cout;
using std::endl;

int main()
{
  PrzykladowaKlasa X;

  X.Liczba=5;
  cout << X.Liczba;
  X.Liczba=8; 

  return 0;
}


Program działa prawidłowo. Jaki jest wobec tego sens całej tej hermetyzacji? 
Otóż załóżmy, że potem dostajemy kolejne zadanie:

Klasa dodatkowo powinna pamiętać, ile razy liczba została odczytana i ile razy zapisana.

Modyfikujemy zatem kod:


#include <iostream>

using std::cout;
using std::endl;

class PrzykladowaKlasa
{
  public:
  int Liczba;
  int IleOdczytow;
  int IleZapisow;
};

int main()
{
  PrzykladowaKlasa X;
    

  X.Liczba=5;
  X.Zapisow++;
  
  cout << X.Liczba;
  X.IleOdczytow++;  
  
  X.Liczba=8; 
  X.Zapisow++;

  return 0;
}


Już teraz przy każdym obcowaniu z klasą musimy używać dwóch instrukcji. A co, jeżeli dostaniemy dodatkowo zadanie:

Co 10 odczytów liczby, powinna ona się zwiększyć o 4 (zwiększenie jest uwzględnione przy ilości zapisów).

I co teraz? Przy każdym odczycie należałoby pisać instrukcje warunkowe, przez co każde odwołanie do obiektu powodowałoby lawinę kodu.
TAK NIE WOLNO PROGRAMOWAĆ!

Rozwiążmy zatem ten przykład tak, jak powinno to być zrobione zgodnie z założeniami hermetyzacji:

class PrzykladowaKlasa
{

  private:
  int Liczba;
  int IleOdczytow;
  int IleZapisow;  
  
  public:
  int Odczytaj();
  int Zmien(int Val); 
};

I tyle.
W tej chwili klasa jest już zupełnie gotowa do pracy, a implementacja w niej kolejnych założeń jest kwestią jednokrotnego wpisania kilku linijek kodu w ciele klasy. 

OSTATECZNIE: prawidłowa, hermetyczna klasa spełniająca ostatnie zadanie:

class PrzykladowaKlasa
{
  private:
  int Liczba;
  int IleOdczytow;
  int IleZapisow;  

  public:
  int Odczytaj()
    {
      ++IleOdczytow;
      if(IleOdczytow % 10 == 0)
        {
          Liczba+=4;
          ++IleZapisow;
        }
      return Liczba;
    }
  int Zmien(int Val)
    {
      Liczba=Val;
      ++IleZapisow;
    } 
};






PODSUMUJMY:

4 główne zasady programowania obiektowego powinny być zawsze przestrzegane. Program napisany bez ich uwzględnienia staje się toporny i trudny w modyfikacji.

Założenie tych 4 zasad można skwitować jednym zdaniem:

MODYFIKACJA INTERFEJSU KLASY NIE POWINNA POCIĄGAĆ ZA SOBĄ KONIECZNOŚCI MODYFIKACJI KODU POZA KLASĄ.





6 komentarzy:

  1. Ten komentarz został usunięty przez administratora bloga.

    OdpowiedzUsuń
  2. Warto co jakiś czas czytać takie artykuły już po nauce podstaw. Bo bardzo ważne jest nabranie odpowiednich nawyków możliwie wcześniej.

    OdpowiedzUsuń
  3. Można też przemyśleć rozpoczęcie studiów informatycznych o specjalizacji programowania http://www.wseiz.pl/pl/studia/wydzial-zarzadzania/informatyka/22-dla-kandytata/oferta-studiow-i-i-ii-stopnia/wydzial-zarzadzania/1752-programowanie-aplikacji . To bardzo przyszłościowy kierunek, tym bardziej że technologia się bardzo szybko rozwija i cały czas na rynku pracy poszukiwani są wykwalifikowani specjaliści

    OdpowiedzUsuń
  4. Muszę przyznać, że jeśli chodzi o mnie to ja niestety nie rozumiem programowania, ale muszę na pewno sporo poczytać. Również jak czytałem niedawno na stronie https://gst24.pl/pl/664_materialy_partnera/2852_nie-rob-strony-internetowej-samodzielnie---zostaw-to-zadanie-specjalistom.html to w sumie samo stworzenie strony internetowej na pewno najlepiej zlecić specjalistom, którzy się na tym znają.

    OdpowiedzUsuń
  5. Baccarat | New casino games | FeBCasino
    A beginner's guide to Baccarat, with a list of bonus 1xbet korean codes, promotions and how to play, and a beginner's guide to winning kadangpintar at online casinos. 바카라사이트

    OdpowiedzUsuń