Proxy: выдвижение вперед другого объекта


Если мы реализуем Proxy, как показано на приведенной выше диаграмме, это будет выглядеть так:

//: proxy:ProxyDemo.java
// Простейшая демонстрация шаблона Proxy.
package proxy;

import junit.framework.*;

interface ProxyBase {
  
void f();
  
  
void g();
  
  
void h();
}

class Proxy implements ProxyBase {
  
private ProxyBase implementation;
  
  
public Proxy() {
     
implementation = new Implementation();
  
}
  
  
// Передаем вызовы методов в реализацию (implementation):
  
public void f() {
     
implementation.f();
  
}
  
  
public void g() {
     
implementation.g();
  
}
  
  
public void h() {
     
implementation.h();
  
}
}

class Implementation implements ProxyBase {
  
public void f() {
     
System.out.println("Implementation.f()");
  
}
  
  
public void g() {
     
System.out.println("Implementation.g()");
  
}
  
  
public void h() {
     
System.out.println("Implementation.h()");
  
}
}

public class ProxyDemo extends TestCase {
  
Proxy p = new Proxy();
  
  
public void test() {
     
// Здесь просто убеждаемся, что это работает
      // без выбрасывания исключений.
     
p.f();
      p.g
();
      p.h
();
  
}
  
  
public static void main(String args[]) {
     
junit.textui.TestRunner.run(ProxyDemo.class);
  
}
}
// /:~

Конечно, необязательно, чтобы реализация Implementation имела такой же интерфейс, как и Proxy; до тех пор, пока Proxy - это нечто, "говорящее за" класс, которому перенаправляет вызовы, но основная идея отражена верно (обратите внимание, что это утверждение конфликтует с определением Proxy в GoF). Однако, это достаточно последовательно, иметь общий интерфейс, так что класс Implementation вынужден реализовать все те же методы, которые вызывает Proxy.

PoolManager с использованием Proxy

//: proxy:PoolManager.java
package proxy;

import java.util.*;

public class PoolManager {
  
private static class PoolItem {
     
boolean inUse = false;
      Object item;
     
      PoolItem
(Object item) {
        
this.item = item;
     
}
   }
  
  
public class ReleasableReference { // Используется для построения прокси
     
private PoolItem reference;
     
private boolean released = false;
     
     
public ReleasableReference(PoolItem reference) {
        
this.reference = reference;
     
}
     
     
public Object getReference() {
        
if (released)
           
throw new RuntimeException(
                 
"Tried to use reference after it was released");
        
return reference.item;
     
}
     
     
public void release() {
        
released = true;
         reference.inUse =
false;
     
}
   }
  
  
private ArrayList items = new ArrayList();
  
  
public void add(Object item) {
     
items.add(new PoolItem(item));
  
}
  
  
// Отличие (улучшение?) подхода в запуске вне элементов:
  
public static class EmptyPoolItem {
   }
  
  
public ReleasableReference get() {
     
for (int i = 0; i < items.size(); i++) {
        
PoolItem pitem = (PoolItem) items.get(i);
        
if (pitem.inUse == false) {
           
pitem.inUse = true;
           
return new ReleasableReference(pitem);
        
}
      }
     
// Получи ошибку, как только попробуем сделать преобразование:
      // return new EmptyPoolItem();
     
return null; // временно
  
}
}
// /:~
//: proxy:ConnectionPoolProxyDemo.java
package proxy;

import junit.framework.*;

interface Connection {
  
Object get();
  
  
void set(Object x);
  
  
void release();
}

class ConnectionImplementation implements Connection {
  
public Object get() {
     
return null;
  
}
  
  
public void set(Object s) {
   }
  
  
public void release() {
   }
// никогда не вызывается напрямую
}

class ConnectionPool { // синглетон
  
private static PoolManager pool = new PoolManager();
  
  
private ConnectionPool() {
   }
// Предотвращаем синтез конструктора
  
  
public static void addConnections(int number) {
     
for (int i = 0; i < number; i++)
        
pool.add(new ConnectionImplementation());
  
}
  
  
public static Connection getConnection() {
     
PoolManager.ReleasableReference rr = (PoolManager.ReleasableReference) pool
            .get
();
     
if (rr == null)
        
return null;
     
return new ConnectionProxy(rr);
  
}
  
  
// Proxy, как вложенный класс:
  
private static class ConnectionProxy implements Connection {
     
private PoolManager.ReleasableReference implementation;
     
     
public ConnectionProxy(PoolManager.ReleasableReference rr) {
        
implementation = rr;
     
}
     
     
public Object get() {
        
return ((Connection) implementation.getReference()).get();
     
}
     
     
public void set(Object x) {
         ((
Connection) implementation.getReference()).set(x);
     
}
     
     
public void release() {
        
implementation.release();
     
}
   }
}

public class ConnectionPoolProxyDemo extends TestCase {
  
static {
     
ConnectionPool.addConnections(5);
  
}
  
  
public void test() {
     
Connection c = ConnectionPool.getConnection();
      c.set
(new Object());
      c.get
();
      c.release
();
  
}
  
  
public void testDisable() {
     
Connection c = ConnectionPool.getConnection();
      String s =
null;
      c.set
(new Object());
      c.get
();
      c.release
();
     
try {
        
c.get();
     
}
     
catch (Exception e) {
        
s = e.getMessage();
         System.out.println
(s);
     
}
     
assertEquals(s, "Tried to use reference after it was released");
  
}
  
  
public static void main(String args[]) {
     
junit.textui.TestRunner.run(ConnectionPoolProxyDemo.class);
  
}
}
// /:~

Динамический прокси (Dynamic Proxy)

В JDK 1.3, бал введен Динамический Прокси. Хотя сначала это немного сложновато, это интригующий инструмент.

Вот небольшой интересный стартовый пример, который работает и доказывает, что действительно, вызов обработчика происходит через прокси. Это здорово, и теперь это в моей голове, но я все еще должен кое-что уяснить для себя, что делать с вызванным обработчиком, прежде чем сделать полезный пример...

// proxy:DynamicProxyDemo.java
// not work in JDK 1.4.1_01
package proxy;

import java.lang.reflect.*;

interface Foo {
  
void f(String s);
  
  
void g(int i);
  
   String h
(int i, String s);
}

public class DynamicProxyDemo {
  
public static void main(String[] clargs) {
     
Foo prox = (Foo) Proxy.newProxyInstance(Foo.class.getClassLoader(),
           
new Class[] { Foo.class }, new InvocationHandler() {
              
public Object invoke(Object proxy, Method method, Object[] args) {
                 
System.out.println("InvocationHandler called:" + "ntMethod = " + method);
                 
if (args != null) {
                    
System.out.println("targs = ");
                    
for (int i = 0; i < args.length; i++)
                       
System.out.println("tt" + args[i]);
                 
}
                 
return null;
              
}
            })
;
      prox.f
("hello");
      prox.g
(47);
      prox.h
(47, "hello");
  
}
}
// /:~
Упражнение: Используя динамический Java-прокси создайте объект, который взаимодействует с простым файлом конфигурации. Например, в файле good_stuff.txt получите следующие записи:
a=1
b=2
c="Hello World"

Чтобы клиентский программист такого NeatPropertyBundle мог потом написать:

NeatPropertyBundle p = new NeatPropertyBundle("good_stuff");
System.out.println
(p.a);
System.out.println
(p.b);
System.out.println
(p.c);

Конфигурационный файл может содержать что угодно, с любыми именами переменных. Динамический прокси либо берет название из карты, либо говорит так или иначе, что это элемент не существует (вероятно, вернув null). Если вы установите параметр, который еще не существует, динамический прокси создаст новый элемент. Метод toString() должен отображать все существующие в настоящий момент элементы.

Упражнение: аналогично предыдущему, используя динамический Java-прокси создайте подключение в файлу DOS Autoexec.bat. Упражнение: Получите SQL запрос, который возвращает данные, затем прочтите метаданные базы. Теперь, для каждой записи предоставьте объект, который имеет атрибуты, соответствующие именам колонок и имеют соответствующий тип данных. Упражнение: Создайте простейший сервер и клиента, который использует XML-RPC. Каждый объект, возвращаемый клиентом, должен использовать концепцию динамического прокси для использования удаленных методов.

Andrea пишет:

Я не совсем уверен насчет упражнений, которые вы предлагаете, за исключением последнего. Дело в том, что я привык думать о привлечении обработчика, как о чем-то, обеспечивающем способность, ортогональную к тем, которые обеспечивает объект позади "прокси".

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

Таким образом, обработчик предоставляет службу, которая ортогональна службе, предоставляемой проксируемым объектом. Ричард (Richard) создал несколько обработчиков в своем примере SmartWord, и они, я думаю, являются лучшими обработчиками повторного вызова. В основном они передают вызов в реальный объект, и если вызов генерирует исключение, он ждет некоторое время, а замет делает это же вызов еще раз, и так три раза. Если все три вызова заканчиваются неудачей, то возвращается исключение. И вы можете использовать такой обработчик _любого_ класса.

Implementation - слишком сложный способ, который вы хотите продемонстрировать. Я использую этот пример только для объяснения, что я понимаю под ортогональными службами.

В вашем списке упражнений только одно, по моему мнению, имеет смысл реализации с использованием динамического прокси - это последнее, относящееся к использованию XML-RPC коммуникации с объектом. Это потому, что механизм, который вы используете для отправки сообщений (XML-RPC) является ортогональным относительно службы, предоставляемой объектом, с которым идет взаимодействие.