En la programación orientada a objetos , el reenvío significa que el uso de un miembro de un objeto (ya sea una propiedad o un método ) da como resultado el uso real del miembro correspondiente de un objeto diferente: el uso se reenvía a otro objeto. El reenvío se usa en varios patrones de diseño , donde algunos miembros se reenvían a otro objeto, mientras que otros son manejados por el objeto usado directamente. El objeto de reenvío con frecuencia se denomina objeto contenedor , y los miembros de reenvío explícitos se denominan funciones contenedoras .
Delegación
El reenvío a menudo se confunde con la delegación ; formalmente, son conceptos complementarios. En ambos casos, hay dos objetos, y el primer objeto (envío, envoltorio) utiliza el segundo objeto (recepción, envoltorio), por ejemplo, para llamar a un método. Se diferencian en lo que se self
refiere al objeto receptor (formalmente, en el entorno de evaluación del método en el objeto receptor): en delegación se refiere al objeto emisor, mientras que en reenvío se refiere al objeto receptor. Tenga en cuenta que a self
menudo se usa implícitamente como parte del envío dinámico (resolución de método: a qué función se refiere el nombre de un método).
La diferencia entre reenvío y delegación es la vinculación del parámetro self en el contenedor cuando se llama a través del contenedor. Con la delegación, el parámetro self está vinculado al contenedor, con el reenvío está vinculado al contenedor. ... El reenvío es una forma de reenvío automático de mensajes; la delegación es una forma de herencia con enlace del padre (superclase) en tiempo de ejecución, en lugar de en tiempo de compilación / enlace como con la herencia "normal". [1]
Por ejemplo, dado el siguiente código:
// Remitente void n () { print ( "n1" ); }// Receptor void m () { print ( "m2," ); n (); }void n () { imprimir ( "n2" ); }
bajo delegación esto saldrá m2, n1porque n()
se evalúa en el contexto del objeto original (de envío), mientras que en el reenvío se generarám2, n2porque n()
se evalúa en el contexto del objeto receptor. [1]
En el uso casual, el reenvío a menudo se denomina "delegación", o se considera una forma de delegación, pero en un uso cuidadoso se distingue claramente por lo que se self
refiere. Si bien la delegación es análoga a la herencia , permitiendo la reutilización del comportamiento (y concretamente la reutilización del código ) sin cambiar el contexto de evaluación, el reenvío es análogo a la composición , ya que la ejecución depende solo del objeto receptor (miembro), no del objeto remitente (original). En ambos casos, la reutilización es dinámica, es decir, determinada en tiempo de ejecución (según el objeto al que se delega o reenvía el uso), en lugar de estática, es decir, determinada en tiempo de compilación / enlace (según la clase de la que se hereda). Al igual que la herencia, la delegación permite que el objeto emisor modifique el comportamiento original, pero es susceptible a problemas análogos a los de la frágil clase base ; mientras que el reenvío proporciona una encapsulación más fuerte y evita estos problemas; ver composición sobre herencia . [1]
Ejemplos de
Un ejemplo simple de reenvío explícito en Java: una instancia de reenvío de B
llamadas al foo
método de su a
campo:
clase B { A a ; T foo () { volver a . foo (); } }
Tenga en cuenta que al ejecutar a.foo()
, el this
objeto es a
(un subtipo de A
), no el objeto original (una instancia de B
). Además, a
no es necesario que sea una instancia de A
: puede ser una instancia de un subtipo. De hecho, A
ni siquiera es necesario que sea una clase: puede ser una interfaz / protocolo .
En contraste con la herencia, en la que foo
se define en una superclase A
(que debe ser una clase, no una interfaz), y cuando se llama en una instancia de una subclase B
, usa el código definido en A
, pero el this
objeto sigue siendo una instancia de B
:
clase A { T foo () { / * ... * / }; }la clase B extiende A { }
En este ejemplo de Python, la clase B
reenvía el foo
método y la x
propiedad al objeto en su a
campo: usarlos en b
(una instancia de B
) es lo mismo que usarlos en b.a
(la instancia de A
a la que se reenvían).
clase A : def __init__ ( self , x ) -> None : self . x = x def foo ( auto ): imprimir ( auto . x )clase B : def __init__ ( self , a ) -> None : self . a = a def foo ( yo ): yo . a . foo () @property def x ( self ): devuelve self . a . X @x . setter def x ( self , x ): self . a . x = x @x . deleter def x ( self ): del self . a . Xa = A ( 42 ) b = B ( a ) b . foo () # Imprime '42'. b . x # Tiene valor '42' b . x = 17 # bax ahora tiene el valor 17 del b . x # Elimina bax
Sencillo
En este ejemplo de Java , la Printer
clase tiene un print
método. Este método de impresión, en lugar de realizar la impresión en sí, reenvía a un objeto de clase RealPrinter
. Para el mundo exterior, parece que el Printer
objeto está haciendo la impresión, pero el RealPrinter
objeto es el que realmente está haciendo el trabajo.
Reenviar es simplemente pasar un deber a alguien / algo más. A continuación, se muestra un ejemplo sencillo:
class RealPrinter { // el "receptor" void print () { System . fuera . println ( "¡Hola mundo!" ); } }class Printer { // el "remitente" RealPrinter p = new RealPrinter (); // crea el receptor void print () { p . imprimir (); // llama al receptor } } public class Main { public static void main ( String [] argumentos ) { // para el mundo exterior parece que la impresora realmente imprime. Impresora impresora = nueva Impresora (); Impresora . imprimir (); } }
Complejo
El caso más complejo es un patrón de decorador que mediante el uso de interfaces , el reenvío se puede hacer más flexible y seguro para los tipos . "Flexibilidad" aquí significa que C
no es necesario hacer referencia A
ao B
de ninguna manera, ya que se abstrae el cambio de reenvío C
. En este ejemplo, la clase C
puede reenviar a cualquier clase que implemente una interfaz I
. La clase C
tiene un método para cambiar a otro reenviador. La inclusión de las implements
cláusulas mejora la seguridad de los tipos , porque cada clase debe implementar los métodos en la interfaz. La principal compensación es más código.
interfaz I { void f (); vacío g (); } la clase A implementa I { public void f () { System . fuera . println ( "A: haciendo f ()" ); } public void g () { Sistema . fuera . println ( "A: haciendo g ()" ); } } la clase B implementa I { public void f () { System . fuera . println ( "B: haciendo f ()" ); } public void g () { System . fuera . println ( "B: haciendo g ()" ); } } // cambiar el objeto de implementación en tiempo de ejecución (normalmente se hace en tiempo de compilación) la clase C implementa I { I i = null ; // reenviar public C ( I i ) { setI ( i ); } public void f () { i . f (); } public void g () { i . g (); } // atributos normales public void setI ( I i ) { this . i = yo ; } } public class Main { public static void main ( String [] argumentos ) { C c = new C ( new A ()); c . f (); // salida: A: haciendo f () c . g (); // salida: A: haciendo g () c . setI ( nuevo B ()); c . f (); // salida: B: haciendo f () c . g (); // salida: B: haciendo g () } }
Aplicaciones
El reenvío se utiliza en muchos patrones de diseño. [2] El reenvío se utiliza directamente en varios patrones:
- Patrón de cadena de responsabilidad
- Patrón decorador: el objeto decorador agrega sus propios miembros, reenviando otros al objeto decorado.
- Patrón de proxy: el objeto proxy reenvía el uso de los miembros al objeto real.
El reenvío se puede utilizar en otros patrones, pero a menudo se modifica el uso; por ejemplo, una llamada a un método en un objeto da como resultado que se invoquen varios métodos diferentes en otro:
Referencias
- ^ a b c Büchi, Martin; Weck, Wolfgang (2000). "Envoltorios genéricos" (PDF) . ECOOP 2000 - Programación orientada a objetos . Apuntes de conferencias en Ciencias de la Computación. 1850 . págs. 212-213 . doi : 10.1007 / 3-540-45102-1_10 . ISBN 978-3-540-67660-7.
- ^ Gamma, Erich ; Helm, Richard ; Johnson, Ralph ; Vlissides, John (1995). Patrones de diseño: elementos de software orientado a objetos reutilizable . Addison-Wesley . Bibcode : 1995dper.book ..... G . ISBN 978-0-201-63361-0.