Пересылка (объектно-ориентированное программирование)
В объектно-ориентированном программировании пересылка означает , что использование члена объекта ( свойства или метода ) приводит к фактическому использованию соответствующего члена другого объекта: использование перенаправляется на другой объект. Пересылка используется в ряде шаблонов проектирования , где некоторые члены перенаправляются другому объекту, а другие обрабатываются напрямую используемым объектом. Объект пересылки часто называют объектом-оболочкой , а явные члены пересылки называются функциями-оболочками .
Делегация
[ редактировать ]Пересылку часто путают с делегированием ; формально это взаимодополняющие понятия. В обоих случаях имеется два объекта, и первый (отправляющий, оберточный) объект использует второй (получающий, оберточный) объект, например, для вызова метода. Они отличаются тем, что self
ссылается на принимающий объект (формально, в среде оценки метода на принимающем объекте): при делегировании он ссылается на отправляющий объект, а при пересылке — на принимающий объект. Обратите внимание, что self
часто используется неявно как часть динамической диспетчеризации (разрешение метода: к какой функции относится имя метода).
Разница между пересылкой и делегированием заключается в привязке параметра self в оболочке при вызове через оболочку. При делегировании параметр self привязан к оболочке, при пересылке — к оболочке. ... Переадресация — это форма автоматической повторной отправки сообщений; делегирование — это форма наследования с привязкой родителя (суперкласса) во время выполнения, а не во время компиляции/связывания, как при «обычном» наследовании. [1]
Например, учитывая следующий код:
// Sender
void n() {
print("n1");
}
// Receiver
void m() {
print("m2, ");
n();
}
void n() {
print("n2");
}
В рамках делегирования, m()
выведет м2, н1 потому что n()
оценивается в контексте исходного (отправляющего) объекта, а при пересылке выводится м2, н2 потому что n()
оценивается в контексте принимающего объекта. [1]
При обычном использовании пересылку часто называют «делегированием» или считают формой делегирования, но при осторожном использовании они четко отличаются по тому, что self
относится к. Хотя делегирование аналогично наследованию , допуская повторное использование поведения (и, в частности, повторное использование кода ) без изменения контекста оценки, пересылка аналогична композиции , поскольку выполнение зависит только от принимающего объекта (члена), а не от (исходного) отправляющего объекта. В обоих случаях повторное использование является динамическим, то есть определяется во время выполнения (на основе объекта , которому делегировано или перенаправлено использование), а не статическим, то есть определяется во время компиляции/связывания (на основе класса , от которого унаследовано). Как и наследование, делегирование позволяет отправляющему объекту изменять исходное поведение, но подвержено проблемам, аналогичным хрупкому базовому классу ; при этом пересылка обеспечивает более сильную инкапсуляцию и позволяет избежать этих проблем; см. композицию вместо наследования . [1]
Примеры
[ редактировать ]Простой пример явной пересылки в 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
поле: используя их на b
(пример B
) то же самое, что использовать их на b.a
(пример 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.
Простой
[ редактировать ]
В этом Java примере Printer
в классе есть print
метод. Этот метод печати вместо выполнения самой печати перенаправляется объекту класса RealPrinter
. Внешнему миру кажется, что Printer
объект выполняет печать, но RealPrinter
объект — это тот, кто фактически выполняет работу.
Пересылка — это просто передача обязанности кому-то/чему-то другому. Вот простой пример:
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
не нужно ссылаться на A
или B
никак, так как переключение переадресации абстрагировано от C
. В этом примере класс C
может пересылаться в любой класс, реализующий интерфейс I
. Сорт C
имеет способ переключиться на другой сервер пересылки. В том числе implements
Предложения улучшают безопасность типов , поскольку каждый класс должен реализовывать методы в интерфейсе. Главный компромисс — больше кода.
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] Переадресация используется непосредственно в нескольких шаблонах:
- Схема цепочки ответственности
- Шаблон декоратора : объект-декоратор добавляет свои собственные члены, перенаправляя других к декорируемому объекту.
- Шаблон прокси : прокси-объект перенаправляет использование члена реальному объекту.
Переадресация может использоваться и в других шаблонах, но часто ее использование модифицируется; например, вызов метода одного объекта приводит к вызову нескольких разных методов для другого:
Ссылки
[ редактировать ]- ^ Jump up to: а б с Бючи, Мартин; Век, Вольфганг (2000). «Общие обертки» (PDF) . ЭКООП 2000 — Объектно-ориентированное программирование . Конспекты лекций по информатике. Том. 1850. С. 212–213 . дои : 10.1007/3-540-45102-1_10 . ISBN 978-3-540-67660-7 .
- ^ Гамма, Эрих ; Хельм, Ричард ; Джонсон, Ральф ; Влиссидес, Джон (1995). Шаблоны проектирования: элементы объектно-ориентированного программного обеспечения многократного использования . Аддисон-Уэсли . Бибкод : 1995dper.book.....G . ISBN 978-0-201-63361-0 .