Programowanie obiektowe to sposób pisania kodu, w którym dane i działania na nich trzymasz razem w obiektach. Warto je zrozumieć, bo opiera się na nim większość kodu w Pythonie, Javie, C# i JavaScripcie, który przeczytasz w pracy. Przykładem w całym tekście jest koszyk sklepu internetowego, napisany równolegle w Pythonie i JavaScripcie, a każdy fragment kodu uruchomiliśmy (Python 3.14, Node.js 22) i podajemy, co wypisał.
Programowanie obiektowe: co to jest
Programowanie obiektowe (OOP, od object-oriented programming) to paradygmat programowania, czyli styl budowania programu, w którym podstawową cegiełką jest obiekt. Dokumentacja Javy od Oracle definiuje obiekt jako “pakiet powiązanego stanu i zachowania”. Stan to dane, na przykład lista produktów w koszyku. Zachowanie to metody, czyli funkcje przypisane do obiektu, na przykład “dodaj produkt” albo “policz sumę”.
Klasa to przepis, z którego powstają obiekty. Oracle nazywa ją planem albo prototypem. Klasa Cart opisuje, jak działa każdy koszyk, a konkretny koszyk konkretnego klienta jest obiektem, czyli instancją tej klasy. Z jednej klasy możesz utworzyć tysiąc koszyków i każdy będzie miał własną zawartość.
W praktyce zmienia to sposób myślenia o kodzie. Zamiast pisać funkcję, która dostaje listę produktów i coś z nią robi, mówisz koszykowi: “policz swoją sumę”. To koszyk wie, jak jego dane są ułożone, a reszta programu nie musi tego wiedzieć.
Klasa i obiekt na przykładzie koszyka w sklepie
Zaczynamy od dwóch klas: produktu i koszyka. Produkt ma nazwę i cenę. Koszyk przechowuje pozycje i umie policzyć sumę.
class Product:
def __init__(self, name, price):
self.name = name
self.price = price
class Cart:
def __init__(self):
self._items = []
def add(self, product, quantity=1):
self._items.append((product, quantity))
def total(self):
return sum(p.price * q for p, q in self._items)
cart = Cart()
cart.add(Product("Książka", 50), 2)
cart.add(Product("Kubek", 30))
print(cart.total()) # 130
To samo w JavaScripcie:
class Product {
constructor(name, price) {
this.name = name;
this.price = price;
}
}
class Cart {
#items = [];
add(product, quantity = 1) {
this.#items.push({ product, quantity });
}
total() {
return this.#items.reduce(
(sum, item) => sum + item.product.price * item.quantity, 0);
}
}
const cart = new Cart();
cart.add(new Product("Książka", 50), 2);
cart.add(new Product("Kubek", 30));
console.log(cart.total()); // 130
__init__ w Pythonie i constructor w JavaScripcie to metoda, która ustawia stan nowego obiektu w chwili jego tworzenia. self i this wskazują na obiekt, na którym metoda właśnie działa. Oba programy wypisują 130, czyli dwie książki po 50 zł i kubek za 30 zł.
Frontend Master 2026 · Kodożercy
Od pierwszej linijki kodu do deploy
Frontend Master 2026 to pakiet sześciu kursów Kodożerców z HTML, CSS, JavaScriptu i Git. Polskie wideo, prawdziwe projekty, fundamenty, dzięki którym przestajesz trafiać w ciemno.
Sprawdź pakiet →

Cztery filary programowania obiektowego
Na kursach i rozmowach rekrutacyjnych programowanie obiektowe opisuje się czterema filarami: hermetyzacją, abstrakcją, dziedziczeniem i polimorfizmem. To konwencja dydaktyczna, która dobrze porządkuje temat.
Hermetyzacja: koszyk pilnuje własnych danych
Hermetyzacja oznacza, że obiekt sam pilnuje swoich danych i udostępnia na zewnątrz tylko metody, którymi wolno je zmieniać. Bez niej ktoś może dopisać do listy pozycję z ilością -3 i suma koszyka zrobi się mniejsza od zera. Dlatego dokładamy sprawdzenie do metody add:
def add(self, product, quantity=1):
if quantity < 1:
raise ValueError("Ilość musi być większa od zera")
self._items.append((product, quantity))
Próba cart.add(Product("Kubek", 30), -3) w Pythonie albo cart.add(new Product("Kubek", 30), -3) w JavaScripcie kończy się komunikatem Ilość musi być większa od zera. W JavaScripcie sprawdzenie wygląda tak samo: if (quantity < 1) throw new Error("Ilość musi być większa od zera");.
Tu języki się różnią. W JavaScripcie pole z #, takie jak #items, jest naprawdę prywatne: console.log(cart.items) wypisuje undefined, a odwołanie do cart.#items spoza klasy to błąd składni. W Pythonie, jak pisze oficjalny samouczek, prywatnych zmiennych dostępnych tylko z wnętrza obiektu nie ma. Podkreślenie w _items to umowa między programistami: “nie ruszaj tego z zewnątrz”. Technicznie cart._items da się odczytać.
Abstrakcja: wiesz, co robi obiekt, nie musisz wiedzieć jak
Abstrakcja to ukrycie szczegółów za prostym poleceniem. Kod sklepu woła cart.total() i nie wie, czy koszyk liczy sumę pętlą, czy pobiera ceny z bazy. Dzięki temu sposób liczenia można zmienić w jednym miejscu.
Abstrakcja przydaje się też przy rabatach. Definiujemy ogólne pojęcie “rabat”, które obiecuje tylko jedno: metodę apply, zwracającą kwotę po rabacie. W Pythonie robi się to klasą abstrakcyjną z modułu abc:
from abc import ABC, abstractmethod
class Discount(ABC):
@abstractmethod
def apply(self, amount):
"""Zwraca kwotę po rabacie."""
Z klasy abstrakcyjnej nie da się utworzyć obiektu. Discount() w Pythonie 3.14 kończy się błędem: Can't instantiate abstract class Discount without an implementation for abstract method 'apply'. JavaScript nie ma klas abstrakcyjnych, więc najprostszy odpowiednik to metoda, która rzuca błąd, dopóki klasa potomna jej nie nadpisze.
Dziedziczenie: dwa rodzaje rabatu
Dziedziczenie pozwala utworzyć klasę na bazie innej i przejąć jej zachowanie. Tworzymy dwa konkretne rabaty: procentowy i kwotowy. Oba “są rabatami”, więc dziedziczą po Discount.
class PercentDiscount(Discount):
def __init__(self, percent):
self.percent = percent
def apply(self, amount):
return amount * (100 - self.percent) / 100
class FixedDiscount(Discount):
def __init__(self, value):
self.value = value
def apply(self, amount):
return max(amount - self.value, 0)
W JavaScripcie to samo zapisuje się słowem extends:
class Discount {
apply(amount) {
throw new Error("Każdy rabat musi mieć własną metodę apply()");
}
}
class PercentDiscount extends Discount {
constructor(percent) {
super();
this.percent = percent;
}
apply(amount) {
return (amount * (100 - this.percent)) / 100;
}
}
super() w konstruktorze uruchamia konstruktor klasy bazowej. W JavaScripcie trzeba go wywołać, zanim użyjesz this. FixedDiscount w JavaScripcie wygląda analogicznie.
Polimorfizm: jeden koszyk, różne rabaty
Polimorfizm oznacza, że różne obiekty odpowiadają na to samo polecenie, każdy na swój sposób. Koszyk woła discount.apply(...) i nie sprawdza, jaki to rabat:
for d in [PercentDiscount(10), FixedDiscount(20)]:
print(type(d).__name__, d.apply(100))
Wynik w Pythonie to PercentDiscount 90.0 i FixedDiscount 80. W JavaScripcie pętla po [new PercentDiscount(10), new FixedDiscount(20)] wypisuje PercentDiscount 90 i FixedDiscount 80. Gdy jutro dojdzie rabat “druga sztuka za pół ceny”, koszyka nie trzeba zmieniać. Wystarczy nowa klasa z metodą apply.
JavaScript: klasy to składnia nad prototypami
W JavaScripcie słowo class nie wprowadza nowego mechanizmu dziedziczenia. Według MDN klasy w JS są zbudowane na prototypach i ukrywają większość tego mechanizmu, ale “nie wnoszą nowego wzorca dziedziczenia”. Każdy obiekt ma łącze do prototypu, a gdy szukasz właściwości, JavaScript sprawdza obiekt, potem jego prototyp, potem prototyp prototypu. To jest łańcuch prototypów.
Widać to w trzech linijkach:
console.log(typeof Cart); // "function"
console.log(Object.getPrototypeOf(cart) === Cart.prototype); // true
console.log(
Object.getPrototypeOf(PercentDiscount.prototype) === Discount.prototype
); // true
Klasa jest funkcją, obiekt cart ma za prototyp Cart.prototype, a extends po prostu łączy prototypy w łańcuch. W codziennej pracy wystarczy składnia class. Wiedza o prototypach przydaje się przy debugowaniu, czytaniu starszego kodu i na rozmowie rekrutacyjnej, gdzie to pytanie pada często. Obiekty jako strukturę danych opisujemy osobno w tekście o zmiennych, obiektach i tablicach w JavaScript.
Kompozycja zamiast dziedziczenia
Kompozycja polega na tym, że obiekt zawiera inne obiekty i deleguje im pracę, zamiast po nich dziedziczyć. Nasz koszyk nie dziedziczy po rabacie. Koszyk ma rabat:
class Cart:
def __init__(self, discount=None):
self._items = []
self.discount = discount
def add(self, product, quantity=1):
if quantity < 1:
raise ValueError("Ilość musi być większa od zera")
self._items.append((product, quantity))
def total(self):
subtotal = sum(p.price * q for p, q in self._items)
if self.discount:
return self.discount.apply(subtotal)
return subtotal
cart = Cart()
cart.add(Product("Książka", 50), 2)
cart.add(Product("Kubek", 30))
print(cart.total()) # 130
cart.discount = PercentDiscount(10)
print(cart.total()) # 117.0
cart.discount = FixedDiscount(200)
print(cart.total()) # 0
Klasy Product, PercentDiscount i FixedDiscount są te same co wyżej. Rabat da się wymienić w trakcie działania programu, a koszyk nie musi wiedzieć, jaki to rabat.
Wersja z dziedziczeniem wyglądałaby tak: CartWithPercentDiscount, CartWithFixedDiscount, potem VipCartWithPercentDiscount. Każda nowa cecha mnoży klasy. Zasadę, by przedkładać kompozycję nad dziedziczenie, spopularyzowała książka Design Patterns z 1994 roku i nadal jest to dobra domyślna odpowiedź. Dziedziczenie zostaw dla relacji “X jest rodzajem Y”, tak jak rabat procentowy jest rodzajem rabatu. Dalszym krokiem są wzorce projektowe i zasady SOLID, które w dużej części rozwijają te same pomysły.
Kiedy programowanie obiektowe nie jest potrzebne
Obiektowość opłaca się wtedy, gdy masz stan, który żyje dłużej niż jedno wywołanie funkcji, i reguły, które ten stan chronią. Gdy potrzebujesz tylko policzyć sumę z listy, wystarczy funkcja:
def cart_total(items, percent_discount=0):
subtotal = sum(price * quantity for price, quantity in items)
return subtotal * (100 - percent_discount) / 100
print(cart_total([(50, 2), (30, 1)], percent_discount=10)) # 117.0
Ten sam wynik, trzy linijki i zero klas. Skrypt, który przetwarza plik CSV i kończy pracę, rzadko potrzebuje hierarchii obiektów.
Dobrym przykładem z frontendu jest React. Dokumentacja Reacta zaleca dziś definiowanie komponentów jako funkcji, a nie klas. Komponenty klasowe nadal działają, ale React nie zaleca ich w nowym kodzie. Poza Reactem obiektowość ma się dobrze: Python, Java i C# są zbudowane wokół obiektów, a biblioteki, z których korzystasz, i tak dają ci obiekty do używania.
Kod obiektowy pisany z AI: jak rozpoznać klasy na wyrost
Modele AI chętnie produkują kod obiektowy “na zapas”: klasę bazową z jedną klasą potomną, interfejs z jedną implementacją, fabrykę, która tworzy jeden rodzaj obiektu. Taki kod wygląda profesjonalnie, ale jest trudniejszy do czytania niż funkcja, która robi to samo.
Trzy pytania pomagają ocenić, czy klasa jest potrzebna. Pierwsze: czy obiekt ma stan, który trzeba chronić? Drugie: czy istnieje więcej niż jedna implementacja tej samej roli, jak u nas dwa rabaty? Trzecie: czy zastąpienie klasy funkcją coś psuje? Jeśli na wszystkie trzy odpowiedź brzmi “nie”, poproś model o prostszą wersję. Szerzej o tym, dlaczego warto rozumieć kod, który generuje AI, pisaliśmy w eseju po co Python, skoro AI pisze kod.
FAQ
Jakie są paradygmaty programowania obiektowego?
Ściśle biorąc, paradygmatem jest samo OOP, a to pytanie zwykle dotyczy jego czterech filarów: hermetyzacji, abstrakcji, dziedziczenia i polimorfizmu. Hermetyzacja chroni dane obiektu. Abstrakcja ukrywa szczegóły za prostymi metodami. Dziedziczenie pozwala budować klasy na bazie innych, a polimorfizm sprawia, że różne obiekty odpowiadają na to samo polecenie po swojemu.
Czym różni się klasa od obiektu?
Klasa to przepis, a obiekt to rzecz zrobiona według tego przepisu. Klasa Cart opisuje, jak działa każdy koszyk. Obiekt cart to konkretny koszyk z konkretnymi produktami.
Czy Python jest językiem obiektowym?
Tak, Python obsługuje klasy, dziedziczenie, także wielokrotne, i polimorfizm. Nie wymusza jednak pisania obiektowego: możesz pisać same funkcje. Nie ma też prywatności wymuszanej przez język, tylko konwencję z podkreśleniem w nazwie.
Od którego języka zacząć naukę programowania obiektowego?
Python ma najmniej składni wokół klas, więc pozwala skupić się na pomysłach. Java jest mocniej oparta na klasach i dobrze uczy dyscypliny typowej dla większych aplikacji. Porównanie obu języków pod kątem pierwszej pracy znajdziesz w tekście Python vs Java.
Podsumowanie
Programowanie obiektowe łączy dane i operacje na nich w obiektach tworzonych z klas. Cztery filary to hermetyzacja, abstrakcja, dziedziczenie i polimorfizm. Na przykładzie koszyka hermetyzacja blokuje ujemną ilość, abstrakcja ukrywa liczenie sumy za total(), dziedziczenie daje dwa rodzaje rabatu, a polimorfizm pozwala koszykowi używać każdego z nich tak samo. W Pythonie prywatność to konwencja z podkreśleniem, w JavaScripcie pole z # jest naprawdę prywatne. Klasy w JavaScripcie są składnią nad prototypami. Domyślnie wybieraj kompozycję zamiast dziedziczenia, a tam, gdzie nie ma stanu do ochrony, zwykła funkcja wystarczy. Klasy wprowadzaj wtedy, gdy porządkują realne reguły, jak u nas zakaz ujemnej ilości i wymienne rabaty. To samo kryterium pomaga ocenić kod obiektowy wygenerowany przez AI.
Newsletter · DevstockAcademy & Kodożercy
Bądź na bieżąco ze światem IT, AI i automatyzacji
Co wtorek: newsy z branży, praktyczne tipy i narzędzia które warto znać. Zero spamu.



