# Pytanie 6: Metody reużywalności kodu w językach obiektowych ## Pytanie **"Omówić metody reużywalności kodu i struktur danych w obiektowych językach programowania."** Przedmiot: PROI (Programowanie Obiektowe) --- ## 📚 Odpowiedź główna ### Wprowadzenie **Reużywalność kodu (code reuse)** to fundamentalna zasada inżynierii oprogramowania - "nie wynajduj koła na nowo". W programowaniu obiektowym mamy kilka mechanizmów umożliwiających wielokrotne wykorzystanie kodu. ### Główne metody reużywalności ``` ┌─────────────────────────────────────────────────────────────────┐ │ METODY REUŻYWALNOŚCI │ ├─────────────────┬─────────────────┬─────────────────────────────┤ │ DZIEDZICZENIE │ KOMPOZYCJA │ PROGRAMOWANIE │ │ (Inheritance) │ (Composition) │ GENERYCZNE │ ├─────────────────┼─────────────────┼─────────────────────────────┤ │ INTERFEJSY │ DELEGACJA │ BIBLIOTEKI │ │ (Interfaces) │ (Delegation) │ (Libraries) │ ├─────────────────┼─────────────────┼─────────────────────────────┤ │ MIXINY │ TRAITY │ WZORCE PROJEKTOWE │ │ (Mixins) │ (Traits) │ (Design Patterns) │ └─────────────────┴─────────────────┴─────────────────────────────┘ ``` --- ## 1. Dziedziczenie (Inheritance) ### Definicja **Dziedziczenie** to mechanizm, w którym klasa pochodna (child) przejmuje atrybuty i metody klasy bazowej (parent). ### Typy dziedziczenia | Typ | Opis | Języki | |-----|------|--------| | **Pojedyncze** | Jedna klasa bazowa | Java, C# | | **Wielokrotne** | Wiele klas bazowych | C++, Python | | **Wielopoziomowe** | A → B → C | Wszystkie | | **Hierarchiczne** | A → B, A → C | Wszystkie | ### Przykład (C++) ```cpp // Klasa bazowa class Pojazd { protected: std::string marka; int predkosc; public: Pojazd(const std::string& m) : marka(m), predkosc(0) {} virtual void jedz() { std::cout << marka << " jedzie " << predkosc << " km/h\n"; } virtual ~Pojazd() = default; }; // Klasa pochodna - dziedziczy i rozszerza class Samochod : public Pojazd { private: int liczbaDrzwi; public: Samochod(const std::string& m, int drzwi) : Pojazd(m), liczbaDrzwi(drzwi) {} void jedz() override { predkosc = 120; Pojazd::jedz(); // wywołanie metody bazowej std::cout << "Drzwi: " << liczbaDrzwi << "\n"; } void parkuj() { /* nowa metoda */ } }; // Użycie Samochod auto1("BMW", 4); auto1.jedz(); // Polimorfizm - wywołuje Samochod::jedz() ``` ### Zalety i wady dziedziczenia | Zalety | Wady | |--------|------| | Naturalne modelowanie hierarchii | Silne wiązanie (tight coupling) | | Polimorfizm | Problem kruchej klasy bazowej | | Łatwe rozszerzanie | Problemy z wielodziedziczeniem (diamond) | | Współdzielenie implementacji | Narusza enkapsulację | ### Problem diamentu (Diamond Problem) ``` A / \ B C \ / D ``` ```cpp class A { public: void metoda() {} }; class B : public A {}; class C : public A {}; class D : public B, public C {}; // Która A::metoda()? D d; // d.metoda(); // BŁĄD: niejednoznaczne! d.B::metoda(); // OK - jawne wskazanie ``` **Rozwiązanie w C++:** Dziedziczenie wirtualne ```cpp class B : virtual public A {}; class C : virtual public A {}; class D : public B, public C {}; // Jedna kopia A ``` --- ## 2. Kompozycja (Composition) ### Definicja **Kompozycja** to budowanie złożonych obiektów z prostszych komponentów. Relacja "zawiera" (has-a) zamiast "jest" (is-a). ### Zasada: "Favor composition over inheritance" ```cpp // ZŁE - dziedziczenie class Stack : public std::vector { // Stack NIE JEST wektorem! // Użytkownik może wywołać insert(), erase()... }; // DOBRE - kompozycja class Stack { private: std::vector elements; // Stack ZAWIERA wektor public: void push(int x) { elements.push_back(x); } int pop() { int top = elements.back(); elements.pop_back(); return top; } bool empty() const { return elements.empty(); } // Tylko te metody, które mają sens dla stosu }; ``` ### Typy relacji obiektowych | Relacja | Siła | Cykl życia | Przykład | |---------|------|------------|----------| | **Kompozycja** | Silna | Zależny (owns) | Samochód → Silnik | | **Agregacja** | Słaba | Niezależny (uses) | Uniwersytet → Student | | **Asocjacja** | Luźna | Niezależny | Klient ↔ Zamówienie | ```cpp // Kompozycja - silnik "umiera" z samochodem class Samochod { private: Silnik silnik; // Obiekt wewnętrzny public: Samochod() : silnik() {} // Tworzy silnik // ~Samochod() niszczy silnik automatycznie }; // Agregacja - student istnieje niezależnie od uniwersytetu class Uniwersytet { private: std::vector studenci; // Wskaźniki/referencje public: void dodajStudenta(Student* s) { studenci.push_back(s); } // ~Uniwersytet() NIE niszczy studentów }; ``` --- ## 3. Programowanie generyczne (Generic Programming) ### Definicja **Programowanie generyczne** to pisanie kodu niezależnego od konkretnych typów danych, używając szablonów (templates) lub generyków. ### Szablony w C++ ```cpp // Szablon funkcji template T maximum(T a, T b) { return (a > b) ? a : b; } // Użycie - kompilator generuje wersje dla każdego typu int m1 = maximum(3, 5); // int double m2 = maximum(3.14, 2.71); // double std::string m3 = maximum("abc", "xyz"); // string // Szablon klasy template class Array { private: T data[N]; public: T& operator[](size_t i) { return data[i]; } constexpr size_t size() const { return N; } }; Array arr; // Tablica 10 intów Array darr; // Tablica 5 double'i ``` ### Generyki w Java/C# ```java // Java public class Box { private T value; public void set(T value) { this.value = value; } public T get() { return value; } } // Ograniczenia typów (bounded type parameters) public > T max(T a, T b) { return a.compareTo(b) > 0 ? a : b; } ``` ```csharp // C# public class Repository where T : class, IEntity, new() { public T Create() => new T(); public void Save(T entity) { /* ... */ } } ``` ### Zalety programowania generycznego | Zaleta | Opis | |--------|------| | **Type safety** | Błędy wykrywane w czasie kompilacji | | **Brak duplikacji** | Jeden kod dla wielu typów | | **Wydajność** | C++: specjalizacja w kompilacji, brak rzutowania | | **Czytelność** | Jawne wymagania typów | --- ## 4. Interfejsy (Interfaces) ### Definicja **Interfejs** definiuje kontrakt (zestaw metod), który klasa musi zaimplementować. Oddziela "co" od "jak". ### Przykład ```java // Java - interfejs public interface Drawable { void draw(); default void clear() { /* domyślna implementacja */ } } public interface Resizable { void resize(int width, int height); } // Klasa implementuje wiele interfejsów public class Rectangle implements Drawable, Resizable { @Override public void draw() { /* implementacja */ } @Override public void resize(int w, int h) { /* implementacja */ } } ``` ```cpp // C++ - klasa abstrakcyjna jako interfejs class IDrawable { public: virtual void draw() = 0; // Pure virtual virtual ~IDrawable() = default; }; class IResizable { public: virtual void resize(int w, int h) = 0; virtual ~IResizable() = default; }; class Rectangle : public IDrawable, public IResizable { public: void draw() override { /* ... */ } void resize(int w, int h) override { /* ... */ } }; ``` ### Interfejsy vs Klasy abstrakcyjne | Cecha | Interfejs | Klasa abstrakcyjna | |-------|-----------|-------------------| | Wielodziedziczenie | TAK | NIE (Java/C#) | | Pola | NIE (do Java 8) | TAK | | Konstruktor | NIE | TAK | | Implementacja metod | default (Java 8+) | TAK | | Cel | Definiuje kontrakt | Współdzieli implementację | --- ## 5. Delegacja (Delegation) ### Definicja **Delegacja** to przekazywanie odpowiedzialności za wykonanie operacji do innego obiektu. ```cpp // Bez delegacji - dziedziczenie class LoggingList : public std::list { public: void push_back(int x) { log("Adding: " + std::to_string(x)); std::list::push_back(x); } }; // Z delegacją - kompozycja class LoggingList { private: std::list delegate; // Delegat public: void push_back(int x) { log("Adding: " + std::to_string(x)); delegate.push_back(x); // Delegacja } size_t size() const { return delegate.size(); // Delegacja } }; ``` ### Wzorzec strategii (Strategy Pattern) ```cpp // Interfejs strategii class SortStrategy { public: virtual void sort(std::vector& data) = 0; virtual ~SortStrategy() = default; }; class QuickSort : public SortStrategy { public: void sort(std::vector& data) override { /* quicksort */ } }; class MergeSort : public SortStrategy { public: void sort(std::vector& data) override { /* mergesort */ } }; // Kontekst deleguje sortowanie do strategii class Sorter { private: std::unique_ptr strategy; public: void setStrategy(std::unique_ptr s) { strategy = std::move(s); } void performSort(std::vector& data) { strategy->sort(data); // Delegacja } }; ``` --- ## 6. Mixiny i Traity ### Mixiny (Mixins) Klasy dostarczające funkcjonalność do "wmieszania" do innych klas. ```python # Python - mixiny przez wielodziedziczenie class JSONSerializableMixin: def to_json(self): import json return json.dumps(self.__dict__) class XMLSerializableMixin: def to_xml(self): # implementacja... pass class User(JSONSerializableMixin, XMLSerializableMixin): def __init__(self, name, email): self.name = name self.email = email user = User("Jan", "jan@example.com") print(user.to_json()) # {"name": "Jan", "email": "jan@example.com"} ``` ### Traity (Traits) ```rust // Rust - traits trait Drawable { fn draw(&self); } trait Movable { fn move_to(&mut self, x: i32, y: i32); } struct Circle { x: i32, y: i32, radius: i32, } impl Drawable for Circle { fn draw(&self) { /* ... */ } } impl Movable for Circle { fn move_to(&mut self, x: i32, y: i32) { self.x = x; self.y = y; } } ``` ```php // PHP - traits trait Timestampable { public $createdAt; public $updatedAt; public function touch() { $this->updatedAt = new DateTime(); } } trait SoftDeletable { public $deletedAt; public function softDelete() { $this->deletedAt = new DateTime(); } } class Article { use Timestampable, SoftDeletable; public $title; } ``` --- ## 7. Biblioteki i frameworki ### Poziomy reużywalności ``` ┌─────────────────────────────────────────────────────┐ │ FRAMEWORK │ │ (IoC, definiuje architekturę aplikacji) │ ├─────────────────────────────────────────────────────┤ │ BIBLIOTEKA │ │ (kolekcja klas/funkcji, wywołujesz Ty) │ ├─────────────────────────────────────────────────────┤ │ MODUŁ/PAKIET │ │ (logicznie powiązane klasy) │ ├─────────────────────────────────────────────────────┤ │ KLASA │ │ (jednostka enkapsulacji) │ ├─────────────────────────────────────────────────────┤ │ FUNKCJA │ │ (najmniejsza jednostka reużywalna) │ └─────────────────────────────────────────────────────┘ ``` ### Przykłady | Poziom | Przykłady | |--------|-----------| | Framework | Spring, .NET, Unity, Django | | Biblioteka | STL, Boost, jQuery, NumPy | | Moduł | java.util, System.IO | | Klasa | ArrayList, HttpClient | --- ## 8. Wzorce projektowe (Design Patterns) ### Wzorce wspierające reużywalność | Wzorzec | Typ | Cel | |---------|-----|-----| | **Factory Method** | Kreacyjny | Delegacja tworzenia obiektów | | **Abstract Factory** | Kreacyjny | Rodziny powiązanych obiektów | | **Prototype** | Kreacyjny | Klonowanie obiektów | | **Adapter** | Strukturalny | Dopasowanie interfejsów | | **Decorator** | Strukturalny | Dynamiczne rozszerzanie | | **Strategy** | Behawioralny | Wymienne algorytmy | | **Template Method** | Behawioralny | Szkielet algorytmu z krokami | ### Przykład: Template Method ```cpp // Szkielet algorytmu w klasie bazowej class DataParser { public: // Template method - definiuje szkielet void parse(const std::string& data) { openFile(); readHeader(); processData(data); // Krok abstrakcyjny closeFile(); } protected: virtual void openFile() { /* domyślna impl */ } virtual void readHeader() { /* domyślna impl */ } virtual void processData(const std::string& data) = 0; // PURE virtual void closeFile() { /* domyślna impl */ } }; // Konkretne implementacje class JSONParser : public DataParser { protected: void processData(const std::string& data) override { // Parsowanie JSON } }; class XMLParser : public DataParser { protected: void processData(const std::string& data) override { // Parsowanie XML } }; ``` --- ## 📊 Porównanie metod reużywalności | Metoda | Wiązanie | Elastyczność | Złożoność | |--------|----------|--------------|-----------| | Dziedziczenie | Statyczne | Niska | Średnia | | Kompozycja | Dynamiczne | Wysoka | Niska | | Interfejsy | Statyczne* | Wysoka | Niska | | Generyki | Statyczne | Wysoka | Średnia | | Delegacja | Dynamiczne | Wysoka | Średnia | | Mixiny/Traity | Statyczne | Średnia | Średnia | *ale implementacja może być dynamiczna (polimorfizm) --- ## 🧠 Mnemoniki ### "SOLID" - zasady dobrego OOP: - **S**ingle Responsibility - jedna odpowiedzialność - **O**pen/Closed - otwarte na rozszerzenia, zamknięte na modyfikacje - **L**iskov Substitution - podtyp zastępuje typ bazowy - **I**nterface Segregation - wiele małych interfejsów > jeden duży - **D**ependency Inversion - zależność od abstrakcji, nie konkretu ### "HAS-A przed IS-A": - **HAS-A** = kompozycja (Samochód HAS-A Silnik) - **IS-A** = dziedziczenie (Samochód IS-A Pojazd) - Preferuj HAS-A! ### "DRIED" dla reużywalności: - **D**on't **R**epeat - nie powtarzaj kodu - **I**nterfaces - definiuj kontrakty - **E**ncapsulate - ukrywaj szczegóły - **D**elegate - przekazuj odpowiedzialność ### "GIT" dla generyków: - **G**eneric - niezależne od typu - **I**nvariant - sprawdzane w kompilacji - **T**ype-safe - bezpieczne typowanie --- ## ❓ Możliwe pytania dodatkowe (follow-up) ### Q1: "Kiedy dziedziczenie, a kiedy kompozycja?" **Odpowiedź:** | Użyj dziedziczenia gdy: | Użyj kompozycji gdy: | |------------------------|---------------------| | Relacja "jest" (is-a) | Relacja "zawiera" (has-a) | | Potrzebujesz polimorfizmu | Potrzebujesz elastyczności | | Klasa pochodna JEST typem bazowym | Chcesz zmieniać zachowanie w runtime | | Zasada Liskov jest spełniona | Chcesz uniknąć problemów z hierarchią | **Przykład decyzji:** - Ptak IS-A Zwierzę → dziedziczenie OK - Samochód HAS-A Silnik → kompozycja! - Stack IS-A Vector? → NIE! Kompozycja. --- ### Q2: "Co to jest zasada substytucji Liskov?" **Odpowiedź:** > "Obiekty klasy bazowej powinny być zastępowalne obiektami klas pochodnych bez zmiany poprawności programu." **Naruszenie LSP:** ```cpp class Rectangle { public: virtual void setWidth(int w) { width = w; } virtual void setHeight(int h) { height = h; } int area() { return width * height; } protected: int width, height; }; class Square : public Rectangle { // PROBLEM! public: void setWidth(int w) override { width = height = w; } void setHeight(int h) override { width = height = h; } }; // Kod klienta void resize(Rectangle& r) { r.setWidth(5); r.setHeight(10); assert(r.area() == 50); // FAIL dla Square! } ``` **Rozwiązanie:** Kwadrat nie powinien dziedziczyć po prostokącie (matematycznie IS-A, programistycznie NIE). --- ### Q3: "Czym różnią się szablony C++ od generyków Java?" **Odpowiedź:** | Cecha | C++ Templates | Java Generics | |-------|---------------|---------------| | Implementacja | Kompilacja (code generation) | Type erasure (runtime) | | Specjalizacja | TAK | NIE | | Prymitywy | TAK (int, double...) | NIE (tylko obiekty) | | Sprawdzanie typu | W czasie instancjacji | W czasie kompilacji | | Ograniczenia | Concepts (C++20) | Bounded types | | Wydajność | Optymalna | Rzutowanie w runtime | ```cpp // C++ - specjalizacja szablonu template class Container { /* ogólna implementacja */ }; template<> class Container { /* specjalna dla bool - bitset */ }; ``` --- ### Q4: "Co to jest Dependency Injection?" **Odpowiedź:** **Dependency Injection (DI)** = wzorzec przekazywania zależności z zewnątrz zamiast tworzenia ich wewnątrz. ```cpp // BEZ DI - silne wiązanie class OrderService { private: MySQLDatabase db; // Zależność "zahardkodowana" public: OrderService() : db() {} }; // Z DI - luźne wiązanie class OrderService { private: IDatabase& db; // Zależność od abstrakcji public: OrderService(IDatabase& database) : db(database) {} // Injection }; // Użycie MySQLDatabase mysql; PostgresDatabase postgres; OrderService service1(mysql); // Możemy łatwo zmienić OrderService service2(postgres); // implementację ``` **Typy DI:** 1. Constructor Injection (preferowany) 2. Setter Injection 3. Interface Injection --- ### Q5: "Jak wzorce projektowe wspierają reużywalność?" **Odpowiedź:** | Wzorzec | Mechanizm reużywalności | |---------|------------------------| | **Factory** | Oddziela tworzenie od użycia | | **Strategy** | Wymienna rodzina algorytmów | | **Decorator** | Dynamiczne dodawanie funkcji | | **Adapter** | Reużycie niekompatybilnych klas | | **Template Method** | Reużycie szkieletu algorytmu | | **Composite** | Jednolite traktowanie obiektów i kolekcji | --- ### Q6: "Wyjaśnij różnicę między agregacją a kompozycją" **Odpowiedź:** | Cecha | Kompozycja | Agregacja | |-------|------------|-----------| | Siła związku | Silna ("owns") | Słaba ("uses") | | Cykl życia | Zależny | Niezależny | | UML | Wypełniony romb ◆ | Pusty romb ◇ | | Przykład | Samochód → Silnik | Firma → Pracownik | ```cpp // Kompozycja - silnik jest częścią samochodu class Car { Engine engine; // Engine tworzony z Car, niszczony z Car }; // Agregacja - pracownik może istnieć bez firmy class Company { std::vector employees; // Wskaźniki - nie "posiada" }; ``` --- ## 🎯 Kluczowe punkty do zapamiętania 1. **Kompozycja > Dziedziczenie** - elastyczność, luźne wiązanie 2. **Interfejsy oddzielają** kontrakt od implementacji 3. **Generyki eliminują** duplikację kodu dla różnych typów 4. **SOLID** - 5 zasad dobrego OOP 5. **Delegacja** - przekazuj odpowiedzialność 6. **Wzorce projektowe** - sprawdzone rozwiązania typowych problemów --- ## 📖 Źródła do pogłębienia 1. Gamma et al. - "Design Patterns: Elements of Reusable OO Software" (GoF) 2. Martin, R. - "Clean Code" i "Clean Architecture" 3. Meyers, S. - "Effective C++" 4. Bloch, J. - "Effective Java"