Шаблон делегирования

Материал из Seo Wiki - Поисковая Оптимизация и Программирование

Перейти к: навигация, поиск

В разработке ПО, шаблон делегирования (англ. delegation pattern) — это способ, которым объект внешне выражает некоторое поведение, но в реальности передаёт ответственность за выполнение этого поведения связанному объекту. Шаблон делегирования является фундаментальной абстракцией, которая поддерживает композицию (также называемую агрегацией), примеси (mixins) и аспекты (aspects).

Содержание

Цель

Плюсы

Минусы

Этот шаблон обычно затрудняет оптимизацию по скорости в пользу улучшенной чистоты абстракции.

Применимость

Примеры

Java

Простой пример

В этом примере на языке Java, класс C имеет метод-заглушку (method stub), который передаёт методы f() и g() классу A. Класс C делает вид, что он имеет атрибуты класса A.

class A {
    void f() {
        System.out.println("A: вызываем метод f()");
    }
    void g() {
        System.out.println("A: вызываем метод g()"); 
    }
}
 
class C {
    // Создаём объект методы которого будем делегировать
    A a = new A();
 
    void f() {
        a.f();
    }
    void g() {
        a.g();
    }
 
    // normal attributes
    X x = new X();
    void y() {
        /* do stuff */
    }
}
 
public class Main {
    public static void main(String[] args) {
        C c = new C();
        c.f();
        c.g();
    }
}

Сложный пример

Используя интерфейсы, делегирование может быть осуществлено более гибко и c защитой типов (typesafe). В этом примере, класс C может делегировать либо классу A либо классу B. Класс C имеет методы для переключения между классами A и B. Включение расширения implements улучшает безопасность типа, потому что каждый класс должен выполнять методы в интерфейсе. Основным недостатком является большее количество кода.

Приведем пример. Допустим, нужно реализовать таймер таким образом, чтобы через определённое количество времени вызывалась некоторая функция. Программист таймера хочет предоставить задание функции пользователям своего класса (другим программистам).

/**
 * Интерфейс описывает действие, которое будет вызываться при наступления
 * события от таймера.
 * 
 */
interface TimerAction {
    void onTime();
}
 
class WakeUpAction implements TimerAction {
    @Override
    public void onTime() {
        System.out.println("Пора вставать!");
    }
}
 
class ChickenIsReadyAction implements TimerAction {
    @Override
    public void onTime() {
        System.out.println("Цыпленок готов!");
    }
}
 
/**
 * Класс таймера. При определённых условиях вызывается действие TimerActon. 
 */
class Timer {
    TimerAction action;
 
    /**
     * Функция, которую вызывает программист для установки времени.
     */
    void run() {
        if (isTime()) {
            action.onTime();
        }
    }
 
    /**
     * Некоторая функция, которая берет на себя все работу со временем. Ее
     * релазация не интересна в данном контексте.
     * 
     * @return
     */
    private boolean isTime() {
        return false;
    }
 
    public static void main(String[] args) {
 
        System.out.println("Введите тип действия:");
        String actionType = System.console().readLine();
        Timer timer = new Timer();
 
        if (actionType.equalsIgnoreCase("set wake up timer")) {
            timer.action = new WakeUpAction();
        } else if (actionType.equalsIgnoreCase("set chicken timer")) {
            timer.action = new ChickenIsReadyAction();
        }
 
        timer.run();
    }

C++

Сложный пример

Этот пример — это версия на C++ сложного примера на Java, приведённого выше. Так как C++ не имеет конструкции интерфейса, ту же самую роль играет полностью абстрактный класс. Преимущества и недостатки в основном те же самые, что и в примере на Java.

#include <iostream>
 
class I {
   public:
      virtual void f ( void ) = 0;
      virtual void g ( void ) = 0;
};
 
class A : public I {
   public:
      void f ( void ) { std::cout << "A: вызываем метод f()" << std::endl; }
      void g ( void ) { std::cout << "A: вызываем метод g()" << std::endl; }
};
 
class B : public I {
   public:
      void f ( void ) { std::cout << "B: вызываем метод f()" << std::endl; }
      void g ( void ) { std::cout << "B: вызываем метод g()" << std::endl; }
};
 
class C : public I {
   public:
     // Конструктор
      C() : i ( new A() ) { }
     // Деструктор
      virtual ~C() {
         delete i;
      }
      void f ( void ) { i -> f(); }
      void g ( void ) { i -> g(); }
     // Этими методами меняем поле-объект, чьи методы будем делегировать
      void toA ( void ) {
         delete i;
         i = new A();
      }
      void toB ( void ) {
         delete i;
         i = new B();
      }
   private:
     // Объявляем объект методы которого будем делегировать
      I * i;
};
 
int main ( void ) {
   C * c = new C();
 
   c -> f();
   c -> g();
   c -> toB();
   c -> f();
   c -> g();
 
   delete c;
   return 0;
}

C#

Сложный пример

namespace Patterns
{
    interface I
    {
        void f();
        void g();
    }
 
    class A 
      : I
    {
        public void f()
        {
            System.Console.WriteLine("A: вызываем метод f()");
        }
        public void g()
        {
            System.Console.WriteLine("A: вызываем метод g()");
        }
    }
 
    class B 
      : I
    {
        public void f()
        {
            System.Console.WriteLine("B: вызываем метод f()");
        }
        public void g()
        {
            System.Console.WriteLine("B: вызываем метод g()");
        }
    }
 
    class C 
      : I
    {
        // Создаём объект, методы которого будем делегировать
        I i = new A();
 
        public void f()
        {
            i.f();
        }
        public void g()
        {
            i.g();
        }
 
        // Этими методами меняем поле-объект, чьи методы будем делегировать
        public void toA()
        {
            i = new A();
        }
        public void toB()
        {
            i = new B();
        }
    }
 
    class DelegatePattern
    {
        static void Main( string[] args )
        {
            C c = new C();
            c.f();
            c.g();
            c.toB();
            c.f();
            c.g();
            System.Console.ReadKey();
        }
    }
}

Нетривиальный пример

Это пример случая, часто встречающегося в практике. Стоит задача создать класс для хранения списка сотрудников. Данные каждого сотрудника хранятся в объекте класса Employee. Есть уже готовый и стандартный класс для хранения списка объектов Employee. В нём уже реализованы механизмы для работы со списком (к примеру — выделение памяти, добавление и удаление из списка). Наследование класса списка сотрудников от класса списка объектов здесь неприемлемо, потому как мы получим все методы (даже те, которые нас не интересуют). Кроме того нам придётся в некоторых случаях производить приведение типов. Самый элегантный выход из этого случая — делегировать классу списка сотрудников часть методов класса списка объектов. В правилах ООП лучше всего список объектов представить частным (приватным) методом списка сотрудников. В данном случае доступ к списку возможен через свойство.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
 
namespace Employees
{
    /// <summary>
    /// Класс для хранения данных о сотруднике.
    /// </summary>
    class Employee
    {
        private string name;
        private string department;
 
        public Employee(string name, string departament)
        {
            this.name = name;
            this.department = departament;
        }
 
        /// <summary>
        /// Имя сотрудника.
        /// </summary>
        public string Name
        {
            get { return this.name; }
        }
 
        /// <summary>
        /// Отдел работы.
        /// </summary>
        public string Department
        {
            get { return this.department; }
        }
    }
 
    /// <summary>
    /// Класс для хранения списка сотрудников.
    /// </summary>
    class EmployeesList
    {
        private List<Employee> employees = new List<Employee>();
 
        /// <summary>
        /// Свойство для получения и записи сотрудника по индексу.
        /// </summary>
        /// <param name="index">Индекс сотрудника.</param>
        /// <returns>Сотрудник.</returns>
        public Employee this[int index]
        {
            get
            {
                return employees[index];
            }
            set
            {
                employees[index] = value;
            }
        }
 
        /// <summary>
        /// Добавление нового сотрудника.
        /// </summary>
        /// <param name="employee">Новый сотрудник.</param>
        public void Add(Employee employee)
        {
            employees.Add(employee);
        }
 
        /// <summary>
        /// Удаление существующего сторудника.
        /// </summary>
        /// <param name="employee">Сотрудник для удаления.</param>
        public void Remove(Employee employee)
        {
            employees.Remove(employee);
        }
 
        /// <summary>
        /// Последовательный поиск сотрудника по имени.
        /// </summary>
        /// <param name="name">Имя сотрудника.</param>
        /// <returns>Индекс сотрудника.</returns>
        public int GetIndexOfEmployeeByName(string name)
        {
            int index = -1;
 
            for (int i = 0; i < employees.Count; i++)
            {
                if (employees[i].Name == name)
                {
                    index = i;
                    break;
                }
            }
            return index;
        }
 
        /// <summary>
        /// Последовательный поиск сотрудника по имени.
        /// </summary>
        /// <param name="name">Имя сотрудника.</param>
        /// <param name="offset">Позиция, с которой следует начинать поиск.</param>
        /// <returns>Индекс сотрудника.</returns>
        public int GetIndexOfEmployeeByName(string name, int offset)
        {
            int index = -1;
 
            for (int i = offset; i < employees.Count; i++)
            {
                if (employees[i].Name == name)
                {
                    index = i;
                    break;
                }
            }
            return index;
        }
    }
 
    class Program
    {
        static void Main(string[] args)
        {
            //Создание списка сотрудников и добавление записей в него
            EmployeesList empList = new EmployeesList();
            empList.Add(new Employee("Танасийчук Степан", "web студия"));
            empList.Add(new Employee("Кусый Назар", "web студия"));
            empList.Add(new Employee("Сорока Орест", "web студия"));
 
            //Поиск сотрудника Кусый Назар и вывод результата при поиске с начала и со 2-ой позиции
            Console.WriteLine(empList.GetIndexOfEmployeeByName("Кусый Назар").ToString());
            Console.WriteLine(empList.GetIndexOfEmployeeByName("Кусый Назар", 2).ToString());
 
            //Поиск и удаление сотрудника Сорока Орест
            empList.Remove(empList[empList.GetIndexOfEmployeeByName("Сорока Орест")]);
        }
    }
}

Object Pascal

Простой пример

 
  type
 
    IDelegateInterface = interface
      procedure Method1;
      procedure Method2;
    end;
 
    TClassA = class(TInterfacedObject, IDelegateInterface)
    public
      procedure Method1;
      procedure Method2;
    end;
 
    TClassB = class(TInterfacedObject, IDelegateInterface)
    public
      procedure Method1;
      procedure Method2;
    end;
 
    TClassDel = class(TInterfacedObject, IDelegateInterface)
    private
      FInterface: IDelegateInterface;
    public
      procedure Method1;
      procedure Method2;
      procedure ToClassA;
      procedure ToClassB;
    end;
 
  implementation
 
  { TClassA }
 
  procedure TClassA.Method1;
  begin
    Writeln('TClassA.Method1');
  end;
 
  procedure TClassA.Method2;
  begin
    Writeln('TClassA.Method2');
  end;
 
  { TClassB }
 
  procedure TClassB.Method1;
  begin
    Writeln('TClassB.Method1');
  end;
 
  procedure TClassB.Method2;
  begin
    Writeln('TClassB.Method1');
  end;
 
  { TClassDel }
 
  procedure TClassDel.Method1;
  begin
    FInterface.Method1;
  end;
 
  procedure TClassDel.Method2;
  begin
    FInterface.Method2;
  end;
 
  procedure TClassDel.ToClassA;
  begin
    FInterface := TClassA.Create;
  end;
 
  procedure TClassDel.ToClassB;
  begin
    FInterface := TClassB.Create;
  end;

Нетривиальный пример

Этот пример — это версия на Object Pascal нетривиального примера, приведённого выше.

 unit UnitEmployeers;
 
 interface
 
 uses
   Contnrs;
 
 type
   // Класс для хранения данных о сотруднике
   TEmployee = class
   private
     FName: string;
     FDepartament: string;
   public
     constructor Create(Name, Departament: string);
   published
     property Name: string read FName;
     property Departament: string read FDepartament;
   end;
 
   // Класс для хранения списка сотрудников
   TEmployeersList = class
   private
     // Объект класса "список объектов"
     FEmployeersList: TObjectList;
     function GetEmployee(Index: Integer): TEmployee;
     procedure SetEmployee(Index: Integer; const Value: TEmployee);
   public
     constructor Create;
     destructor Destroy; override;
     function Add(Employee: TEmployee): Integer;
     procedure Remove(Employee: TEmployee);
     function IndexEmployeeByName(Name: string; Offset: Integer = 0): Integer;
     property Employeers[Index: Integer]: TEmployee read GetEmployee write SetEmployee; default;
   end;
 
 implementation
 
 { TEmployee }
 
 constructor TEmployee.Create(Name, Departament: string);
 begin
   FName := Name;
   FDepartament := Departament;
 end;
 
 { TEmployeersList }
 
 constructor TEmployeersList.Create;
 begin
   // Создаём объект методы которого будем делегировать
   FEmployeersList := TObjectList.Create;
 end;
 
 destructor TEmployeersList.Destroy;
 begin
   FEmployeersList.Free;
   inherited;
 end;
 
 function TEmployeersList.GetEmployee(Index: Integer): TEmployee;
 begin
   Result := FEmployeersList[Index] as TEmployee;
 end;
 
 procedure TEmployeersList.SetEmployee(Index: Integer;  const Value: TEmployee);
 begin
   FEmployeersList[Index] := Value;
 end;
 
 function TEmployeersList.IndexEmployeeByName(Name: string; Offset: Integer = 0): Integer;
 // Последовательный поиск сотрудника по имени
 // Через аргумент Offset можно задавать позицию с которой вести поиск.
 // Если сотрудник не найден вернёт значение меньше ноля (-1)
 var
   Index: Integer;
 begin
   Result := -1; // Предполагаем что его нет в списке
   for Index := FEmployeersList.Count - 1 downto Offset do
     if (FEmployeersList[Index] as TEmployee).Name = Name then
     begin
       Result := Index;
       Exit;
     end;
 end;
 
 function TEmployeersList.Add(Employee: TEmployee): Integer;
 begin
   Result := FEmployeersList.Add(Employee);
 end;
 
 procedure TEmployeersList.Remove(Employee: TEmployee);
 begin
   FEmployeersList.Remove(Employee);
 end;
 
 end.

К сожалению не все программисты применяют шаблон делегирования. Например, фирма Borland (разработчик среды программирования Delphi) в своей стандартной библиотеке классов наследовала вышеупомянутый класс списка объектов TObjectList от класса списка указателей TList. Это вызвало недовольство среди некоторых опытных программистов.

PHP5

Простой пример

Этот пример — это версия на PHP простого примера на Java, приведённого выше.

<?php
 
class A {
 
	public function f() {
		print "А: Вызываем метод f()<br />";
	}
 
	public function g() {
		print "А: Вызываем метод g()<br />";
	}
}
 
class C {
 
	private $_a;
 
	public function __construct() {
		$this->_a = new A;
	}
 
	public function f() {
		$this->_a->f();
	}
 
	public function g() {
		$this->_a->g();
	}
 
	public function y() {
		print "C: вызываем метод y()<br />";
	}
}
 
$obj = new C;
$obj->f();
$obj->g();
$obj->y();
 
?>

Сложный пример

Этот пример — это версия на PHP сложного примера на Java, приведённого выше.

<?php
 
// используем интерфейс для безопасности типа
interface I {
	public function f();
	public function g();
}
 
class A implements I {
	public function f() {
		print "А: Вызываем метод f()<br />";
	}
 
	public function g() {
		print "А: Вызываем метод g()<br />";
	}
}
 
class B implements I {
	public function f() {
		print "B: Вызываем метод f()<br />";
	}
 
	public function g() {
		print "B: Вызываем метод g()<br />";
	}
}
 
class C implements I {
	private $_i;
 
	// создаём объект, методы которого будем делегировать
	public function __construct() {
		$this->_i = new A;
	}
 
	// этими методами меняем поле-объект, чьи методы будем делегиролвать
	public function toA() {
		$this->_i = new A;
	}
 
	public function toB() {
		$this->_i = new B;
	}
 
 
	// делегированые методы
	public function f() {
		$this->_i->f();
	}
 
	public function g() {
		$this->_i->g();
	}
}
 
$obj = new C;
$obj->f();
$obj->g();
$obj->toB();
$obj->f();
$obj->g();
 
?>

Нетривиальный пример

Этот пример — это версия на PHP нетривиального примера, приведённого выше.

<?php
 
// класс для хранения данных о сотруднике
class TEmployee {
 
	private $_name;
	private $_departament;
 
	public function __construct($name, $departament) {
		$this->_name = $name;
		$this->_departament = $departament;
	}
 
	public function getName() {
		return $this->_name;
	}
 
	public function getDepartament() {
		return $this->_departament;
	}
}
 
// имитация стандартного класса Delphi для хранения списка объектов
class TObjectList {
 
	private $_objList;
 
	public function __construct() {
		$this->_objList = array();
	}
 
	public function free() {
		unset($this->_objList);
	}
 
	public function count() {
		return sizeof($this->_objList);
	}
 
	public function add($obj) {
		array_push($this->_objList, $obj);
	}
 
	public function remove($obj) {
		foreach($this->_objList as $k=>$v) {
			if ($obj == $v) {
				unset($this->_objList[$k]);
			}
		}
	}
 
	public function get($index) {
		return $this->_objList[$index];
	}
 
	public function set($index, $obj) {
		$this->_objList[$index] = $obj;
	}
}
 
// класс для хранения сотрудников
class TEmployeeList {
 
	// объект класса "список объектов"
	private $_employeersList;
 
	public function __construct(){
		// создаём объект методы которого будем делегировать
		$this->_employeersList = new TObjectList;
	}
 
	public function getEmployer($index) {
		return $this->_employeersList->get($index);
	}
 
	public function setEmployer($index, TEmployee $objEmployer) {
		$this->_employeersList->set($index, $objEmployer);
	}
 
	public function __destruct() {
		$this->_employeersList->free();
	}
 
	public function add(TEmployee $objEmployer) {
		$this->_employeersList->add($objEmployer);
	}
 
	public function remove(TEmployee $objEmployer) {
		$this->_employeersList->remove($objEmployer);
	}
 
	// последовательный поиск сотрудника по имени
	// через аргумент $offset можно задавать позицию с которой вести поиск.
	// если сотрудник не найден вернёт значение меньше ноля (-1)
	public function getIndexByName($name, $offset) {
		$result = -1; // предполагаем, что его нету в списке
		for ($i = $offset; $i<=count($this->_employeersList->count()); $i++) {
			if ($name == $this->_employeersList->get($i)->getName()) {
				return $i;
			}
		}
	}
}
 
$obj1 = new TEmployee("Танасийчук Степан", "web студия");
$obj2 = new TEmployee("Кусый Назар", "web студия");
$obj3 = new TEmployee("Сорока Орест", "web студия");
 
$objList = new TEmployeeList();
$objList->add($obj1);
$objList->add($obj2);
$objList->add($obj3);
 
echo "<pre>";
print_r($objList);
 
echo "<hr>";
 
$index = $objList->getIndexByName("Кусый Назар", 0);
$obj4 = $objList->getEmployer($index);
print_r($obj4);
 
echo "<hr>";
 
$objList->setEmployer(2, $obj4);
print_r($objList);
echo "</pre>";
?>

JavaScript

Простой пример

function A() {
	this.f = function() {
		alert("A: вызываем метод f()");
	};
	this.g = function() {
		alert("A: вызываем метод g()");
	};
}
 
function C() {
	var a = new A();
	this.f = function() {
		a.f();
	};
	this.g = function() {
		a.g();
	};
}
 
var c = new C();
c.f(); // "A: вызываем метод f()"
c.g(); // "A: вызываем метод g()"

Сложный пример

function A() {
	this.f = function() {
		alert("A: вызываем метод f()");
	};
	this.g = function() {
		alert("A: вызываем метод g()");
	};
}
 
function B() {
	this.f = function() {
		alert("B: вызываем метод f()");
	};
	this.g = function() {
		alert("B: вызываем метод g()");
	};
}
 
function C() {
	// единожды инстанцируем A и B
	var a = new A(); 
	var b = new B();
	var cur = a; // ссылка на текущий объект с реализацией методов; по умолчанию - A
 
	this.toA = function() {
		cur = a;
	};
	this.toB = function() {
		cur = b;
	};
 
	this.f = function() {
		cur.f();
	};
	this.g = function() {
		cur.g();
	};
}
 
var c = new C();
c.f(); // "A: вызываем метод f()"
c.g(); // "A: вызываем метод g()"
c.toB();
c.f(); // "B: вызываем метод f()"
c.g(); // "B: вызываем метод g()"

Нетривиальный пример

function Employee(name, departament) {
	this.getName = function() {
		return name;
	};
	this.getDepartament = function() {
		return departament;
	};
	this.toString = function() {
		// преобразование в строку для удобного дебага
		return "Сотрудник "+ name +", "+ departament;
	};
}
 
function EmployeesList() {
	var employees = [];
 
	this.add = function() {
		// функция принимает произвольное кол-во агрументов
		for (var i=0, l=arguments.length; i<l; i++) {
			if (arguments[i].constructor == Employee) {
				// проверка типа
				employees.push(arguments[i]);
			}
		}
	};
	this.set = function(obj, index) {
		if (obj.constructor == Employee) {
			delete employees[index];
			employees[index] = obj;
		}
	};
	this.remove = function(obj) {
		for (var i=0, l=employees.length; i<l; i++) {
			if (employees[i]==obj) {
				employees.splice(i, 1);
				i--;
				l--;
			}
		}
	};
	this.getByIndex = function(num) {
		return employees[num];
	};
	this.getIndexByName = function(name, offset) {
		// последовательный поиск сотрудника по имени
		// через аргумент offset можно задавать позицию с которой вести поиск. (по умолчанию 0)
		// если сотрудник не найден, вернёт -1
		for (var i = offset || 0, l=employees.length; i<l; i++) {
			if (employees[i].getName()==name) return i;
		}
		return -1;
	};
	this.toString = function() {
		// преобразование в строку для удобного дебага
		var ret = "";
		for (var i=0, l=employees.length; i<l; i++) {
			ret += i +": "+ employees[i] +"\n";
		}
		return ret;
	};
}
 
var o1 = new Employee("Танасийчук Степан", "web студия");
var o2 = new Employee("Кусый Назар", "web студия");
var o3 = new Employee("Сорока Орест", "web студия");
 
var emps = new EmployeesList();
emps.add(o1, o2, o3); // можно добавлять и по-одиночке
alert(emps); // "0: Сотрудник Танасийчук Степан, web студия
             // 1: Сотрудник Кусый Назар, web студия
             // 2: Сотрудник Сорока Орест, web студия"
 
var obj4 = emps.getByIndex( emps.getIndexByName("Кусый Назар") ); // получаем ссылку на сотрудника
alert(obj4); // "Сотрудник Кусый Назар, web студия"
 
emps.set(obj4, 2); // вместо 2го (от ноля) сотрудника вставляем obj4
alert(emps); // "0: Сотрудник Танасийчук Степан, web студия
             // 1: Сотрудник Кусый Назар, web студия
             // 2: Сотрудник Кусый Назар, web студия"
 
emps.remove(obj4); // удаляем сотрудника obj4
alert(emps); // "0: Сотрудник Танасийчук Степан, web студия"

См. также

Литература

  • Джулиан Бакнелл «Фундаментальные алгоритмы и структуры данных в Delphi». Изд. DiaSoft 2003 годen:Delegation pattern

zh:委托模式

Личные инструменты

Served in 0.451 secs.