﻿# OOP_LAB_4_zad_05.py
"""
Rozwiązanie zadania 5: Budowa komputera (Kompozycja vs Dziedziczenie)
"""

class UrzadzenieElektroniczne:
    """
    Ogólna klasa bazowa zarządzająca zasilaniem i stanem sprzętu.
    """
    def __init__(self):
        self.czy_wlaczone = False

    def wlacz(self):
        """
        Prosta logika włączenia zasilania we wspólnym urządzeniu.
        """
        self.czy_wlaczone = True
        print("Zasilanie ON. Start systemu...")


# Poniższe klasy stanowią mniejsze, wymienne komponenty. 
# Z logicznego punktu widzenia budowania sprzętu, Komputer 'nie jest' Procesorem, 
# więc relacja dziedziczenia ('is-a') byłaby tutaj krytycznym błędem projektowym!

class Procesor:
    """Niezależna klasa modelująca CPU i jego specyfikację."""
    def __init__(self, model: str, taktowanie: float):
        self.model = model
        self.taktowanie = taktowanie
        
    def info(self) -> str:
        return f"Procesor: {self.model} ({self.taktowanie} GHz)"


class PamiecRAM:
    """Niezależna klasa modelująca moduły RAM."""
    def __init__(self, pojemnosc: int):
        self.pojemnosc = pojemnosc
        
    def info(self) -> str:
        return f"RAM: {self.pojemnosc} GB"


class Komputer(UrzadzenieElektroniczne):
    """
    Komputer jest (is-a) rodzajem urządzenia elektronicznego, więc logicznie poprawnym
    jest zastosowanie tu dziedziczenia po klasie UrzadzenieElektroniczne.
    
    Jednocześnie posiada (has-a) on elementy składowe (jak CPU i RAM) co realizujemy za pomocą kompozycji.
    """
    def __init__(self, cpu_model: str, ram_pojemnosc: int):
        # Inicjalizacja zasilania odziedziczonego z klasy UrzadzenieElektroniczne.
        super().__init__()
        
        # KOMPOZYCJA: Przypisujemy instancje (niezależne obiekty) do atrybutów komputera.
        # W ten sposób delegujemy odpowiedzialność – to procesor wie wszystko o taktowaniu, 
        # a RAM o pojemności, Komputer ich jedynie "używa".
        self.cpu = Procesor(cpu_model, 3.5)  # Dla uproszczenia stałe taktowanie w konstruktorze
        self.ram = PamiecRAM(ram_pojemnosc)
        
    def wlacz(self):
        """
        Nadpisana metoda startowa. Najpierw korzystamy z zachowania odziedziczonego z systemu bazowego,
        a potem wydajemy komendy do poszczególnych "składników" kompozycji.
        """
        # start zasilania
        super().wlacz()
        # Wykorzystanie atrybutów-podobiektów poprzez mechanizm delegacji 
        print(f"{self.cpu.info()}, {self.ram.info()}. Status: Gotowy.")


if __name__ == "__main__":
    print("--- Testowanie architektury hybrydowej (Kompozycja + Dziedziczenie) ---")
    
    laptop = Komputer("Intel i7", 16)
    laptop.wlacz()
    
    print("\n--- Dowód elastyczności Kompozycji ---")
    # Zaletą kompozycji nad dziedziczeniem sprzętowym jest to, że łatwo można w trakcie
    # wykonania (runtime) wymieniać komponenty:
    print("Wymiana procesora na mocniejszy model (Ryzen 9)...")
    laptop.cpu = Procesor("Ryzen 9", 4.2)
    laptop.wlacz()
