跳转到内容

转发 (面向对象程序设计)

维基百科,自由的百科全书

面向对象程序设计中,转发(forwarding)是指使用一个对象的成员(属性方法)导致实际使用了另一个对象的对应的成员。即“转发”到另一个对象。转发被用于很多软件设计模式中,某些成员的使用被转发到别的对象,另外的成员由当前直接使用的对象使用。转发的对象可称作“包装对象)(wrapper object),显式转发的成员可称为包装函数

与委托的区别

转发与委托在形式上是一对互补的概念。两种情况下,有2个对象,第一个(发送者、包装器)对象使用第二个(接收者、被包装者)对象,例如调用一个方法。两种情况的接收对象中的self指向不同的内容(可以理解为不同的运行时上下文):对于委托,self指向发送者对象,对于转发,self指向接收者对象。self也常被隐式作为动态分派的一部分,即方法解析:方法名字指向了哪个函数。[1]

委托类似于继承,允许行为复用(具体说代码复用),转发类似于复合英语object composition,执行仅依赖于接收者对象。两种情况,复用是动态的,在运行时确定其内容(基于委托或者转发到哪个对象),而不是静态的(编译、链接时确定哪个类被“继承”)。

例子

Java的显式转发的例子:B的实例转发foo方法的调用给它的字段a:

class B {
    A a;
    T foo() { return a.foo(); }
}

当执行a.foo(), this对象是a (A的子类型), 不是最初对象的(B的实例)。进一步,a不必是A的实例,也可是其子类型的实例。甚至A不是一个类,而是接口/协议

与继承相比较,foo定义于超类A中(必须是类,不能是接口),在子类B中调用方法,使用A中定义的代码,但this对象仍然是B的实例:

class A {
    T foo() { /* ... */ };
}

class B extends A {
}

下述Python例子,类B转发foo方法调用和属性x到它的字段a的对象:

class A:
    def __init__(self, x) -> None:
        self.x = x

    def foo(self):
        print(self.x)

class B:
    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.

简单示例

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();
    }
}

复杂示例

装饰模式使用接口,转发更为灵活且类型安全。下例子中,灵活是指类C不需要引用类AB,类C可以转发到任何实现了接口I的类。

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()
	}
}

应用

转发可用于许多设计模式。[2] Forwarding is used directly in several patterns:

  • 责任链模式
  • 装饰模式: 装饰器对象增加自己的成员,把其余成员的调用转发给被装饰对象。
  • 代理模式: 代理对象把对成员的使用转发给真正的对象。

转发也可以用于其他设计模式,但可能是修改过的转发:

参考文献

  1. ^ Büchi, Martin; Weck, Wolfgang. Generic Wrappers (PDF). ECOOP 2000 — Object-Oriented Programming. Lecture Notes in Computer Science 1850. 2000: 212–213 [2022-04-16]. ISBN 978-3-540-67660-7. doi:10.1007/3-540-45102-1_10. (原始内容 (PDF)存档于2022-07-12). 
  2. ^ Gamma, Erich; Helm, Richard; Johnson, Ralph; Vlissides, John. Design Patterns: Elements of Reusable Object-Oriented Software. Addison-Wesley. 1995. Bibcode:1995dper.book.....G. ISBN 978-0-201-63361-0.