Továbbítás (objektumorientált programozás)
Az objektumorientált programozásban a továbbítás azt jelenti, hogy egy objektum egy tagjának (akár tulajdonságnak, akár egy metódusának) a használata azt eredményezi, hogy egy másik objektum megfelelő tagját használjuk valójában: egy másik objektumnak átadjuk a használatot. Az átirányítást számos tervezési mintában használják, ahol egyes tagokat egy másik objektumnak továbbítanak, míg másokat a közvetlenül használt objektum kezel. A továbbító objektumot gyakran csomagoló objektumnak nevezik, az explicit továbbító tagokat pedig csomagoló funkciónak.
Delegálás
[szerkesztés]A továbbítást gyakran összekeverik a delegálással; formálisan ezek kiegészítő fogalmak. Mindkét esetben két objektum létezik, és az első (küldő, csomagoló) objektum a második (fogadó, csomagolandó) objektumot használja, például egy metódus meghívására. Különböznek abban, hogy mi utal önmagára a fogadó objektumra (formálisan a metódus kiértékelési környezetében a fogadó objektumra): átruházáskor a küldő objektumra, míg továbbításkor a fogadó objektumra utal. Vegyük figyelembe, hogy a "self" kulcsszó gyakran implicit módon van használva a dinamikus továbbítás részeként (metódus felbontása: melyik funkcióra utal a metódus neve).
„A továbbítás és a delegálás közötti különbség a saját paraméterek összekötésében van, amikor a csomagolandót a csomagolón keresztül hívják meg. Delegálással a saját paraméter a csomagolóhoz van kötve, továbbításnál pedig a csomagolandóhoz. A továbbítás az automatikus üzenetküldés egy formája; a delegálás az öröklés egy formája, amelyben a szülő (ősosztály) összekötése futási időben történik, nem pedig az összeállítási/összekapcsolási időben, mint a „normál” öröklésnél.[1] ” |
Például, adott a következő kód:
// Sender
void n() {
print("n1");
}
// Receiver
void m() {
print("m2, "); n();
}
void n() {
print("n2");
}
delegáláskor ez m2 és n1 kimenetet eredményez, mivel az n()
értékét az eredeti (küldő) objektummal összefüggésben értékeli ki, míg a továbbítás alatt ez m2, n2 kimenetet eredményez mert n()
a fogadó objektummal összefüggésben van kiértékelve.[1]
Általános használat esetén a továbbítást gyakran "delegálásnak" nevezik, vagy átruházásnak tekintik, ám gondos használat esetén egyértelműen megkülönböztető, hogy a "self" mire utal. Míg a delegálás hasonlít az örökléshez, lehetővé téve a működésen alapuló újrafelhasználást (és konkrétan a kód újrafelhasználását) anélkül, hogy megváltoztatná a környezetét. A továbbítás hasonló a kompozícióhoz, mivel a végrehajtás csak a fogadó objektumtól függ, és nem az (eredeti) küldő objektumtól. Mindkét esetben az újrafelhasználás dinamikus, vagyis futási időben meghatározott (az objektum alapján, amelyre a felhasználást átruházzák vagy továbbítja), nem pedig statikus, azaz a fordítási időben meghatározott (az az osztály alapján, amelytől örökölt). Az örökléshez hasonlóan a delegálás lehetővé teszi a küldő objektum számára az eredeti viselkedés módosítását, de érzékeny a alaposztályhoz hasonló problémákra. A továbbítás erősebb beágyazást biztosít és elkerüli ezeket a problémákat, lásd az objektum-összetételt az öröklés felett.[1]
Példák
[szerkesztés]Az explicit továbbítás egyszerű példája Java-ban: a B
példány továbbítja a hívásokat a foo
metódus a
mezőjének:
class B {
A a;
T foo() { return a.foo(); }
}
Vegyük figyelembe, hogy az a.foo()
végrehajtásakor a this
objektum egy a
(A altípusa
), nem pedig az eredeti objektum (B példánya
). Ezenkívül a
-nak nem feltétlenül kell az A
példányának lennie: lehet egy altípus példánya is. Valójában, A
-nak nem is kell osztálynak lennie: lehet interfész.
Az örökléssel ellentétben, amelyben a foo
értéket egy A
szuperosztályban definiálják (amelynek osztálynak kell lennie, nem interfésznek), és amikorB
, alosztály példányára hívják, akkor az A
-ban definiált kódot használja, de ez az objektum még mindig B
példánya:
class A {
T foo() { /* ... */ };
}
class B extends A {
}
Ebben a Python-példában a B
osztály továbbítja a foo metódust
és az x
property-t az a
mezőben lévő objektumnak: ezek használata b-n
(B példánya
) ugyanaz, mint a b.a
használata (A
példánya, amelyhez ezek vannak továbbítva).
class A(object):
def __init__(self, x) -> None:
self.x = x
def foo(self):
print(self.x)
class B(object):
def __init__(self, a) -> None:
self.a = a
def foo(self):
self.a.foo()
@property
def x(self):
return self.a.x
@x.setter
def x(self, x):
self.a.x = x
@x.deleter
def x(self):
del self.a.x
a = A(42)
b = B(a)
b.foo() # Prints '42'.
b.x # Has value '42'
b.x = 17 # b.a.x now has value 17
del b.x # Deletes b.a.x.
Szimpla
[szerkesztés]Ebben a Java-példában a Printer osztálynak van print metódusa. Ez a print metódus ahelyett, hogy maga végrehajtaná a kiíratást, a RealPrinter osztályú objektumnak továbbítja ezt. A külvilág számára úgy tűnik, hogy a Printer objektum végzi a kiíratást, de a RealPrinter objektum az, amely valójában a munkát végzi.
Az átirányítás egyszerűen valamilyen kötelességet ruház át valaki másra. Íme egy egyszerű példa:
class RealPrinter { // the "receiver"
void print() {
System.out.println("Hello world!");
}
}
class Printer { // the "sender"
RealPrinter p = new RealPrinter(); // create the receiver
void print() {
p.print(); // calls the receiver
}
}
public class Main {
public static void main(String[] arguments) {
// to the outside world it looks like Printer actually prints.
Printer printer = new Printer();
printer.print();
}
}
Komplex
[szerkesztés]A bonyolultabb eset a díszítő minta, amely interfészek használatával rugalmasabbá és típusbiztonságosabbá teszi a továbbítást. A "rugalmasság" itt azt jelenti, hogy a C-nek semmilyen módon nem kell utalnia A-ra vagy B-re, mivel a továbbítás váltása C-ből származik. Ebben a példában a C osztály továbbíthat minden osztályhoz, amely implementálja az I interfészt. A C osztály rendelkezik egy metódussal, hogy tudjon váltani a továbbítók között. Az implements kulcsszó javítva a típusbiztonságot, mivel minden osztálynak implementálnia kell a metódusokat amelyeket az interfész tartalmaz. A fő kompromisszum az több kódot jelent.
interface I {
void f();
void g();
}
class A implements I {
public void f() { System.out.println("A: doing f()"); }
public void g() { System.out.println("A: doing g()"); }
}
class B implements I {
public void f() { System.out.println("B: doing f()"); }
public void g() { System.out.println("B: doing g()"); }
}
// changing the implementing object in run-time (normally done in compile time)
class C implements I {
I i = null;
// forwarding
public C(I i){ setI(i); }
public void f() { i.f(); }
public void g() { i.g(); }
// normal attributes
public void setI(I i) { this.i = i; }
}
public class Main {
public static void main(String[] arguments) {
C c = new C(new A());
c.f(); // output: A: doing f()
c.g(); // output: A: doing g()
c.setI(new B());
c.f(); // output: B: doing f()
c.g(); // output: B: doing g()
}
}
Alkalmazások
[szerkesztés]A továbbítást számos tervezési mintában használják.[2] A továbbítást közvetlenül több mintában használják:
- Felelősséglánc minta
- Díszítő/dekorátr minta: A díszítő objektum hozzáadja saját tagjait, majd továbbadja a díszített tárgyat.
- Helyettesítő/proxy minta
A továbbítás más mintákban is felhasználható, de a felhasználás gyakran módosul, például egy metódushívás egy objektumra több különféle metódus hívását eredményezi egy másikon:
Források
[szerkesztés]- ↑ a b c Generic Wrappers, ECOOP 2000 — Object-Oriented Programming, Lecture Notes in Computer Science, 212–213. o.. DOI: 10.1007/3-540-45102-1_10 (2000). ISBN 978-3-540-67660-7
- ↑ Gamma, Erich. Design Patterns: Elements of Reusable Object-Oriented Software. Addison-Wesley (1995). ISBN 978-0-201-63361-0
Fordítás
[szerkesztés]- Ez a szócikk részben vagy egészben a Forwarding (object-oriented programming) című angol Wikipédia-szócikk ezen változatának fordításán alapul. Az eredeti cikk szerkesztőit annak laptörténete sorolja fel. Ez a jelzés csupán a megfogalmazás eredetét és a szerzői jogokat jelzi, nem szolgál a cikkben szereplő információk forrásmegjelöléseként.