轉發 (物件導向程序設計)

物件導向程序設計中,轉發(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.