W moim pierwszym artykule zachęcałem Cię do tego, żebyś zaczął zmieniać świat. Całym sobą podtrzymuje to, zanim jednak stworzysz aplikacje sterującą rynkami finansowymi na całym świecie pozwól, że przejdziemy razem przez pewne (również ciekawe!) podstawy;) zaczynamy tym samym nasz pierwszy, dwuartykułowy cykl z dziedziny Kreatywnego IT, zatytułowany – a jakże by inaczej – baza. Zakładam tutaj, że umiesz podstawy programowania strukturalnego w c++ (Jeśli jest inaczej, w materiałach znajdziesz doskonały olbrzymi tutorial). Cykl bazowy wprowadzi Cię w świat programowania obiektowego – pokażę Ci czym to jest oraz „z czym to się je”. Razem zrobimy również prosty program który z pewnością rozwieje wszelkie wątpliwości;) Do dzieła!
OOP – podstawy
Zacznijmy od podstawowej rzeczy – co to w ogóle jest OOP i po co je stosujemy? Ujmując rzecz nieformalnie, OOP (Object Oriented Programming – programowanie zorientowane obiektowo) to pewna filozofia tworzenia oprogramowania, która – w przeciwieństwie do strukturalnego – bazuje na klasach i ich obiektach. Już się tłumaczę z tego dość zawiłego zwrotu. Załóżmy ze chcemy napisać jakiegoś RPGa, dla ułatwienia weźmy lubianego powszechnie Wiedźmina, który w końcu jest naszą narodową dumą;) Cała gra może oczywiście zostać napisana w jednym pliku, w funkcji main z milionem zmiennych na początku… Nieee, oczywiście że nie może! Jak mniemam domyślasz się, ze nie dałoby się tego zrobić ze względu na monstrualnych rozmiarów bałagan jaki by powstał. Dodatkowo – przy takiej złożoności gry jaka ma Wiedźmin – nie sposób byłoby zaprojektować od strony inżynierskiej takiego kodu. Na szczęście programiści są zaradni i stworzyli… Klasy! Klasy to pewne typy struktur danych, które programista tworzy sam. Pozwala to wejść na zupełnie inny, dużo bardziej naturalny poziom. Każda klasa składa się z pól (zmiennych) oraz metod (funkcji).
Wróćmy do przykładu Wiedźmina – nie musimy już posługiwać się milionem nic nie mówiących zmiennych. Możemy stworzyć klasę ‚postać’, która będzie miała pola: życie, wytrzymałość, szybkość, imię oraz bron. Metody jakie stworzylibyśmy dla naszej klasy to: Biegnij() oraz Zaatakuj(). Wspaniale! Zauważyłeś pewnie jednak drobną nieścisłość. Przypatrzmy się typom pól: int, float, float, string i…? No właśnie, czym miałaby być bron? Stringiem który opisuje jej nazwę? A może floatem który opisuje jej atak? Na szczęście żyjemy w świecie programowania obiektowego i nie musimy się ograniczać;) Stwórzmy zatem klasę… Bron! Sadze ze tutaj sprawa jest bardzo prosta, klasa będzie zawierać jedynie dwa pola: nazwa (string) oraz atak (float).
Wszystko bardzo fajnie, ale ‚postać’ to dosyć ogólne stwierdzenie. Wiemy przecież, że w świecie Andrzeja Sapkowskiego jest bardzo dużo rodzajów postaci i ze różnią się one od siebie dosyć znacząco. Możemy zatem uszczegółowić. W tym celu skorzystamy z pewnej relacji miedzy klasami, która nazwana została ‚dziedziczeniem’. To bardzo proste – klasa pochodna(potomek) dziedziczy wszystkie pola i metody po klasie bazowej (przodek). W tym przypadku możemy stworzyć kilka klas pochodnych: krasnolud, elf, człowiek. Każda z tych klas automatycznie będzie miała życie, wytrzymałość, szybkość oraz bron. Będzie również miała metody Biegnij() oraz Zaatakuj(). Możemy jednak każdej z nich dodać cos specyficznego dla danej rasy. Np. krasnoludowi możemy dać dodatkowa metodę RozpocznijAwanture() a elfowi dodać pole kołczan. Jakie jeszcze inne klasy i związki mogłyby wystąpić w grze autorstwa CD Projekt Red? Wymieńmy pokrótce kilka: zapewne może być bron po której dziedziczy miecz, łuk oraz topór. Potwór po którym dziedziczą utopiec, przeraza, kikimora oraz troll. Na końcu możemy oczywiście stworzyć ‚Eliksir’, który zawiera w sobie inne klasy – składniki. Gdy mamy już zbudowaną strukturę naszego programu (Gry tez są rodzajem aplikacji. W dodatku jednym z fajniejszych;)), możemy przystąpić do tworzenia obiektów. Jeśli pomyślisz o klasie jako o typie zmiennych, to obiekt jest właśnie zmienną (z resztą nie trzeba traktować tego jak metaforę. Tak naprawdę jest!). Możemy więc stworzyć całą armię robiąc 1000 elementową tablicę krasnoludów. Możemy stworzyć kilka obiektów klasy ‚Krasnolud’ oraz kilka klasy ‚Człowiek’ i umieścić ich w obiekcie klasy ‚Karczma’, który to będzie elementem obiektu klasy ‚Miasto’. Prawda ze programowanie zaczyna wyglądać bardzo naturalnie, żeby nie powiedzieć „przyjaźnie”? Jestem przekonany, że w mig przyswoiłeś sobie wszystko co napisałem na górze. Jest jednak jeszcze jedna rzecz, o której koniecznie musisz wiedzieć.
Ta rzecz to hermetyczność. Wyobraźmy sobie klasę człowiek (ale w życiu realnym, nie w grze). Może ona mieć takie pola jak: wątroba, poziom krwi, serce, nerki. Nie chcielibyśmy żeby każdy mógł nam majstrować przy nerkach prawda? Właśnie dlatego w klasach zarówno pola jak i metody maja jeden z trzech modyfikatorów dostępu. Public wskazuje, że element jest dostępny publicznie i ma do niego dostęp każdy. Private – dostęp jedynie dla metod wewnątrz klasy. Protected – podobnie jak private, jednak jest dziedziczone przez klasy potomne. No dobrze, ale pewnie chciałbyś w razie potrzeby, aby do poziomu krwi miał dostęp lekarz? Od tego są specjalne metody zwane geterami oraz seterami. Getery (lub krócej – gety) wyciągają wartość danego pola zaś setery (sety) nadają określonemu polu wartość. Daje nam to bardzo ważną możliwość kontroli tego co wpływa do naszych pól. Tak więc możemy ustawić ogranicznik w seterze przeznaczonym dla poziomu krwi i nadać granice – nie można zmienić stanu pola jeśli będzie miało ono mieć w efekcie mniej niż 2.5 oraz nie wolno jeśli mielibyśmy mieć więcej niż 6 litrow. Możemy nie stworzyć getera lub setera, wtedy ograniczany możliwości tego kto chciałby majstrować później przy obiekcie. Dla przykładu – pole może być stanem konta bankowego w danym momencie. Nie chcemy raczej żeby ktoś poza nami miał do niego dostęp, nie stworzymy więc geta. Nie damy tym samym żadnej możliwości zdobycia wiedzy o tym ile pieniążków zgromadziliśmy. Znakomicie! Poznałeś już teoretyczne podstawy programowania obiektowego, czas zabrać się za aspekt praktyczny.
Tworzymy aplikację – baza sklepu
Stworzymy razem prostą aplikację do przechowywania danych o produktach oraz prostych operacji na nich. Do dzieła! Na początku wypiszmy funkcjonalności jakie będzie posiadał nasz program – nazwijmy go sklepem.
- Przeglądanie obecnego zbioru produktów
- Dodawanie nowych produktów do zbioru
- Dodanie konkretnych produktów do naszego prywatnego koszyka
- Wyświetlenie naszego koszyka
Po tym krótkim wstępie projektowym pobawmy się w inżynierię oprogramowania. Zastanówmy się jakich klas będziemy potrzebować do Sklepu. Mimo mojej miłości do wszystkiego co polskie, w tym oczywiście do języka – kod budować będę w języku angielskim i Tobie też to zalecam;)
- Product- oczywiście najbardziej podstawowa klasa.
Pola:
- int id
- string name
- Float price
Metody:
- GetId()
- GetName()
- SetPrice()
- SetId()
- GetPrice()
- WriteProduct()
- Base – baza produktów. Wykorzystamy ją dwukrotnie. Najpierw jako ogólny zbiór, a później również jako nas osobisty koszyk.
Pola:
- Product products[100] – 100 elementowa tablica produktów
- String name
Metody:
- AddProduct()
- RemoveProduct()
- GetName()
- SetName()
- WriteProducts()
- CalculateProducts()
Zatem zabierzmy się za implementację! Używam do tego celu środowiska Visual Studio 2013 i Tobie też to zalecam. Po utworzeniu projektu (który nazwiemy „ShopBase”) oraz pliku „main.cpp” z funkcją main() zabierzmy się za utworzenie pierwszej klasy! Klikamy „Project->Add Class”, a następnie „Add”. W pole „Class Name” wpisujemy „Product” i klikamy „Finish”. Brawo! Twoja pierwsza klasa została właśnie stworzona! Jak widzisz środowisko wygenerowało Ci dwa pliki: jedno z rozszerzeniem „.h” i jedno z rozszerzeniem „.cpp”. Pierwszy z nich to tzw. header – plik nagłówkowy, w którym będziemy jedynie definiować pola i metody, w drugim zaś te same pola i metody będziemy implementować. Dopisz odpowiednie miejsca, aby cała klasa wyglądała następująco:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 |
Product.h #pragma once #include <iostream> #include <string> using namespace std; class Product { private: int id; string name; float price; public: Product(int _id, string _name, float _price); Product(); int GetId(); string GetName(); float GetPrice(); void SetPrice(float _price); void SetId(int _id); void WriteProduct(); ~Product(); }; Product.cpp #include "Product.h" Product::Product(int _id, string _name, float _price) { id = _id; name = _name; price = _price; } Product::Product() { id = -1; name = "empty"; price = 0; } float Product::GetPrice() { return price; } void Product::SetPrice(float _price) { price = _price; } void Product::WriteProduct() { cout << "Produkt id: " << id << "\n nazwa: "<< name << " \n cena: " << price << "\n"; } Product::~Product() { } |
Jak widzisz zaimplementowaliśmy wszystkie pola oraz metody które zaplanowaliśmy. Ale zaraz, co to za dziwne metody na początku i na końcu?! Już tłumaczę. Gdy tworzymy klasę automatycznie tworzą nam się: konstruktor oraz destruktor. Pierwszy z nich to metoda, która wywołana będzie zawsze podczas tworzenia obiektu, zaś destruktor – tak, zgadłeś! Podczas usuwania 😉 Mamy jednak dość specyficzną konstrukcję. Pierwszy z konstruktorów jako argument przyjmuje id, name oraz string – czyli nasze pola prywatne, które zaraz też wypełniamy. Gdy jednak użytkownik nie poda żadnych argumentów stosujemy konstruktor domyślny – w naszym przypadku tworzymy bardzo specyficzny obiekt o „nierealnych danych” takich jak -1 jako id. Jeśli któryś z produktów będzie miał to id będziemy go traktować jako obiekt który nie istnieje i dopiero jest „do stworzenia”. Reszta metod jak mniemam nie wymaga komentarza (Gety i Sety pisze się podobnie, więc zostawiłem jedynie po jednym dla przykładu), jednak gdybyś miał z którąkolwiek z nich problem nie wahaj się i śmiało pisz na mojego maila 😉
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
Base.h #pragma once #include <iostream> #include "Product.h" using namespace std; class Base { private: int lastId; Product products[100]; string name; public: Base(); void AddProduct(Product _product); void RemoveProduct(int _id); string GetName(); void SetName(string _name); void WriteProducts(); float CalculateProducts(); Product FindProduct(int _id); ~Base(); }; |
Sądzę, że większość jest jasna. Jestem jednak winien komentarz w jednym z moich ruchów. Gdy spojrzysz na drugą linijkę pól prywatnych zobaczysz tablicę 100 elementową produktów. Musisz wiedzieć, że zastosowałem takie rozwiązanie TYLKO ze względu na łatwość implementacji. Trzeba jasno powiedzieć, że jest ono brzydkie i w następnym artykule przejdziemy do bardziej eleganckich i nowoczesnych rozwiązań!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 |
Base.cpp #include "Base.h" Base::Base() { lastId = 0; } void Base::AddProduct(Product _product) { for (int i = 0; i < 100; i++) { if (products[i].GetId() == -1) { lastId++; products[i] = _product; products[i].SetId(lastId); break; } } } void Base::RemoveProduct(int _id) { for (int i = 0; i < 100; i++) { if (products[i].GetId() == _id) { products[i] = Product(); break; } } } void Base::WriteProducts() { cout << "produkty bazy " << name << ":" << endl; for (int i = 0; i < 100; i++) { if (products[i].GetId() != -1) { products[i].WriteProduct(); } } } float Base::CalculateProducts() { float price = 0; for (int i = 0; i < 100; i++) { price += products[i].GetPrice(); } return price; } Product Base::FindProduct(int _id) { Product tmpProd = Product(); for (int i = 0; i < 100; i++) { if (products[i].GetId() == _id) { tmpProd = products[i]; } } return tmpProd; } |
Z powodu oszczędności miejsca usunąłem stąd get’y i set’y oraz destruktor. W konstruktorze ustawiamy „lastId” na 0 – będzie to nasz licznik dzięki któremu będziemy nadawali kolejne id. W AddProduct() przebiegamy po tablicy produktów i jeśli jest „wolne” miejsce (id == -1) to wstawiamy tam nasz produkt z id o 1 większym od ostatniego oddanego produktu. W RemoveProduct() wykorzystujemy konstruktor domyślny Product i wybrany rekord tablicy produktów czynimy „pustym”. WriteProducts() przechodzi tradycyjnie po tablicy i wypisuje kolejne elementy. Finalnie FindProduct() – wyszukujemy w tablicy produkt o podanym id i zwracamy go.
Znakomicie! Udało nam się zrobić to co najważniejsze – klasy. Teraz należy napisać tylko interfejs do nich. I tu czeka na Ciebie wyzwanie – ja wstawię „brzydki” bez komentarza, a Ty stwórz jego lepszą wersję i wyślij do mnie. Oczywiście najlepiej, gdyby była to wersja obiektowa!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 |
Main.cpp – funkcja main() void main() { bool condition = true; Base ShopBase = Base(); Base Basket = Base(); while (condition) { cout << "Witaj w naszym sklepie! Wybierz co chcesz zrobic:" << endl; cout << "1 - Wyswietlic wszystkie produkty w bazie" << endl; cout << "2 - Dodac produkt do bazy" << endl; cout << "3 - Wyswietlic twoj koszyk" << endl; cout << "4 - Dodac produkt do koszyka" << endl; cout << "5 - usunac produkt z koszyka" << endl; cout << "6 - Obliczyc wartosc koszyka" << endl; int helpVar = 0; cin >> helpVar; switch (helpVar) { case 1: { ShopBase.WriteProducts(); cout << endl; break; } case 2: { string name; float price; cout << "Wpisz nazwe: "; cin >> name; cout << "Wpisz cene: "; cin >> price; Product tmpProduct = Product(-1, name, price); ShopBase.AddProduct(tmpProduct); break; } case 3: { Basket.WriteProducts(); cout << endl; break; } case 4: { int tmpId; cout << "Podaj id: "; cin >> tmpId; Product tmpProd = ShopBase.FindProduct(tmpId); Basket.AddProduct(tmpProd); break; } case 5: { int tmpId; cout << "Podaj id: "; cin >> tmpId; Basket.RemoveProduct(tmpId); break; } case 6: { float tmpPrice = Basket.CalculateProducts(); cout << "Wartosc koszyka to: " << tmpPrice << endl; break; } default: cout << "Wybrales zla opcje!" << endl; break; } } _getch(); } |
Co dalej?
Brawo! Stworzyłeś swój pierwszy program do obsługi sklepowej bazy produktów 😉 Przy tej okazji dowiedziałeś się czym są klasy, metody, pola, hermetyzacja, konstruktory, destruktory oraz najważniejsze – po co my to wszystko stosujemy? Rozumiem oczywiście, że to dużo nowych informacji i jeszcze nie wszystko musi być jasne. Spokojnie! Jest czas, tylko heros zrozumie zawsze za pierwszym razem. Jeśli masz jakiekolwiek pytania pisz śmiało na mojego maila. Zapraszam do materiałów, gdzie znajdziesz również pełny projekt do pobrania. Zdaję sobie sprawę, że niektóre rozwiązania, które zastosowałem są „brzydkie”. Wobec tego zachęcam Cię… do poprawy ich! Popraw i przyślij mi rozwiązanie, a ja z przyjemnością je przejrzę. Miłego kodowania!
Skoro dotarłeś do końca – zostańmy w kontakcie na dłużej
Jeśli dotarłeś aż tutaj, to znaczy że jesteś człowiekiem innym niż większość. To znaczy, że nie wystarczają Ci skrótowce, że prawdopodobnie wymagasz od siebie nieco więcej niż przeciętnie. Pozostańmy zatem w kontakcie – zapisz się na Newsletter Wolnego Człowieka i pomóż mi tworzyć coraz lepsze treści! Jeśli zdecydujesz się wpisać – obiecuję zero spamu. Raz w tygodniu będziesz dostawał maila z podsumowaniem tego co zrobiłem na blogu.