
Java, ako objektovo orientovaný programovací jazyk, ponúka rôzne mechanizmy na dosiahnutie flexibility, znovupoužiteľnosti kódu a modulárnosti. Medzi tieto mechanizmy patrí dedenie z tried a implementácia rozhraní (interfaces). Hoci oba prístupy umožňujú triedam preberať vlastnosti a správanie, existujú medzi nimi zásadné rozdiely, ktoré ovplyvňujú návrh a architektúru aplikácií. Tento článok sa zameriava na preskúmanie týchto rozdielov a na to, kedy je vhodné použiť jeden prístup namiesto druhého.
V objektovo orientovanom programovaní je dedenie mechanizmus, prostredníctvom ktorého trieda (podtrieda alebo odvodená trieda) preberá vlastnosti a správanie inej triedy (nadtrieda alebo základná trieda). Rozhrania, na druhej strane, definujú zmluvu, ktorú musia triedy implementovať. Táto zmluva určuje, aké metódy musí trieda obsahovať.
V Jave je dedenie realizované pomocou kľúčového slova extends. Trieda môže dediť len od jednej triedy (single inheritance). To znamená, že trieda môže mať len jedného priameho predka. Dedenie umožňuje podtriede preberať všetky verejné a chránené (protected) členy nadtriedy (atribúty a metódy). Podtrieda môže pridať nové členy alebo predefinovať (override) existujúce metódy nadtriedy.
Rozhrania sú definované pomocou kľúčového slova interface. Rozhranie definuje množinu metód, ktoré musia byť implementované triedou, ktorá rozhranie implementuje. Trieda môže implementovať viacero rozhraní (multiple inheritance of type). Implementácia rozhrania je realizovaná pomocou kľúčového slova implements. V Jave 8 boli do rozhraní pridané defaultné metódy, čo umožnilo definovať metódy s implementáciou priamo v rozhraní.
Hlavné rozdiely medzi dedením z abstraktnej triedy a implementáciou rozhrania spočívajú v ich účele, možnostiach a obmedzeniach.
Prečítajte si tiež: Ako dediť výsluhový dôchodok?
Pes "je" druh Cicavec, ktorý "je" druh Zviera.Auto "má schopnosť" byť Ovládateľné.Voľba medzi dedením a rozhraniami závisí od konkrétnej situácie a požiadaviek aplikácie.
Dedenie je vhodné použiť, ak:
Príklad:
abstract class Zviera { private String meno; public Zviera(String meno) { this.meno = meno; } public abstract void vydajZvuk(); public String getMeno() { return meno; }}class Pes extends Zviera { public Pes(String meno) { super(meno); } @Override public void vydajZvuk() { System.out.println("Haf!"); }}Rozhrania sú vhodné použiť, ak:
Príklad:
Prečítajte si tiež: Nároky na dedičstvo po manželovi
interface Ovladatelne { void riad(); void zastav();}class Auto implements Ovladatelne { @Override public void riad() { System.out.println("Auto ide."); } @Override public void zastav() { System.out.println("Auto zastavilo."); }}V praxi sa často kombinuje dedenie a rozhrania. Trieda môže dediť od jednej triedy a zároveň implementovať viacero rozhraní. To umožňuje využiť výhody oboch prístupov.
Príklad:
interface Plavec { void plav();}class Obojzivelnik extends Zviera implements Plavec { public Obojzivelnik(String meno) { super(meno); } @Override public void vydajZvuk() { System.out.println("Kvak!"); } @Override public void plav() { System.out.println("Obojživelník pláva."); }}Od Javy 8 je možné definovať defaultné metódy v rozhraniach. Defaultná metóda má implementáciu priamo v rozhraní. To umožňuje pridať nové metódy do rozhrania bez toho, aby sa museli upravovať všetky triedy, ktoré toto rozhranie implementujú.
Príklad:
interface MotoroveVozidlo { void startMotor(); void stopMotor(); default void vypisInformacie() { System.out.println("Motorové vozidlo."); }}class Motorka implements MotoroveVozidlo { @Override public void startMotor() { System.out.println("Motorka štartuje motor."); } @Override public void stopMotor() { System.out.println("Motorka zastavuje motor."); }}V tomto príklade trieda Motorka nemusí implementovať metódu vypisInformacie(), pretože má defaultnú implementáciu v rozhraní MotoroveVozidlo.
Prečítajte si tiež: Podmienky dedenia DDS
Abstraktné triedy sú triedy, ktoré nemôžu byť inštanciované. Sú určené na to, aby boli podtriedami. Abstraktné triedy môžu obsahovať abstraktné metódy (metódy bez implementácie) a konkrétne metódy (metódy s implementáciou).
Príklad:
abstract class GrafickyObjekt { private int x, y; public GrafickyObjekt(int x, int y) { this.x = x; this.y = y; } public abstract void nakresli(); public void presun(int dx, int dy) { this.x += dx; this.y += dy; } public int getX() { return x; } public int getY() { return y; }}class Kruh extends GrafickyObjekt { private int radius; public Kruh(int x, int y, int radius) { super(x, y); this.radius = radius; } @Override public void nakresli() { System.out.println("Kreslím kruh s polomerom " + radius + " na pozícii (" + getX() + ", " + getY() + ")"); }}V tomto príklade trieda GrafickyObjekt je abstraktná trieda. Nemôžeme vytvoriť inštanciu triedy GrafickyObjekt. Musíme vytvoriť podtriedu, ako napríklad Kruh, a implementovať abstraktnú metódu nakresli().
Java Reflection API umožňuje skúmať a upravovať správanie aplikácií za behu. To zahŕňa prácu s triedami, metódami, atribútmi a konštruktormi. Reflection umožňuje dynamicky zisťovať informácie o triedach, vytvárať inštancie objektov, spúšťať metódy a pristupovať k atribútom.
Pomocou Reflection môžeme získať informácie o triede, ako napríklad jej názov, modifikátory, nadtriedu a implementované rozhrania.
Class<?> clazz = String.class;System.out.println("Názov triedy: " + clazz.getName());System.out.println("Modifikátory: " + clazz.getModifiers());System.out.println("Nadtrieda: " + clazz.getSuperclass());Pomocou Reflection môžeme získať informácie o metódach triedy, ako napríklad ich názov, návratový typ, parametre a modifikátory. Môžeme tiež dynamicky spúšťať metódy.
Class<?> clazz = String.class;Method[] methods = clazz.getMethods();for (Method method : methods) { System.out.println("Názov metódy: " + method.getName()); System.out.println("Návratový typ: " + method.getReturnType());}Pomocou Reflection môžeme získať informácie o atribútoch triedy, ako napríklad ich názov, typ a modifikátory. Môžeme tiež dynamicky pristupovať k atribútom a meniť ich hodnoty.
Class<?> clazz = String.class;Field[] fields = clazz.getDeclaredFields();for (Field field : fields) { System.out.println("Názov atribútu: " + field.getName()); System.out.println("Typ atribútu: " + field.getType());}Pomocou Reflection môžeme získať informácie o konštruktoroch triedy a dynamicky vytvárať inštancie objektov.
Class<?> clazz = String.class;Constructor<?>[] constructors = clazz.getConstructors();for (Constructor<?> constructor : constructors) { System.out.println("Počet parametrov: " + constructor.getParameterCount());}Bridge vzor je štrukturálny návrhový vzor, ktorý oddeľuje abstrakciu od jej implementácie, takže sa obe môžu meniť nezávisle. To umožňuje flexibilnejší a modulárnejší návrh.
Bridge vzor sa skladá z dvoch hlavných častí:
Predstavme si, že máme systém pre správu notifikácií. Môžeme mať rôzne typy notifikácií (napr. TransactionNotification, SecurityAlert) a rôzne spôsoby odosielania notifikácií (napr. EmailSender, SMSSender). Pomocou Bridge vzoru môžeme oddeliť typ notifikácie od spôsobu jej odosielania.
interface NotificationSender { void send(String message);}class EmailSender implements NotificationSender { @Override public void send(String message) { System.out.println("Odosielam email: " + message); }}class SMSSender implements NotificationSender { @Override public void send(String message) { System.out.println("Odosielam SMS: " + message); }}abstract class Notification { protected NotificationSender sender; public Notification(NotificationSender sender) { this.sender = sender; } public abstract void sendNotification(String message);}class TransactionNotification extends Notification { public TransactionNotification(NotificationSender sender) { super(sender); } @Override public void sendNotification(String message) { sender.send("Transaction notification: " + message); }}V tomto príklade NotificationSender je rozhranie implementácie a Notification je abstraktná trieda abstrakcie. Môžeme ľahko pridať nové typy notifikácií alebo nové spôsoby odosielania bez toho, aby sme museli meniť existujúci kód.