حواله (برنامهنویسی شیءگرا)
در برنامهنویسی شئگرا فورواردینگ یا همان حواله یا اِحاله به این معناست که استفاده از عضوی(متد یا فیلد) از یک شئ منجر به استفاده از عضوی متناظر از شئ دیگر میشود یا به قولی آن عضو به آن شئ دیگر، حواله یا محوَّل میشود. مفهوم حواله در چندیدن الگوی طراحی مورد استفاده قرار میگیرد، به این صورت که بعضی از اعضای شئ(اسلوبها یا فیلدها) به شئ دیگر حواله میشوند(مثلاً متدی را صدا میزنیم که بدنهٔ این متد خود متدی از شئ دیگر را صدا میزند) در حالی که خود شئ به اعمال اعضای دیگر(اعضای به غیر اعضای حواله شده) رسیدگی میکند. شئای که اعضایش فراخوانیها را از طریق حواله پاسخ میدهد، با نام شئ پیچنده یا پیچه (wrapper)، میشناسند و اسلوبهایی که حواله میشوند را توابع پیچنده میگویند.[۱]
مثالها
مثالی ساده در باب حوالهٔ صریح در جاوا: یک شئ از کلاس B فراخوانیهای مربوط به اسلوبFoo() را به شئ a از کلاس A حواله میکند:
class B {
A a;
T foo() { return a.foo(); }
}
در مثالی که به زبان پایتون در ذیل این پاراگراف آمده است، کلاس B متد foo و فیلد x را به شئ a حواله میکند. (در این قسمت از کد: () a.foo یا a.x = x )
class A:
def __init__(self, x):
self.x = x
def foo(self):
print(self.x)
class B:
def __init__(self, a):
self.a = a
def foo(self):
a.foo()
@property
def x(self):
return a.x
@x.setter
def x(self, x):
a.x = x
@x.deleter
def x(self):
del 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.
نمونهٔ ساده
در این مثال جاوا، کلاس Printer یک اسلوب به نام print دارد. این متد به جای آنکه خودش عملیات پرینت را به انجام دهد، این عملیات را به شئ ای از کلاس RealPrinter محوّل میکند(همانطور که در کد زیر در متد print() از کلاس Printer میبینیم، متد print() از شئ RealPrinter صدا زده میشود ). در ظاهر و (و از منظر قسمتهای دیگر برنامه که این متد را فراخوانی میکنند)، به نظر میرسد که این عملیات را شئِ از کلاس Printer انجام میدهد در حالی که در اصلْ شئ از کلاس RealPrinter عملیات اصلی مربوط به پرینت را انجام میدهد.
حواله(احاله) به زبان ساده محوّل کردن یک وظیفه به چیز/کس دیگری است. یک مثال ساده:
class RealPrinter { // دریافت کننده
void print() {
System.out.println("Hello world!");
}
}
class Printer { //ارسال کننده
RealPrinter p = new RealPrinter(); // ساختن دریافت کننده
void print() {
p.print(); // فراخوانی دریافت کننده
}
}
public class Main {
public static void main(String[] arguments) {
// از منظر جهان خارجی و سایر اشیاء برنامه اینطور به نظر میرسد که
//که کلاس
// Printer
//خودش عملیات
//print
// را انجام میدهد
Printer printer = new Printer();
printer.print();
}
}
در اینجا تابع printer از شئ p از کلاس Printer وظیفهٔ پرینت کردن را به شئ p از کلاس RealPrinter محوّل میکند(p.print();)
نمونهٔ پیچیده
نمونهٔ پیچیدهتر برای عملیات حواله، در الگوی آذینگر استفاده میشود. در الگوی طراحی آذینگر میتوان با استفاده از interface، حواله را بسیار منعطفتر و ایمنتر از لحاظ نوع(ایمنی نوع) کرد.
«انعطافپذیری» در اینجا به این معناست که نیازی نیست که C به A یا B اشاره کند چرا که شئ از کلاسی که از داخل کلاس C به آن حواله میشود، از طریق تجرید از این کلاس خارج شدهاست.(منظور این است که کلاس C بهطور مستقیم به کلاسهای A و B که پیادهسازیهای رابط I هستند، چیزی را حواله نمیکند. بلکه بهطور غیرمستقیم اینکار را میکند، همانطور که میبینیم به I حواله میشود. ) در این مثال کلاس C میتواند به هر کلاسی که رابط I را پیادهسازی کند، حواله انجام دهد. در کلاس C اسلوبی با نام setI() وجود دارد که از آن میتوان برای تغییر کلاسی که به آن حواله میشود(کلاسی که I را پیادهسازی میکند) استفاده کرد(این کار را در زمان اجرا نیز میتوان انجام داد). استفاده از رابطِI موجب میشود تا ایمنی در نوعداده لحاظ شود. چرا که هر کلاس که 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()
}
}