{"id":25396,"date":"2025-10-15T05:00:00","date_gmt":"2025-10-15T03:00:00","guid":{"rendered":"https:\/\/sii.pl\/blog\/?p=25396"},"modified":"2025-10-15T09:48:41","modified_gmt":"2025-10-15T07:48:41","slug":"programowanie-obiektowe-na-przykladzie-pythona","status":"publish","type":"post","link":"https:\/\/sii.pl\/blog\/programowanie-obiektowe-na-przykladzie-pythona\/","title":{"rendered":"Programowanie obiektowe na przyk\u0142adzie Pythona"},"content":{"rendered":"\n<p>Zaczynaj\u0105c swoj\u0105 przygod\u0119 z programowaniem, w zdecydowanej wi\u0119kszo\u015bci przypadk\u00f3w kieruje nami ch\u0119\u0107 zmuszenia maszyny (najcz\u0119\u015bciej komputera), aby co\u015b dla nas zrobi\u0142a. W tym celu musimy znale\u017a\u0107 spos\u00f3b, jak si\u0119 z ni\u0105 porozumie\u0107. St\u0105d te\u017c wybieramy odpowiedni \u2013 z punktu widzenia naszych upodoba\u0144 lub zadania, kt\u00f3re przed nami stoi \u2013 j\u0119zyk programowania.<\/p>\n\n\n\n<p>Aby komunikacja by\u0142a skuteczna, niezb\u0119dne jest poznanie instrukcji, kt\u00f3re umo\u017cliwi\u0105 nam osi\u0105gni\u0119cie zamierzonego celu, czyli po prostu sk\u0142adni. Wraz z kolejnymi etapami nauki dowiadujemy si\u0119 coraz wi\u0119cej o strukturach i zagadnieniach typowych dla danego j\u0119zyka, dlatego pr\u0119dzej czy p\u00f3\u017aniej przyjdzie nam zmierzy\u0107 si\u0119 r\u00f3wnie\u017c z tzw. <em>programowaniem obiektowym<\/em>.<\/p>\n\n\n\n<p>W tym artykule chcia\u0142bym przybli\u017cy\u0107, pos\u0142uguj\u0105c si\u0119 <strong>j\u0119zykiem Python<\/strong>, <strong>czym charakteryzuje si\u0119 ten konkretny rodzaj programowania, jak poprawnie go stosowa\u0107 oraz kiedy jest to zalecane. <\/strong><\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Programowanie obiektowe \u2013 czym jest?<\/strong><\/h2>\n\n\n\n<p>Programowanie zorientowane obiektowo (ang. <em>object-oriented programming, OOP<\/em>) to spos\u00f3b tworzenia oprogramowania, w kt\u00f3rym obiekty ze \u015bwiata rzeczywistego przedstawiamy jako obiekty programowe, gdzie ka\u017cdy z nich posiada swoje cechy (zwane atrybutami) oraz mo\u017ce wykonywa\u0107 jakie\u015b czynno\u015bci (nazywane metodami). Innymi s\u0142owy \u2013 <strong>\u0142\u0105czymy dane z operacjami<\/strong>, kt\u00f3re mog\u0105 by\u0107 na tych danych przeprowadzane.<\/p>\n\n\n\n<p>Wyobra\u017amy sobie, \u017ce mamy zaprogramowa\u0107 prostego robota imituj\u0105cego zachowania psa. W takiej sytuacji mo\u017cemy przedstawi\u0107 go w formie obiektu programowego o nazwie <em>Dog<\/em> o nast\u0119puj\u0105cych atrybutach:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>imi\u0119,<\/li>\n\n\n\n<li>wiek,<\/li>\n\n\n\n<li>waga,<\/li>\n\n\n\n<li>rasa,<\/li>\n\n\n\n<li>umaszczenie.<\/li>\n<\/ul>\n\n\n\n<p>Ponadto, jak w przypadku typowego psa, chcemy, aby nasz robot potrafi\u0142:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>szczeka\u0107,<\/li>\n\n\n\n<li>aportowa\u0107,<\/li>\n\n\n\n<li>merda\u0107 ogonem,<\/li>\n<\/ul>\n\n\n\n<p>co, stosuj\u0105c zasady programowania obiektowego, <strong>implementujemy w formie metod<\/strong>.<\/p>\n\n\n\n<p>Oczywi\u015bcie, jest to jedynie do\u015b\u0107 barwna pr\u00f3ba zobrazowania, jak obiektowo\u015b\u0107 mo\u017ce zosta\u0107 wykorzystana. Zazwyczaj jednak komercyjne oprogramowanie zawiera bardziej abstrakcyjne koncepcje takie jak:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>klient poczty e-mail,<\/li>\n\n\n\n<li>instancja bazy danych,<\/li>\n\n\n\n<li>koszyk w sklepie internetowym.<\/li>\n<\/ul>\n\n\n\n<p>Generalnie rzecz bior\u0105c, taka forma pozwala nam w do\u015b\u0107 prosty spos\u00f3b odwzorowa\u0107 dowolne obiekty z naszego otoczenia oraz wyst\u0119puj\u0105ce pomi\u0119dzy nimi relacje.<\/p>\n\n\n\n<figure class=\"wp-block-image aligncenter size-full\"><a href=\"https:\/\/sii.pl\/szkolenia\/wyszukiwarka-szkolen\/all\/all\/all\/python\/\" target=\"_blank\" rel=\"noreferrer noopener\"><img decoding=\"async\" width=\"737\" height=\"170\" src=\"https:\/\/sii.pl\/blog\/wp-content\/uploads\/2024\/12\/szkolenia-12.jpg\" alt=\"oferta szkole\u0144\" class=\"wp-image-29785\" srcset=\"https:\/\/sii.pl\/blog\/wp-content\/uploads\/2024\/12\/szkolenia-12.jpg 737w, https:\/\/sii.pl\/blog\/wp-content\/uploads\/2024\/12\/szkolenia-12-300x69.jpg 300w\" sizes=\"(max-width: 737px) 100vw, 737px\" \/><\/a><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Klasy i obiekty w programowaniu obiektowym<\/strong><\/h2>\n\n\n\n<p>No dobrze. Teoria teori\u0105, ale jak to wszystko wygl\u0105da w praktyce? <strong>Czym tak naprawd\u0119 s\u0105 te klasy, obiekty, metody itp., i jak je tworzy\u0107?<\/strong><\/p>\n\n\n\n<p>Og\u00f3lna definicja klasy jest wsp\u00f3lna dla wszystkich j\u0119zyk\u00f3w wspieraj\u0105cych paradygmat programowania obiektowego i m\u00f3wi, \u017ce <strong>klasa to nic innego jak szablon<\/strong>, na podstawie kt\u00f3rego tworzone s\u0105 obiekty. Zawiera wi\u0119c wszystkie elementy niezb\u0119dne do utworzenia (lub, jak kto woli, skonkretyzowania) interesuj\u0105cego nas obiektu. Natomiast sam <strong>obiekt to reprezentacja danej klasy<\/strong> (instancja) utworzona zgodnie z deklaracj\u0105 tej klasy.<\/p>\n\n\n\n<p>Dla lepszego zrozumienia tematu mo\u017cna pos\u0142u\u017cy\u0107 si\u0119 inn\u0105 analogi\u0105: <strong>klasa jest jak przepis kuchenny zawieraj\u0105cy zestaw instrukcji, za\u015b obiekt to ciasto wykonane na podstawie tego przepisu<\/strong>. Takich obiekt\u00f3w mo\u017cemy mie\u0107 dowoln\u0105 ilo\u015b\u0107 i ka\u017cdy z nich b\u0119dzie mia\u0142 przypisany typ odpowiadaj\u0105cy klasie, na podstawie kt\u00f3rej zosta\u0142 utworzony.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Tworzenie klas<\/strong><\/h2>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: python; title: ; notranslate\" title=\"\">\nclass DogClass:\n   pass\n\n<\/pre><\/div>\n\n\n<p>Powy\u017cej zdefiniowali\u015bmy sobie nasz\u0105 pierwsz\u0105 klas\u0119 o nazwie <em>DogClass<\/em>. Jak wida\u0107, w tym celu konieczne by\u0142o u\u017cycie s\u0142owa kluczowego <em>class,<\/em> a nast\u0119pnie podanie nazwy. Zgodnie ze standardem PEP8 (z kt\u00f3rym zach\u0119cam, aby ka\u017cdy si\u0119 zapozna\u0142!) w Pythonie nazwy klas podajemy, u\u017cywaj\u0105c tzw. <em>CamelCase<\/em> tj. <strong>rozpoczynaj\u0105c ka\u017cde s\u0142owo wielk\u0105 liter\u0105 z pomini\u0119ciem spacji pomi\u0119dzy nimi<\/strong>. Na t\u0119 chwil\u0119 cia\u0142o naszej klasy jest puste (<em>pass<\/em>). W kolejnych krokach b\u0119dziemy j\u0105 rozbudowywa\u0107 oraz tworzy\u0107 obiekty na jej podstawie.<\/p>\n\n\n\n<p><strong><em>Komentarz: <\/em><\/strong><em>w Pythonie wszystko jest obiektem, tzn. wszystko jest reprezentacj\u0105 jakiej\u015b klasy. Przyk\u0142adowo, posiadaj\u0105c dowoln\u0105 warto\u015b\u0107 tekstow\u0105 (string), np. \u201cSome text value\u201d, jest ona niczym innym, jak po prostu reprezentacj\u0105 klasy str (m\u00f3wi\u0105c inaczej: posiada typ str). Definiuj\u0105c w\u0142asne klasy, tworzymy tak naprawd\u0119 nowe typy.<\/em><\/p>\n\n\n\n<figure class=\"wp-block-image aligncenter size-full\"><img decoding=\"async\" width=\"752\" height=\"128\" src=\"https:\/\/sii.pl\/blog\/wp-content\/uploads\/2023\/11\/Obraz1-1.png\" alt=\"Some text value w Pythonie\" class=\"wp-image-25399\" srcset=\"https:\/\/sii.pl\/blog\/wp-content\/uploads\/2023\/11\/Obraz1-1.png 752w, https:\/\/sii.pl\/blog\/wp-content\/uploads\/2023\/11\/Obraz1-1-300x51.png 300w\" sizes=\"(max-width: 752px) 100vw, 752px\" \/><figcaption class=\"wp-element-caption\">Ryc. 1 Some text value w Pythonie<\/figcaption><\/figure>\n\n\n\n<p>Co w takim razie mo\u017ce zawiera\u0107 klasa, aby jej definicja mia\u0142a sens oraz mog\u0142a by\u0107 p\u00f3\u017aniej wykorzystana? Ot\u00f3\u017c, klasy <strong>posiadaj\u0105 dwie g\u0142\u00f3wne sk\u0142adowe<\/strong>, kt\u00f3rymi s\u0105 atrybuty i metody.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Atrybuty<\/strong><\/h3>\n\n\n\n<p>Atrybuty mo\u017cemy zdefiniowa\u0107 po prostu jako cechy, kt\u00f3re b\u0119dzie posiada\u0142 obiekt. To nic innego, jak zmienne maj\u0105ce jak\u0105\u015b warto\u015b\u0107.<\/p>\n\n\n\n<p>Wyr\u00f3\u017cniamy:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>atrybuty instancji<\/strong> \u2013 jak wskazuje nazwa, odnosz\u0105 si\u0119 do instancji (obiekt\u00f3w) i okre\u015blaj\u0105, jakie cechy dana instancja posiada. Dzi\u0119ki nim jeste\u015bmy w stanie odr\u00f3\u017cni\u0107 od siebie wiele obiekt\u00f3w tej samej klasy. Tworzymy je zazwyczaj w konstruktorze <em>__init__<\/em> (inaczej: metodzie inicjalizacji), a ich nazwy s\u0105 poprzedzone przedrostkiem <em>self<\/em>. Odwo\u0142anie do atrybut\u00f3w instancji poza cia\u0142em klasy odbywa si\u0119 jedynie z wykorzystaniem obiektu. Nie ma mo\u017cliwo\u015bci np. zmiany warto\u015bci atrybutu instancji bez posiadania instancji tej klasy: <strong>nie istnieje instancja \u2192 nie istniej\u0105 jej atrybuty.<\/strong><br><br><strong><em>Komentarz: <\/em><\/strong><em>\u201cself\u201d to parametr odnosz\u0105cy si\u0119 do instancji klasy. Dzi\u0119ki niemu wiemy, \u017ce dany atrybut lub metoda dotyczy w\u0142a\u015bnie instancji i mo\u017ce zmienia\u0107 jej stan. \u201cself\u201d nie jest s\u0142owem kluczowym, a jedynie powszechnie przyj\u0119t\u0105 konwencj\u0105, dlatego mo\u017cna je zast\u0105pi\u0107 dowolnym s\u0142owem, aczkolwiek nie jest to zalecane, poniewa\u017c mo\u017ce wprowadza\u0107 w b\u0142\u0105d.&nbsp; <\/em><br><\/li>\n\n\n\n<li><strong>atrybuty klasy<\/strong> \u2013 zwane r\u00f3wnie\u017c statycznymi. Przechowuj\u0105 dane specyficzne dla klasy i nie odwo\u0142uj\u0105 si\u0119 w \u017caden spos\u00f3b do obiekt\u00f3w (nie opisuj\u0105 ich). Tworzymy je najcz\u0119\u015bciej bezpo\u015brednio po nazwie klasy bez u\u017cycia przedrostka <em>self<\/em>. Mo\u017cna korzysta\u0107 z nich zar\u00f3wno z poziomu instancji jak i bez posiadania jakiegokolwiek obiektu danej klasy poprzez notacj\u0119 z kropk\u0105: <em>Klasa.atrybut<\/em>.<\/li>\n<\/ul>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: python; title: ; notranslate\" title=\"\">\nclass Dog:\n    # class attributes\n    division = &quot;mammal&quot;\n    counter = 0\n\n    def __init__(self, name, age):\n        # instance attributes\n        self.name = name\n        self. Age = age\n        self.life_expectancy = 12\n        Dog.counter += 1\n\n\nif __name__ == &#039;__main__&#039;:\n    # usage of class attributes\n    print(f&quot;Division: {Dog.division}&quot;)\n    doggie = Dog(&quot;Pluto&quot;, 5)  # object initialization with parameters\n    print(f&quot;Number of dogs: {doggie.counter}&quot;)  # alternatively: Dog.counter\n\n\n    # usage of instance attributes\n    print(f&quot;Name: {doggie.name}&quot;)\n    print(f&quot;Age: {doggie.age}&quot;)\n    doggie.life_expectancy = 14  # object attribute modification\n    print(f&quot;Expected length of life: {doggie.life_expectancy}&quot;)\n\n\n# Output:\n# Division: mammal\n# Number of dogs: 1\n# Name: Pluto\n# Age: 5\n# Expected length of life: 14\n<\/pre><\/div>\n\n\n<h3 class=\"wp-block-heading\"><strong>Metody<\/strong><\/h3>\n\n\n\n<p>Metody to nic innego, jak funkcje zdefiniowane wewn\u0105trz klasy. Je z kolei mo\u017cemy podzieli\u0107 na trzy rodzaje:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>metody instancji<\/strong> \u2013 podobnie jak w przypadku atrybut\u00f3w instancji, dotycz\u0105 wszystkich tworzonych obiekt\u00f3w i wp\u0142ywaj\u0105 na ich stan (np. modyfikuj\u0105 ich atrybuty). Tworz\u0105c metod\u0119 instancji, jako pierwszy parametr zawsze przekazujemy <em>self<\/em>, a dopiero po nim kolejne. <em>self <\/em>wskazuje jedynie, \u017ce odnosimy si\u0119 do instancji i nie przekazujemy jego warto\u015bci podczas wywo\u0142ywania danej metody.<\/li>\n\n\n\n<li><strong>metody statyczne<\/strong> \u2013 cho\u0107 s\u0105 cz\u0119\u015bci\u0105 klasy, to nie widz\u0105 innych jej element\u00f3w i nie musz\u0105 mie\u0107 z klas\u0105 \u017cadnego zwi\u0105zku (s\u0105 niezale\u017cne). Nie odwo\u0142uj\u0105 si\u0119 r\u00f3wnie\u017c do obiekt\u00f3w, dlatego nie posiadaj\u0105 parametru <em>self<\/em>. Ich definicj\u0119 poprzedzamy dekoratorem <em>@staticmethod<\/em>, a wywo\u0142anie odbywa si\u0119 poprzez nazw\u0119 klasy, w kt\u00f3rej si\u0119 zawieraj\u0105 (<em>Klasa.metoda_statyczna()<\/em>).<\/li>\n\n\n\n<li><strong>metody klasy<\/strong> \u2013 pracuj\u0105 na poziomie klasy. Podobnie jak metody statyczne, nie wymagaj\u0105 instancji, ale s\u0105 \u015bwiadome bycia cz\u0119\u015bci\u0105 klasy i mog\u0105 odwo\u0142ywa\u0107 si\u0119 do pozosta\u0142ych jej metod (r\u00f3wnie\u017c statycznych). Tworzymy je z u\u017cyciem dekoratora <em>@classmethod<\/em> oraz poprzez przekazanie parametru <em>cls<\/em>.<br><br><strong><em>Komentarz: <\/em><\/strong><em>Parametr \u201ccls\u201d, podobnie jak \u201cself\u201d, jest konwencj\u0105 nazewnicz\u0105. Odnosi si\u0119 do klasy i jest u\u017cywany wraz z dekoratorem @classmethod. Umo\u017cliwia dost\u0119p do atrybut\u00f3w i metod klasy, dzi\u0119ki czemu modyfikuje jej stan (nie wp\u0142ywa jednak bezpo\u015brednio na stan instancji!).<\/em><\/li>\n<\/ul>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>metody specjalne<\/strong> (ang. <em>special methods, dunder methods<\/em> od <em>\u201cdouble underscore\u201d<\/em>) \u2013 zwane r\u00f3wnie\u017c metodami magicznymi. S\u0105 to metody rozpoczynaj\u0105ce si\u0119 oraz zako\u0144czone podw\u00f3jnym znakiem podkre\u015blenia oraz wywo\u0142ywane przez Pythona w okre\u015blonych sytuacjach. Metody magiczne mog\u0105 odwo\u0142ywa\u0107 si\u0119 do instancji dzi\u0119ki parametrowi <em>self<\/em> lub do samej klasy za pomoc\u0105 <em>cls<\/em>. Najcz\u0119\u015bciej spotykan\u0105 jest metoda inicjalizacji <em>__init__<\/em> (konstruktor), kt\u00f3ra jest wywo\u0142ywana automatycznie podczas tworzenia instancji klasy.<\/li>\n<\/ul>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: python; title: ; notranslate\" title=\"\">\nclass Dog:\n    # class attributes\n    division = &quot;mammal&quot;\n    counter = 0\n\n    def __init__(self, name, age):\n        # instance attributes\n        self.name = name\n        self.age = age\n        self.life_expectancy = 13\n        Dog.counter += 1\n\n    def __str__(self):\n        return f&quot;Welcome {self.name} (age: {self.age}, division: {Dog.division}) to the world!&quot;\n\n    @classmethod\n    def get_number_of_dogs(cls):\n        print(f&quot;Currently there is {cls.counter} dog(s)!&quot;)\n\n    @staticmethod\n    def get_average_cost(total_cost, years):\n        avg_cost = total_cost \/ years\n        print(f&quot;The average cost is {avg_cost}$ per year&quot;)\n\n        return avg_cost\n\n    def is_hungry(self, meals, hours_of_fun):\n        calories_eaten = meals * 150\n        calories_burnt = hours_of_fun * 72\n\n        if calories_eaten - calories_burnt &lt; 350:\n            print(f&quot;{self.name} has been playing for {hours_of_fun} hours and he is hungry!&quot;)\n        else:\n            print(f&quot;{self.name} is fine&quot;)\n\n\nif __name__ == &#039;__main__&#039;:\n    doggie = Dog(&quot;Pluto&quot;, 5)       # __init__ magic method is used\n    print(doggie)                  # __str__ magic method is used\n    Dog.get_number_of_dogs()       # usage of class method\n    doggie.is_hungry(3, 2)         # usage of instance method\n    cost_1 = Dog.get_average_cost(52, 2)      # usage of static method via class\n    cost_2 = doggie.get_average_cost(1273, 5) # usage of static method via instance\n\n\n# Output:\n# Welcome Pluto (age: 5, division: mammal) to the world!\n# Currently there is 1 dog(s)!\n# Rex has been playing for 2 hours and he is hungry!\n# The average cost is 26.0$ per year\n# The average cost is 254.6$ per year\n<\/pre><\/div>\n\n\n<h2 class=\"wp-block-heading\"><strong>Cechy programowania obiektowego<\/strong><\/h2>\n\n\n\n<p><strong>Ka\u017cdy j\u0119zyk programowania, aby m\u00f3g\u0142 by\u0107 uznawany za j\u0119zyk zorientowany obiektowo, powinien posiada\u0107 przynajmniej cztery podstawowe cech<\/strong>y, kt\u00f3rymi s\u0105:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>abstrakcja,<\/li>\n\n\n\n<li>hermetyzacja,<\/li>\n\n\n\n<li>dziedziczenie,<\/li>\n\n\n\n<li>polimorfizm.<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Abstrakcja<\/strong><\/h3>\n\n\n\n<p>Programowanie obiektowe s\u0142u\u017cy do definiowania rzeczywistych obiekt\u00f3w, kt\u00f3re, jak wiadomo, mog\u0105 mie\u0107 niemal niesko\u0144czon\u0105 liczb\u0119 cech i w\u0142a\u015bciwo\u015bci. W zasadzie ka\u017cdy otaczaj\u0105cy nas obiekt jest na sw\u00f3j spos\u00f3b unikalny. Nie ma przecie\u017c dw\u00f3ch identycznych samochod\u00f3w, krzese\u0142 czy sto\u0142\u00f3w. Abstrakcja pozwala okre\u015bli\u0107 pewien og\u00f3\u0142 z pomini\u0119ciem nieistotnych detali oraz skupi\u0107 si\u0119 tylko na tym, co istotne.<\/p>\n\n\n\n<p>I tak, chc\u0105c przedstawi\u0107 za pomoc\u0105 abstrakcji dowolne auto, nie b\u0119dziemy koncentrowa\u0107 si\u0119 na jego kolorze, pr\u0119dko\u015bci maksymalnej czy wyposa\u017ceniu \u2013 to wszystko szczeg\u00f3\u0142y. Interesowa\u0107 nas b\u0119dzie tylko to, co wsp\u00f3lne dla wszystkich samochod\u00f3w, np.: fakt, \u017ce posiadaj\u0105 one kierownic\u0119, skrzyni\u0119 bieg\u00f3w, hamulce czy mo\u017cliwo\u015b\u0107 jazdy. <strong>Abstrakcja umo\u017cliwia wi\u0119c skupienie si\u0119 na og\u00f3lnym obrazie bez zag\u0142\u0119biania si\u0119 w szczeg\u00f3\u0142y<\/strong> \u2013 jest to rodzaj uproszczenia rozpatrywanego problemu (klasy\/obiektu).<\/p>\n\n\n\n<p>Z abstrakcj\u0105 nieodzownie \u0142\u0105czy si\u0119 poj\u0119cie <strong>klasy abstrakcyjnej<\/strong>, kt\u00f3ra jest baz\u0105 dla innych klas. Zawiera ona metody wsp\u00f3lne dla klas tworzonych w oparciu o ni\u0105 (tzw. klas potomnych, tj. dziedzicz\u0105cych z niej), a tak\u017ce nie posiada swoich bezpo\u015brednich reprezentacji (nie tworzy si\u0119 jej instancji). Klasa abstrakcyjna w Pythonie jest szczeg\u00f3lnie wa\u017cna w kontek\u015bcie tworzenia interfejs\u00f3w, poniewa\u017c j\u0119zyk ten, w odr\u00f3\u017cnieniu np. od Javy, nie posiada w swojej sk\u0142adni struktur s\u0142u\u017c\u0105cych bezpo\u015brednio do ich budowy. To w\u0142a\u015bnie za pomoc\u0105 klasy abstrakcyjnej <strong>jeste\u015bmy w stanie zdefiniowa\u0107 dowolny interfejs<\/strong>, tzn. okre\u015bli\u0107 zbi\u00f3r wszystkich metod, jakie musz\u0105 posiada\u0107 klasy dziedzicz\u0105ce.<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: python; title: ; notranslate\" title=\"\">\nfrom abc import ABC, abstractmethod\n\n\nclass Animal(ABC):\n  \n   @abstractmethod\n   def move(self):\n       pass\n  \n   @abstractmethod\n   def eat(self):\n       pass\n\n   @abstractmethod\n   def make_sound(self):\n       pass\n\n<\/pre><\/div>\n\n\n<p>Definiuj\u0105c w\u0142asn\u0105 klas\u0119 abstrakcyjn\u0105, musi ona dziedziczy\u0107 po klasie <em>ABC<\/em> z wbudowanego modu\u0142u <em>abc<\/em>. Metody abstrakcyjne poprzedzane s\u0105 z kolei <strong>dekoratorem <em>@abstractmethod<\/em><\/strong>. Pomimo, i\u017c istnieje mo\u017cliwo\u015b\u0107 zdefiniowania ich logiki, to powinny one pozosta\u0107 puste. Klasa abstrakcyjna jest szablonem dla innych klas, dlatego <strong>nigdy nie tworzymy jej instancji<\/strong>.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Hermetyzacja<\/strong><\/h3>\n\n\n\n<p>Hermetyzacja, zwana r\u00f3wnie\u017c enkapsulacj\u0105 (ang. <em>encapsulation<\/em>), to cecha, kt\u00f3ra pozwala odseparowa\u0107 niezale\u017cny kod poprzez ukrycie jego szczeg\u00f3\u0142\u00f3w. W przypadku tworzenia i u\u017cywania zwyk\u0142ych funkcji oznacza to, \u017ce \u017cadna zmienna zdefiniowana w ciele funkcji nie jest dost\u0119pna poza ni\u0105. To dlatego w celu przekazania interesuj\u0105cej nas warto\u015bci na zewn\u0105trz <strong>u\u017cywa si\u0119 parametr\u00f3w i warto\u015bci zwrotnych.<\/strong><\/p>\n\n\n\n<p>W OOP natomiast hermetyzacja dotyczy atrybut\u00f3w obiektu, kt\u00f3re powinny by\u0107 ukryte przed klientami, czyli przed t\u0105 cz\u0119\u015bci\u0105 programu, kt\u00f3ra tych obiekt\u00f3w u\u017cywa. Dost\u0119p do szczeg\u00f3\u0142\u00f3w obiektu jest mo\u017cliwy jedynie poprzez u\u017cycie interfejs\u00f3w, czyli zestawu metod dost\u0119powych, kt\u00f3re w kontrolowany spos\u00f3b umo\u017cliwiaj\u0105 modyfikacj\u0119 danych wewn\u0105trz obiekt\u00f3w.<\/p>\n\n\n\n<p>Innymi s\u0142owy, <strong>komunikacja z obiektami<\/strong> powinna odbywa\u0107 si\u0119, podobnie jak w przypadku zwyk\u0142ych funkcji, <strong>za pomoc\u0105 parametr\u00f3w przekazywanych do metod i zwracanych przez nie warto\u015bci<\/strong> (nale\u017cy unika\u0107 bezpo\u015bredniego wp\u0142ywu na atrybuty obiektu przez klienta). Takie podej\u015bcie pozwala zachowa\u0107 integralno\u015b\u0107 danych i ogranicza ryzyko pope\u0142nienia b\u0142\u0119du.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Przyk\u0142ad hermetyzacji<\/strong><\/h3>\n\n\n\n<p>Za przyk\u0142ad mo\u017ce pos\u0142u\u017cy\u0107 obiekt klasy Bank posiadaj\u0105cy atrybut <em>saldo<\/em> oraz metody: <em>wp\u0142a\u0107<\/em>, <em>wyp\u0142a\u0107<\/em> i <em>sprawd\u017a stan konta<\/em>. Nie chcemy, aby klient m\u00f3g\u0142 bezpo\u015brednio zmieni\u0107 saldo konta bankowego poprzez przypisanie mu dowolnej warto\u015bci, dlatego potencjalna modyfikacja jest ograniczona do metod wp\u0142a\u0107 i wyp\u0142a\u0107. To do nich przekazujemy okre\u015blon\u0105 warto\u015b\u0107 i to one odpowiadaj\u0105 za zmian\u0119 stanu konta w zale\u017cno\u015bci od tego, jak\u0105 operacj\u0119 klient chce przeprowadzi\u0107. Dzi\u0119ki temu mo\u017cemy w prosty spos\u00f3b nie dopu\u015bci\u0107 do sytuacji, gdzie stan konta b\u0119dzie posiada\u0142 np. warto\u015b\u0107 ujemn\u0105, za\u015b pr\u00f3ba wyp\u0142acenia zbyt du\u017cej ilo\u015bci got\u00f3wki zwr\u00f3ci b\u0142\u0105d.<\/p>\n\n\n\n<p>W Pythonie wszystkie metody i atrybuty obiektu s\u0105 domy\u015blnie publiczne, co powoduje, \u017ce klient mo\u017ce si\u0119 do nich bezpo\u015brednio odnosi\u0107 i je wywo\u0142ywa\u0107. W celu ograniczenia dost\u0119pu konieczne jest zastosowanie mechanizmu znanego jako \u201e<em>name mangling\u201d<\/em>. Odbywa si\u0119 to poprzez poprzedzenie nazwy metod i atrybut\u00f3w podw\u00f3jnym znakiem podkre\u015blenia.<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: python; title: ; notranslate\" title=\"\">\nclass BankAccount:\n\n   def __init__(self, account_name):\n       self.account_name = account_name    # public attribute\n       self._operations = 0                # protected attribute\n       self.__account_balance = 0          # private attribute\n\n   def deposit_money(self, amount):\n       self.__account_balance += amount\n       self._operations += 1\n       print(f&quot;You deposited ${amount}&quot;)\n      \n   def withdraw_money(self, amount):\n       if amount &gt; self.__account_balance:\n           raise ValueError(f&quot;You don&#039;t have enough money!&quot;)\n      \n       self.__account_balance -= amount\n       self._operations += 1\n       print(f&quot;You withdrew ${amount}&quot;)\n\n   def get_balance(self):\n       print(f&quot;You have ${self.__account_balance} in your account&quot;)\n\n\nif __name__ == &#039;__main__&#039;:\n   my_account = BankAccount(&quot;My private account&quot;)\n   my_account.get_balance()\n   my_account.deposit_money(1000)\n   my_account.get_balance()\n   my_account.withdraw_money(500)\n   my_account.get_balance()\n   print(&quot;Number of operations:&quot;, my_account._operations)\n   print(my_account.__account_balance)  # AttributeError\n\n# Output:\n# You have $0 in your account\n# You deposited $1000\n# You have $1000 in your account\n# You withdrew $500\n# You have $500 in your account\n# Number of operations: 2\n\n# Traceback (most recent call last):\n#   File &quot;C:\\PycharmProjects\\OOP_project\\encapsulation_2.py&quot;, line 33, in &lt;module&gt;\n#     print(my_account.__account_balance)  # AttributeError\n# AttributeError: &#039;BankAccount&#039; object has no attribute &#039;__account_balance&#039;\n<\/pre><\/div>\n\n\n<p>Atrybuty lub metody zdefiniowane za pomoc\u0105 prywatnego modyfikatora dost\u0119pu (jak wy\u017cej) s\u0105 dost\u0119pne tylko poprzez inne metody tego samego obiektu. Jest to jednak g\u0142\u00f3wnie konwencja, kt\u00f3rej \u0142amanie nie jest zalecane. Istnieje natomiast spos\u00f3b na ich bezpo\u015brednie wywo\u0142anie poza definicj\u0105 klasy, dlatego ci\u0119\u017cko tutaj m\u00f3wi\u0107 o ca\u0142kowitym zabezpieczeniu.<\/p>\n\n\n\n<p>Ponadto, mo\u017cemy spotka\u0107 si\u0119 r\u00f3wnie\u017c z elementami zdefiniowanymi z przedrostkiem w formie pojedynczego podkre\u015blenia, kt\u00f3re s\u0105 traktowane jako \u201e<em>internal use<\/em>\u201d. Ich modyfikacja jest dopuszczalna tylko w klasie, w kt\u00f3rej zosta\u0142y zadeklarowane oraz \u2013 dodatkowo \u2013 w jej klasach potomnych, jednak\u017ce Python nie blokuje bezpo\u015bredniego dost\u0119pu do nich.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Dziedziczenie<\/strong><\/h3>\n\n\n\n<p>Dziedziczenie jest kluczow\u0105 cech\u0105 programowania obiektowego, umo\u017cliwiaj\u0105c\u0105 tworzenie nowych klas na podstawie klas ju\u017c istniej\u0105cych. Jest to szczeg\u00f3lnie u\u017cyteczne w sytuacji, gdy obiekty r\u00f3\u017cni\u0105ce si\u0119 mi\u0119dzy sob\u0105 pod pewnymi wzgl\u0119dami, posiadaj\u0105 te\u017c wiele cech wsp\u00f3lnych.<\/p>\n\n\n\n<p>Dziedziczenie w OOP jest oparte na <strong>koncepcji relacji rodzic-dziecko<\/strong>. Rodzic, czyli tzw. Klasa nadrz\u0119dna (nadklasa, ang. <em>Superclass<\/em>), to klasa, kt\u00f3ra ju\u017c istnieje i posiada pewien zestaw cech (metod, atrybut\u00f3w). Z kolei dziecko (klasa podrz\u0119dna, potomna, ang. <em>Subclass<\/em>) to nowa klasa, kt\u00f3ra nie do\u015b\u0107, \u017ce przejmuje (dziedziczy) cechy klasy ju\u017c istniej\u0105cej, to dodatkowo mo\u017ce je nadpisywa\u0107 lub rozszerza\u0107 oraz posiada\u0107 w\u0142asne, unikalne cechy. <\/p>\n\n\n\n<p>Zastosowanie dziedziczenia wprowadza hierarchiczno\u015b\u0107 w naszym kodzie, a fakt, i\u017c klasa podrz\u0119dna ma dost\u0119p do wszystkich metod i atrybut\u00f3w klasy nadrz\u0119dnej, pozwala rozszerza\u0107 funkcjonalno\u015bci bez potrzeby przepisywania ich na nowo.<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: python; title: ; notranslate\" title=\"\">\nclass Mammal:\n    division = &quot;mammal&quot;\n\n    @staticmethod\n    def make_sound():\n        print(&quot;Making sound!&quot;)\n\n\nclass Dog(Mammal):\n\n    def __init__(self, name):\n        self.name = name\n\n    def play(self, toy):\n        print(f&quot;{self.name} is playing with a {toy}&quot;)\n\n\nif __name__ == &#039;__main__&#039;:\n    doggie = Dog(&quot;Pluto&quot;)\n    print(f&quot;Dogs belong to {doggie.division}s&quot;)    # usage of inherited attribute\n    doggie.play(toy=&quot;ball&quot;)     # usage of defined method\n    doggie.make_sound()         # usage of inherited method\n\n\n# Output:\n# Dogs belong to mammals\n# Pluto is playing with a ball\n# Making sound!\n<\/pre><\/div>\n\n\n<p>Powy\u017cszy kod prezentuje przypadek jedno dziedziczenia, gdzie klasa \u201edziecka\u201d posiada tylko jednego \u201erodzica\u201d. Nie jest to jednak wym\u00f3g i <strong>klasa potomna mo\u017ce dziedziczy\u0107 z wielu klas naraz<\/strong>. Bywa to problematyczne m.in.: w sytuacji, gdy obie klasy rodzicielskie posiadaj\u0105 atrybuty lub metody o identycznych nazwach, ale robi\u0105ce zupe\u0142nie co\u015b innego. O tym, kt\u00f3ra implementacja (cecha kt\u00f3rego rodzica) zostanie w takiej sytuacji odziedziczona, <strong>decyduje kolejno\u015b\u0107<\/strong> w jakiej klasy rodzicielskie zosta\u0142y wymienione w definicji klasy dziecka. Jest to tzw. <strong>zasada <em>Method Resolution Order<\/em><\/strong><em> (MRO)<\/em>, kt\u00f3ra okre\u015bla hierarchi\u0119 dziedziczenia.<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: python; title: ; notranslate\" title=\"\">\nclass Person:\n\n    @staticmethod\n    def get_info():\n        print(&quot;Info about Person...&quot;)\n\n\nclass Businessman(Person):\n\n    @staticmethod\n    def get_info():\n        print(&quot;Info about Businessman...&quot;)\n\n\nclass Sportsman(Person):\n\n    @staticmethod\n    def get_info():\n        print(&quot;Info about Sportsman...&quot;)\n\n\nclass SportsmanBusinessman(Sportsman, Businessman):\n    pass\n\n\nif __name__ == &#039;__main__&#039;:\n    obj = SportsmanBusinessman()\n    obj.get_info()\n\n    print(f&quot;Inheritance order:\\n{SportsmanBusinessman.__mro__}&quot;)\n\n\n# Output:\n# Info about Sportsman...\n# Inheritance order:\n# (&lt;class &#039;__main__.SportsmanBusinessman&#039;&gt;, &lt;class &#039;__main__.Sportsman&#039;&gt;, # &lt;class &#039;__main__.Businessman&#039;&gt;, &lt;class &#039;__main__.Person&#039;&gt;, &lt;class &#039;object&#039;&gt;)\n\n<\/pre><\/div>\n\n\n<h3 class=\"wp-block-heading\"><strong>Polimorfizm<\/strong><\/h3>\n\n\n\n<p>Polimorfizm to inaczej wielopostaciowo\u015b\u0107, a w t\u0142umaczeniu dos\u0142ownym \u201ewiele form\u201d (gr. <em>polys <\/em>\u2018wiele\u2019, <em>morfe<\/em> \u2018forma, kszta\u0142t\u2019). W kontek\u015bcie OOP jest on \u015bci\u015ble zwi\u0105zany z poj\u0119ciem abstrakcji i dziedziczenia, poniewa\u017c dopuszcza traktowanie wielu obiekt\u00f3w r\u00f3\u017cnych klas w taki sam spos\u00f3b. Oznacza to, \u017ce, maj\u0105c klas\u0119 nadrz\u0119dn\u0105 (rodzica) i kilka jej klas pochodnych (dzieci), posiadaj\u0105cych w swojej implementacji metody nazwane identycznie jak metoda klasy rodzicielskiej, wywo\u0142anie tej metody z poziomu ka\u017cdej z tych klas (oboj\u0119tnie czy rodzica, czy kt\u00f3rego\u015b z dzieci), zwr\u00f3ci nam co innego.<\/p>\n\n\n\n<p>M\u00f3wi\u0105c inaczej, <strong>polimorfizm umo\u017cliwia posiadanie wielu metod o takiej samej nazwie, ale zachowuj\u0105cych si\u0119 w inny spos\u00f3b<\/strong>. Zatem, pomimo \u017ce klasy potomne dziedzicz\u0105 jak\u0105\u015b metod\u0119, to mog\u0105 wp\u0142ywa\u0107 na jej zawarto\u015b\u0107 poprzez ca\u0142kowit\u0105 zmian\u0119 (jak wy\u017cej) lub rozszerzenie.<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: python; title: ; notranslate\" title=\"\">\nclass Mammal:\n    division = &quot;mammal&quot;\n\n    def __init__(self, name):\n        self.name = name\n\n    def make_sound(self):\n        print(f&quot;{self.name} is making a sound...&quot;)\n\n\nclass Dog(Mammal):\n\n    def __init__(self, name, breed):\n        super(Dog, self).__init__(name)     # extending inherited method\n        self.breed = breed\n\n    def make_sound(self):\n        super().make_sound()                # extending inherited method\n        print(&quot;Woof woof!&quot;)\n\n\nclass Cat(Mammal):\n\n    def make_sound(self):                   # overriding inherited method\n        print(&quot;Miau...&quot;)\n\n\n\nif __name__ == &#039;__main__&#039;:\n    doggie = Dog(&quot;Pluto&quot;, &quot;bulldog&quot;)\n    kitten = Cat(&quot;Filemon&quot;)\n\n    doggie.make_sound()\n    kitten.make_sound()\n\n\n# Output:\n# Pluto is making a sound...\n# Woof woof!\n# Miau...\n\n<\/pre><\/div>\n\n\n<h3 class=\"wp-block-heading\"><strong>Przyk\u0142ad polimorfizmu<\/strong><\/h3>\n\n\n\n<p>Powy\u017cej zdefiniowano trzy klasy: og\u00f3ln\u0105 klas\u0119 <em>Mammal<\/em>, a tak\u017ce klasy <em>Dog <\/em>i <em>Cat <\/em>dziedzicz\u0105ce po niej, przez co wszystkie one maj\u0105 dost\u0119p do tych samych metod.<\/p>\n\n\n\n<p><strong>Klasa <em>Dog<\/em><\/strong> nie tylko dziedziczy obie metody klasy nadrz\u0119dnej, ale r\u00f3wnie\u017c rozszerza ich dzia\u0142anie. Dzieje si\u0119 tak dzi\u0119ki wykorzystaniu <strong>s\u0142owa kluczowego <em>super<\/em><\/strong>, kt\u00f3re pozwala odnie\u015b\u0107 si\u0119 do klasy nadrz\u0119dnej i wywo\u0142a\u0107 zawarto\u015b\u0107 odziedziczonej metody w klasie pochodnej.<\/p>\n\n\n\n<p><strong>Klasa <em>Cat<\/em><\/strong>, podobnie jak <em>Dog<\/em>, posiada dost\u0119p do obu metod klasy <em>Mammal<\/em>, ale jej zachowanie jest zupe\u0142nie inne: brak jakiegokolwiek odniesienia do metody <em>__init__<\/em> sprawia, \u017ce pozostaje ona w niezmienionej formie. Z kolei implementacja <em>make_sound<\/em> zawiera ca\u0142kowicie inn\u0105 logik\u0119, a wi\u0119c ma miejsce przes\u0142oni\u0119cie odziedziczonej metody o tej samej nazwie.<\/p>\n\n\n\n<p>W takiej sytuacji wyra\u017anie wida\u0107, i\u017c posiadamy <strong>trzy r\u00f3\u017cne implementacje<\/strong> dla metody <em>make_sound<\/em>. Wynik, jaki otrzymujemy z jej wywo\u0142ania, zale\u017cy od typu obiektu, za pomoc\u0105 kt\u00f3rego to wywo\u0142anie nast\u0119puje (dla ka\u017cdego z obiekt\u00f3w rezultat jest inny). <strong>To jest w\u0142a\u015bnie polimorfizm<\/strong> \u2013 pomimo, i\u017c posiadamy t\u0119 sam\u0105 metod\u0119 w r\u00f3\u017cnych klasach sprz\u0119\u017conych ze sob\u0105, to jej implementacje r\u00f3\u017cni\u0105 si\u0119 od siebie.<\/p>\n\n\n\n<p>Jak ju\u017c wspomnia\u0142em, klasa <em>Cat <\/em>doprowadza do ca\u0142kowitej zmiany zachowania odziedziczonej metody <em>make_sound<\/em>. <strong>Nie jest to jednak dobra praktyka z punktu widzenia OOP<\/strong>. Dlaczego? Poniewa\u017c <strong>\u0142amie trzeci\u0105 zasad\u0119 SOLID<\/strong>, z kt\u00f3rej wynika, \u017ce g\u0142\u00f3wnym celem polimorfizmu w przypadku, gdy mamy do czynienia z dziedziczeniem metod, jest rozszerzanie istniej\u0105cej implementacji, a nie jej kompletna zmiana (przyk\u0142ad: klasa <em>Dog<\/em>).<\/p>\n\n\n\n<p>Warto wiedzie\u0107, \u017ce wielopostaciowo\u015b\u0107 mo\u017cna rozdzieli\u0107 na dwa osobne poj\u0119cia:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><em>method overriding<\/em>,<\/li>\n\n\n\n<li><em>method overloading<\/em>.<\/li>\n<\/ul>\n\n\n\n<p>Powy\u017cej opisany przypadek z klasami <em>Mammal, Dog <\/em>i<em> Cat<\/em>, zawiera przyk\u0142ad <em>method overridingu<\/em>, czyli nadpisywania\/przes\u0142aniania metody klasy nadrz\u0119dnej. <em>Method overloading<\/em> to za\u015b tzw. przeci\u0105\u017canie metod i ma ono miejsce, gdy zachowanie metod\/funkcji o tych samych nazwach jest podyktowane liczb\u0105 i typem parametr\u00f3w, jakie te metody\/funkcje przyjmuj\u0105.<\/p>\n\n\n\n<p>Jako, \u017ce j\u0119zyk <strong>Python jest j\u0119zykiem interpretowanym<\/strong> (podczas wykonywania kod jest czytany linijka po linijce przez interpreter), a tak\u017ce dynamicznie typowanym (nie deklarujemy na sztywno typu zmiennych; typ zmiennej jest okre\u015blany dopiero w momencie przypisania do niej warto\u015bci i mo\u017ce ulega\u0107 zmianie), to tradycyjny <em>overloading<\/em> tutaj nie istnieje i osi\u0105ga si\u0119 go w inny spos\u00f3b, ani\u017celi definiuj\u0105c t\u0119 sam\u0105 metod\u0119 z r\u00f3\u017cn\u0105 liczb\u0105 parametr\u00f3w czy z parametrami o innych typach. Jest to jednak temat na inny artyku\u0142 \ud83d\ude09<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Zasady SOLID<\/strong><\/h2>\n\n\n\n<p>Kolejny obowi\u0105zkowy <strong>element programowania obiektowego <\/strong>to tzw. <strong>SOLID<\/strong>. Jest to <a href=\"https:\/\/sii.pl\/blog\/solid-dobre-praktyki-programowania\/\" target=\"_blank\" aria-label=\" (opens in a new tab)\" rel=\"noreferrer noopener\" class=\"ek-link\">akronim opisuj\u0105cy podstawowe zasady programowania zorientowanego obiektowo<\/a>. Za tw\u00f3rc\u0119 i popularyzatora tego poj\u0119cia uwa\u017ca si\u0119 ameryka\u0144skiego programist\u0119 <a href=\"https:\/\/sii.pl\/blog\/author\/rcmartin\/\" target=\"_blank\" aria-label=\" (opens in a new tab)\" rel=\"noreferrer noopener\" class=\"ek-link\">Roberta C. Martina<\/a>, znanego szerzej jako \u201e<a href=\"https:\/\/helion.pl\/autorzy\/robert-c-martin\" target=\"_blank\" aria-label=\"Wujek Bob (opens in a new tab)\" rel=\"noreferrer noopener\" class=\"ek-link\" rel=\"nofollow\" >Wujek Bob<\/a>\u201d<em> (<\/em>ang.<em> Uncle Bob<\/em>).<\/p>\n\n\n\n<p>Motywacj\u0105 do sformu\u0142owania zasad dotycz\u0105cych dobrych praktyk dla programowania obiektowego by\u0142 dla niego fakt, i\u017c \u2013 jego zdaniem \u2013 du\u017ca cz\u0119\u015b\u0107 programist\u00f3w nie jest do ko\u0144ca \u015bwiadoma, dlaczego tak naprawd\u0119 u\u017cywa j\u0119zyk\u00f3w zorientowanych obiektowo i jakie p\u0142yn\u0105 z tego prawdziwe korzy\u015bci.<\/p>\n\n\n\n<p>Zasady SOLID \u015bci\u015ble dotycz\u0105 zarz\u0105dzania zale\u017cno\u015bciami (ang. <em>dependency management<\/em>) tj. relacjami pomi\u0119dzy klasami i obiektami. <strong>Z\u0142e zarz\u0105dzanie zale\u017cno\u015bciami<\/strong> powoduje, \u017ce kod jest trudny do zmiany, podatny na awarie i bardzo cz\u0119sto nie nadaje si\u0119 do ponownego u\u017cycia. St\u0105d, je\u015bli zale\u017cy nam, aby tworzone przez nas oprogramowanie by\u0142o \u0142atwiejszy do rozwijania i utrzymania, wa\u017cne jest dok\u0142adne zrozumienie i przestrzeganie owych zasad.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>\u201eS\u201d jak Samodzielny (ang. <em>Single Responsibility Principle<\/em>, <em>SRP<\/em>) \u2013 zasada pojedynczej odpowiedzialno\u015bci<\/strong><\/h3>\n\n\n\n<p>Odwo\u0142uj\u0105c si\u0119 do definicji stworzonej przez \u201eWujka Boba\u201d:<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p><\/p>\n<cite><em>Nigdy nie powinien istnie\u0107 wi\u0119cej ni\u017c jeden pow\u00f3d do istnienia klasy lub metody<\/em>, a co za tym idzie, <em>nigdy nie powinien istnie\u0107 wi\u0119cej ni\u017c jeden pow\u00f3d do zmiany<\/em>.<br>Robert C. Martin<\/cite><\/blockquote>\n\n\n\n<p>Zgodnie z powy\u017cszym \u2013 nale\u017cy trzyma\u0107 razem wszystkie elementy, kt\u00f3rych pow\u00f3d do zmiany jest taki sam. Klasa powinna odpowiada\u0107 za jedn\u0105, konkretn\u0105 rzecz. Unikamy klas, kt\u00f3re wykonuj\u0105 wiele r\u00f3\u017cnych czynno\u015bci \u2013 innymi s\u0142owy: s\u0105 do wszystkiego (czyli do niczego ;)). Takie podej\u015bcie, owszem, zwi\u0119ksza ilo\u015b\u0107 klas w naszym projekcie, ale zmniejsza ryzyko awarii w sytuacji, gdy konieczne jest wprowadzenie zmian \u2013 modyfikacja w jednym miejscu nie spowoduje awarii gdzie indziej.<\/p>\n\n\n\n<p>W skr\u00f3cie: <strong>jedna funkcjonalno\u015b\u0107 \u2192 jeden pow\u00f3d do zmian.<\/strong><\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>\u201eO\u201d jak Otwarty (ang. <em>Open-Closed Principle<\/em>, <em>OCP<\/em>) \u2013 zasada otwarty-zamkni\u0119ty<\/strong><\/h3>\n\n\n\n<p>Klasa (ale r\u00f3wnie\u017c funkcje czy modu\u0142y) powinna by\u0107 otwarta na rozszerzanie, ale zamkni\u0119ta na modyfikacje, tzn. w sytuacji, gdy wymagania biznesowe ulegn\u0105 zmianie, programista powinien mie\u0107 mo\u017cliwo\u015b\u0107 rozszerzenia zachowania klasy poprzez dodanie nowych funkcjonalno\u015bci bez konieczno\u015bci modyfikacji starego, dzia\u0142aj\u0105cego kodu. Unikamy w ten spos\u00f3b sytuacji, \u017ce pojedyncza zmiana niesie za sob\u0105 konieczno\u015b\u0107 wprowadzenia korekt w wielu uzale\u017cnionych od siebie modu\u0142ach, poniewa\u017c istniej\u0105cy kod pozosta\u0142 nietkni\u0119ty.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>\u201eL\u201d jak Liskov (ang. <em>Liskov Substitution Principle, LSP<\/em>) \u2013 zasada podstawienia Liskov<\/strong><\/h3>\n\n\n\n<p>Zasada sformu\u0142owana przez Barbar\u0119 Liskov i \u015bci\u015ble zwi\u0105zana z obiektami klasy oraz dziedziczeniem. Wp\u0142ywa na sp\u00f3jno\u015b\u0107 hierarchii wyst\u0119puj\u0105cej pomi\u0119dzy klasami w nast\u0119puj\u0105cy spos\u00f3b: wsz\u0119dzie tam, gdzie u\u017cywamy obiekt\u00f3w klasy bazowej (rodzica), powinna istnie\u0107 mo\u017cliwo\u015b\u0107 zastosowania obiekt\u00f3w klas pochodnych (dzieci) bez jakichkolwiek b\u0142\u0119d\u00f3w. M\u00f3wi\u0105c pro\u015bciej, <strong>kod korzystaj\u0105cy z obiekt\u00f3w klasy rodzicielskiej powinien dzia\u0142a\u0107 r\u00f3wnie\u017c z obiektami klas dziedzicz\u0105cych<\/strong>. Aby by\u0142o to mo\u017cliwe, klasy pochodne powinny spe\u0142nia\u0107 te same za\u0142o\u017cenia co klasa bazowa (tzw. sp\u00f3jno\u015b\u0107 interfejsu) oraz posiada\u0107 nieograniczony dost\u0119p do jej metod.<\/p>\n\n\n\n<p>\u0141atwo zauwa\u017cy\u0107 tutaj bezpo\u015brednie <strong>nawi\u0105zanie do definicji polimorfizmu<\/strong>, gdzie zosta\u0142o wspomniane, \u017ce klasa pochodna powinna rozszerza\u0107 funkcjonalno\u015b\u0107 klasy bazowej, a nie ca\u0142kowicie j\u0105 zmienia\u0107. Takie podej\u015bcie znacznie u\u0142atwia przestrzeganie zasady LSP.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>\u201eI\u201d jak Interfejs (ang. <em>Interface Segregation Principle, ISP<\/em>) \u2013 zasada segregacji interfejs\u00f3w<\/strong><\/h3>\n\n\n\n<p>Zasada ta wymusza zachowanie sp\u00f3jno\u015bci interfejs\u00f3w poprzez implementacj\u0119 tylko niezb\u0119dnych metod. Oznacza to, \u017ce tworz\u0105c interfejs, mamy obowi\u0105zek definiowa\u0107 tylko te metody, kt\u00f3re s\u0105 niezb\u0119dne z punktu widzenia konkretnego klienta (klasy korzystaj\u0105cej z tego interfejsu, tj. implementuj\u0105cej ten interfejs). Zapobiega to sytuacjom, gdzie klient implementuje metody, kt\u00f3re nie s\u0105 mu do niczego potrzebne. W ten spos\u00f3b posiadamy wiele dedykowanych interfejs\u00f3w (a nie jeden og\u00f3lny), co pozwala pozby\u0107 si\u0119 zb\u0119dnych zale\u017cno\u015bci i poprawia czytelno\u015b\u0107 kodu.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>\u201eD\u201d jak oDwr\u00f3cenie zale\u017cno\u015bci (ang. <em>Dependency Inversion Principle, DIP<\/em>) \u2013 zasada odwr\u00f3cenia zale\u017cno\u015bci<\/strong><\/h3>\n\n\n\n<p>Ostatnia, moim zdaniem <strong>najtrudniejsza do poprawnego zrozumienia, zasada<\/strong> g\u0142osi, \u017ce modu\u0142y wysokiego poziomu nie powinny zale\u017ce\u0107 od modu\u0142\u00f3w niskopoziomowych. Zale\u017cno\u015b\u0107 ta powinna by\u0107 realizowana przez abstrakcj\u0119.<\/p>\n\n\n\n<p><strong><em>Komentarz: <\/em><\/strong><em>za modu\u0142 niskopoziomowy (ang. low-level module) traktujemy klas\u0119 wykonuj\u0105c\u0105 konkretne operacje, np. wpis w bazie danych, nieposiadaj\u0105c\u0105 odwo\u0142a\u0144 do innych klas (klasa jest niezale\u017cna i swobodnie mo\u017cemy jej u\u017cywa\u0107). Modu\u0142 wysokopoziomowy (ang. high-level module), to z kolei klasa, kt\u00f3ra potrzebuje do poprawnego dzia\u0142ania modu\u0142\u00f3w ni\u017cszych poziom\u00f3w (jest zale\u017cna od konkretnej implementacji i cz\u0119sto niemo\u017cliwe jest jej u\u017cycie w innym miejscu).<\/em><\/p>\n\n\n\n<p>Jak w takim razie wprowadzi\u0107 zale\u017cno\u015b\u0107 pomi\u0119dzy modu\u0142ami, aby nie z\u0142ama\u0107 zasady DIP? Do tego celu s\u0142u\u017cy wzorzec zwany \u201e<strong>odwr\u00f3ceniem sterowania<\/strong>\u201d (ang. <em>Inversion of Control, IoC<\/em>). Jego najprostsza implementacja, tzw. wstrzykiwanie zale\u017cno\u015bci (ang. <em>dependency injection<\/em>), w pewnym sensie sprawia, \u017ce nasze modu\u0142y wysokopoziomowe same wybieraj\u0105, jakich modu\u0142\u00f3w niskiego poziomu potrzebuj\u0105. Jako \u017ce brzmi to do\u015b\u0107 skomplikowanie, we\u017amy przyk\u0142ad:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: python; title: ; notranslate\" title=\"\">\nfrom abc import ABC, abstractmethod\n\n\n# Abstract interface\nclass Figure(ABC):\n\n    @abstractmethod\n    def get_perimeter(self):\n        pass\n\n    @abstractmethod\n    def get_area(self):\n        pass\n\n\n# Low-level module, sample interface implementation\nclass Circle(Figure):\n    pi_value = 3.1416\n    name = &quot;circle&quot;\n\n    def __init__(self, radius):\n        self.radius = radius\n\n    def get_perimeter(self):\n        return 2 * Circle.pi_value * self.radius\n\n    def get_area(self):\n        return Circle.pi_value * self.radius ** 2\n\n\n# High-level module that uses abstraction instead of specific implementation\nclass FigureInfo:\n\n    def __init__(self, figure: Figure):\n        self.figure = figure\n\n    def get_figure_info(self):\n        print(f&quot;The {self.figure.name} perimeter: {self.figure.get_perimeter()}&quot;)\n        print(f&quot;The {self.figure.name} area: {self.figure.get_area()}&quot;)\n\n\nif __name__ == &#039;__main__&#039;:\n    circle = Circle(10)\n    info_circle = FigureInfo(circle)\n    info_circle.get_figure_info()\n\n\n# Output:\n# The circle perimeter: 62.832\n# The circle area: 314.159\n\n<\/pre><\/div>\n\n\n<p>Mamy tutaj abstrakcyjny interfejs <em>Figure<\/em> oraz jego implementacj\u0119 \u2013 klas\u0119 <em>Circle<\/em>, kt\u00f3ra jest naszym modu\u0142em niskiego poziomu. Nast\u0119pnie definiujemy modu\u0142 wysokopoziomowy <em>FigureInfo<\/em>, w kt\u00f3rym chcemy wykorzysta\u0107 istniej\u0105c\u0105 ju\u017c implementacj\u0119.<\/p>\n\n\n\n<p>Rozwi\u0105zaniem, kt\u00f3re jako pierwsze przychodzi nam do g\u0142owy w celu zrealizowania tego za\u0142o\u017cenia, jest ch\u0119\u0107 zdefiniowania instancji klasy <em>Circle<\/em> wewn\u0105trz klasy <em>FigureInfo<\/em>. <strong>Nie jest to jednak dobre podej\u015bcie<\/strong>, poniewa\u017c uzale\u017cniamy w ten spos\u00f3b modu\u0142 wysokiego poziomu od modu\u0142u niskopoziomowego, a zatem \u0142amiemy zasad\u0119 DIP. <\/p>\n\n\n\n<p><strong>Poprawnym rozwi\u0105zaniem<\/strong> jest w\u0142a\u015bnie <em>wstrzykiwanie zale\u017cno\u015bci<\/em>, co zaprezentowa\u0142em powy\u017cej. Ma ono miejsce poprzez utworzenie instancji klasy <em>Circle<\/em> poza cia\u0142em klasy <em>FigureInfo<\/em>, a nast\u0119pnie przekazanie jej do klasy <em>FigureInfo<\/em> jako parametr. Typ tego parametru jest ponadto okre\u015blony jako <em>Figure<\/em>, a wi\u0119c odnosi si\u0119 do og\u00f3lnej klasy abstrakcyjnej, a nie konkretnej implementacji. Dzi\u0119ki temu nasz modu\u0142 wysokopoziomowy jest uzale\u017cniony od abstrakcji i tworz\u0105c jego kolejne instancje, mo\u017cemy przekazywa\u0107 do niego nie tylko obiekty klasy <em>Circle<\/em>, ale dowolny podtyp klasy abstrakcyjnej.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Programowanie obiektowe \u2013 dlaczego warto?<\/strong><\/h2>\n\n\n\n<p>Tworzenie oprogramowania z u\u017cyciem obiektowo\u015bci jest do\u015b\u0107 intuicyjne, a definicja obiektu, czyli tzw. <em>klasa,<\/em> zawiera wszystkie jego w\u0142a\u015bciwo\u015bci w jednym miejscu. Niesie to za sob\u0105 <strong>szereg zalet<\/strong>.<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Jedn\u0105 z podstawowych jest fakt, \u017ce nasz <strong>kod staje<\/strong> <strong>bardziej zorganizowany<\/strong> i \u0142atwiejszy do zrozumienia. Ka\u017cdy programista mo\u017ce pracowa\u0107 nad oddzielnymi klasami bez ryzyka wyst\u0105pienia konflikt\u00f3w w kodzie. Przek\u0142ada si\u0119 to na jego p\u00f3\u017aniejsze utrzymanie i wprowadzanie ewentualnych zmian przez zesp\u00f3\u0142 deweloperski. Ma to niebagatelny wp\u0142yw na proces rozwoju oprogramowania i jako\u015b\u0107 ko\u0144cowego produktu.<\/li>\n\n\n\n<li>Kolejn\u0105 zalet\u0105 jest<strong> reu\u017cywalno\u015b\u0107 kodu<\/strong>. Dzi\u0119ki klasom i obiektom mo\u017cemy tworzy\u0107 mniejsze, niezale\u017cne modu\u0142y, kt\u00f3re p\u00f3\u017aniej \u0142atwo przenie\u015b\u0107 do nowego projektu bez potrzeby pisania ca\u0142ego kodu od zera.<\/li>\n\n\n\n<li>Dzi\u0119ki zastosowaniu hermetyzacji <strong>wzrasta bezpiecze\u0144stwo tworzonego przez nas kodu<\/strong>, gdy\u017c u\u017cytkownik zewn\u0119trzny mo\u017ce korzysta\u0107 jedynie z okre\u015blonego zestawu narz\u0119dzi (atrybut\u00f3w, metod). Wszelkie dane \u201ewra\u017cliwe\u201d s\u0105 przed nim ukryte i dost\u0119pne tylko wewn\u0105trz klasy, co znacz\u0105co zmniejsza ryzyko wp\u0142ywania na obiekt w niepo\u017c\u0105dany spos\u00f3b.<\/li>\n\n\n\n<li>Stosowanie klas, a wraz za tym dziedziczenia, pozwala na <strong>wprowadzenie hierarchii w budowanym kodzie<\/strong>. Umo\u017cliwia to ponowne u\u017cycie istniej\u0105cych ju\u017c struktur oraz definiowanie bardziej wyspecjalizowanych obiekt\u00f3w w oparciu o te ju\u017c istniej\u0105ce (bardziej og\u00f3lne). Zwi\u0119ksza to elastyczno\u015b\u0107 budowanego kodu oraz obni\u017ca ryzyko redundancji (powtarzania tych samych operacji), dzi\u0119ki czemu mo\u017cemy sprawniej przestrzega\u0107 zasady DRY (ang. <em>Don\u2019t Repeat Yourself<\/em>).<\/li>\n\n\n\n<li>Programowanie obiektowe to r\u00f3wnie\u017c polimorfizm, z kt\u00f3rego wynika <strong>perspektywa rozszerzania funkcjonalno\u015bci programu<\/strong> poprzez u\u017cywanie tych samych nazw metod w r\u00f3\u017cnych klasach. Jest to zabieg, kt\u00f3ry pozytywnie wp\u0142ywa na sp\u00f3jno\u015b\u0107 i intuicyjno\u015b\u0107 tworzonego przez nas interfejsu.<\/li>\n<\/ul>\n\n\n\n<figure class=\"wp-block-image aligncenter size-full\"><a href=\"https:\/\/sii.pl\/oferty-pracy\/\" target=\"_blank\" rel=\"noreferrer noopener\"><img decoding=\"async\" width=\"737\" height=\"170\" src=\"https:\/\/sii.pl\/blog\/wp-content\/uploads\/2024\/12\/praca-m-14.jpg\" alt=\"oferty pracy\" class=\"wp-image-29782\" srcset=\"https:\/\/sii.pl\/blog\/wp-content\/uploads\/2024\/12\/praca-m-14.jpg 737w, https:\/\/sii.pl\/blog\/wp-content\/uploads\/2024\/12\/praca-m-14-300x69.jpg 300w\" sizes=\"(max-width: 737px) 100vw, 737px\" \/><\/a><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Podsumowanie<\/strong><\/h2>\n\n\n\n<p>Programowanie obiektowe nie jest proste i cho\u0107 samo definiowanie klas czy metod nie jest jako\u015b mocno skomplikowane, to <strong>sporo tu niuans\u00f3w i regu\u0142, kt\u00f3rych trzeba przestrzega\u0107<\/strong>. Szczeg\u00f3lnie na pocz\u0105tku nauki mo\u017ce nam si\u0119 wydawa\u0107, \u017ce im bardziej zg\u0142\u0119biamy temat, tym bardziej zaczyna on by\u0107 dla nas zawi\u0142y i \u0142atwo si\u0119 w nim pogubi\u0107.<\/p>\n\n\n\n<p>Z czasem okazuje si\u0119 jednak, \u017ce wszystko razem tworzy <strong>sp\u00f3jn\u0105 ca\u0142o\u015b\u0107<\/strong> i \u2013 koniec ko\u0144c\u00f3w \u2013 <strong>u\u0142atwia prac\u0119,<\/strong> dlatego ci\u0119\u017cko sobie dzisiaj wyobrazi\u0107 programowanie bez obiektowo\u015bci. Potrzeba jednak sporo praktyki, aby dobrze zrozumie\u0107 programowanie obiektowe oraz nauczy\u0107 si\u0119 je poprawnie u\u017cywa\u0107. No i najwa\u017cniejsze, na pewno <strong>nie nale\u017cy si\u0119 zniech\u0119ca\u0107 pomimo pierwszych niepowodze\u0144 \ud83d\ude09<\/strong><\/p>\n\n\n\n<p>***<\/p>\n\n\n\n<p>Artyku\u0142 zosta\u0142 opublikowany po raz pierwszy 10.11.2023<\/p>\n\n\n<div class=\"kk-star-ratings kksr-auto kksr-align-left kksr-valign-bottom\"\n    data-payload='{&quot;align&quot;:&quot;left&quot;,&quot;id&quot;:&quot;25396&quot;,&quot;slug&quot;:&quot;default&quot;,&quot;valign&quot;:&quot;bottom&quot;,&quot;ignore&quot;:&quot;&quot;,&quot;reference&quot;:&quot;auto&quot;,&quot;class&quot;:&quot;&quot;,&quot;count&quot;:&quot;10&quot;,&quot;legendonly&quot;:&quot;&quot;,&quot;readonly&quot;:&quot;&quot;,&quot;score&quot;:&quot;5&quot;,&quot;starsonly&quot;:&quot;&quot;,&quot;best&quot;:&quot;5&quot;,&quot;gap&quot;:&quot;11&quot;,&quot;greet&quot;:&quot;&quot;,&quot;legend&quot;:&quot;5\\\/5 ( votes: 10)&quot;,&quot;size&quot;:&quot;18&quot;,&quot;title&quot;:&quot;Programowanie obiektowe na przyk\u0142adzie Pythona&quot;,&quot;width&quot;:&quot;139.5&quot;,&quot;_legend&quot;:&quot;{score}\\\/{best} ( {votes}: {count})&quot;,&quot;font_factor&quot;:&quot;1.25&quot;}'>\n            \n<div class=\"kksr-stars\">\n    \n<div class=\"kksr-stars-inactive\">\n            <div class=\"kksr-star\" data-star=\"1\" style=\"padding-right: 11px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 18px; height: 18px;\"><\/div>\n        <\/div>\n            <div class=\"kksr-star\" data-star=\"2\" style=\"padding-right: 11px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 18px; height: 18px;\"><\/div>\n        <\/div>\n            <div class=\"kksr-star\" data-star=\"3\" style=\"padding-right: 11px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 18px; height: 18px;\"><\/div>\n        <\/div>\n            <div class=\"kksr-star\" data-star=\"4\" style=\"padding-right: 11px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 18px; height: 18px;\"><\/div>\n        <\/div>\n            <div class=\"kksr-star\" data-star=\"5\" style=\"padding-right: 11px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 18px; height: 18px;\"><\/div>\n        <\/div>\n    <\/div>\n    \n<div class=\"kksr-stars-active\" style=\"width: 139.5px;\">\n            <div class=\"kksr-star\" style=\"padding-right: 11px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 18px; height: 18px;\"><\/div>\n        <\/div>\n            <div class=\"kksr-star\" style=\"padding-right: 11px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 18px; height: 18px;\"><\/div>\n        <\/div>\n            <div class=\"kksr-star\" style=\"padding-right: 11px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 18px; height: 18px;\"><\/div>\n        <\/div>\n            <div class=\"kksr-star\" style=\"padding-right: 11px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 18px; height: 18px;\"><\/div>\n        <\/div>\n            <div class=\"kksr-star\" style=\"padding-right: 11px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 18px; height: 18px;\"><\/div>\n        <\/div>\n    <\/div>\n<\/div>\n                \n\n<div class=\"kksr-legend\" style=\"font-size: 14.4px;\">\n            5\/5 ( votes: 10)    <\/div>\n    <\/div>\n","protected":false},"excerpt":{"rendered":"<p>Zaczynaj\u0105c swoj\u0105 przygod\u0119 z programowaniem, w zdecydowanej wi\u0119kszo\u015bci przypadk\u00f3w kieruje nami ch\u0119\u0107 zmuszenia maszyny (najcz\u0119\u015bciej komputera), aby co\u015b dla nas &hellip; <a class=\"continued-btn\" href=\"https:\/\/sii.pl\/blog\/programowanie-obiektowe-na-przykladzie-pythona\/\">Continued<\/a><\/p>\n","protected":false},"author":582,"featured_media":32301,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"_editorskit_title_hidden":false,"_editorskit_reading_time":0,"_editorskit_is_block_options_detached":false,"_editorskit_block_options_position":"{}","inline_featured_image":false,"footnotes":""},"categories":[1314],"tags":[1675,1528,584,286],"class_list":["post-25396","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-development-na-twardo","tag-moim-zdaniem","tag-jezyk-programowania","tag-python","tag-solid"],"acf":[],"aioseo_notices":[],"republish_history":[],"featured_media_url":"https:\/\/sii.pl\/blog\/wp-content\/uploads\/2023\/11\/Computer_2.jpg","category_names":["Development na twardo"],"_links":{"self":[{"href":"https:\/\/sii.pl\/blog\/wp-json\/wp\/v2\/posts\/25396"}],"collection":[{"href":"https:\/\/sii.pl\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/sii.pl\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/sii.pl\/blog\/wp-json\/wp\/v2\/users\/582"}],"replies":[{"embeddable":true,"href":"https:\/\/sii.pl\/blog\/wp-json\/wp\/v2\/comments?post=25396"}],"version-history":[{"count":3,"href":"https:\/\/sii.pl\/blog\/wp-json\/wp\/v2\/posts\/25396\/revisions"}],"predecessor-version":[{"id":32329,"href":"https:\/\/sii.pl\/blog\/wp-json\/wp\/v2\/posts\/25396\/revisions\/32329"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/sii.pl\/blog\/wp-json\/wp\/v2\/media\/32301"}],"wp:attachment":[{"href":"https:\/\/sii.pl\/blog\/wp-json\/wp\/v2\/media?parent=25396"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/sii.pl\/blog\/wp-json\/wp\/v2\/categories?post=25396"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/sii.pl\/blog\/wp-json\/wp\/v2\/tags?post=25396"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}