Наследование vs аггрегация -> Наследование + аггрегация
От: Trean Беларусь http://axamit.com/
Дата: 03.08.06 20:46
Оценка:
Время от времени, в целях углубления познаний в Java и сопутствующих библиотек
решаю разного рода задачки.
В очередной раз, столкнувшись с проблемой, наследование vs аггрегация, подумалось а
почему бы не реализовать аггрегацию с элементами наследования, т.е. с одной стороны
получить гибкость декораторов (оберток) с другой стороны сохранить динамическую диспетчеризацию
в том самом виде как она существует при наследовании (согласно закона диалектики про
борьбу и единство противоположностей).

Простейший пример того, что хотелось получить:

/** Базовый интерфейс */
public interface Algorithm {
   void foo();
   void goo();
}

/** Реализация (одна из N возможных) */
public class AlgorithmImpl implements Algorithm {

  public void foo() {
    System.out.println("AlgorithmImpl->foo {");
    goo();
    System.out.println("}");
  }

  public void goo() {
    System.out.println("AlgorithmImpl->goo {}");
  }

}

/** Декоратор, один на все N реализаций. Добавляем новые методы, в 
    переопределенных методах вызываем свой код
    и опционально делегируем вызов другому объекту */
public class AlgorithmDecorator implements Algorithm {

  private Algorithm superObject; // объект которому мы опционально делегируем вызовы

  ....

  public void foo() {
    System.out.println("AlgorithmDecorator->foo {");
    superObject.foo();
    System.out.println("} ");
  }

  public void goo() {
    System.out.println("AlgorithmDecorator->goo {");
    superObject.goo();
    System.out.println("} ");
  }

  /** Новый метод */
  public void hoo() {
    System.out.println("AlgorithmDecorator->hoo {}");  
  }


Если делать обычный декоратор, то мы не можем перехватить методы, вызываемые в объекте, которому мы делегируем вызов. Например, при вызове AlgorithmDecorator->foo() мы вызываем AlgorithmImpl->foo(), однако вызов AlgorithmImpl->goo() из AlgorithmImpl->foo() мы "поймать" уже не можем, хотя в ряде случаев хотелось бы. Если делать наследование, то все нормально, за исключением того, что для каждой реализации интерфейса Algorithm придется писать свой декоратор, что не есть круто.

Подумалось, что реализация возможность перехвата методов от обернутого класса могла быть полезна при реализации TemplateMethod, когда есть несколько реализаций одного и того же алгоритма. И при использовании шаблона Observer, когда есть базовый класс и наследники базового класса, и мы хотим иметь возможность получать уведомления о событиях от всех классов, но при этом не включать код генерации событий (fireBlaBla) в этот самый базовый класс, т.е. добавить "аспект" получения уведомлений по необходимости, так чтобы базовый класс об этом не знал, не нагружать его лишним кодом.

Немного сумбурно и запутанно, но надеюсь общая идея понятна. Хотелось бы от уважаемого All узнать, где еще можно было бы применить такую штуку с пользой.
Re: Наследование vs аггрегация -> Наследование + аггрегация
От: kl Германия http://stardog.com
Дата: 03.08.06 21:47
Оценка:
Здравствуйте, Trean, Вы писали:


T>Немного сумбурно и запутанно, но надеюсь общая идея понятна. Хотелось бы от уважаемого All узнать, где еще можно было бы применить такую штуку с пользой.


Хм, может я уже сплю, но не очень понятно. Метод hoo() ты сделал public, т.е. подразумевается, что где-то в коде, ты явно кастуешь Algorithm к AlgorithmDecorator для того чтобы вызвать hoo? А зачем? Обертка она тем и хороша, что прозрачная и в идеале никто ничего про имплементацию знать не должен. Это во-первых.
Потом, я не очень понял, а как будет реализовываться это: "однако вызов AlgorithmImpl->goo() из AlgorithmImpl->foo() мы "поймать" уже не можем"?
А главное — складывается ощущение, что основная мотивация выражена здесь:

И при использовании шаблона Observer, когда есть базовый класс и наследники базового класса, и мы хотим иметь возможность получать уведомления о событиях от всех классов, но при этом не включать код генерации событий (fireBlaBla) в этот самый базовый класс, т.е. добавить "аспект" получения уведомлений по необходимости, так чтобы базовый класс об этом не знал, не нагружать его лишним кодом.


Но тогда причем тут вообще наследование vs агрегация — создал InvocationHandler для интерфейса Algorithm, далее динамические прокси радостно генерятся на все его имплементации. Уведомления "по-необходимости" — тоже просто: нужны уведомления — сгенерил проксю для конкретного экземпляра, не нужны — не сгенерил. Совершенно прозрачно для всех.
Или я что-то не понял?
no fate but what we make
Re[2]: Наследование vs аггрегация -> Наследование + аггрегац
От: Trean Беларусь http://axamit.com/
Дата: 03.08.06 22:26
Оценка:
Здравствуйте, kl, Вы писали:

kl>Здравствуйте, Trean, Вы писали:



T>>Немного сумбурно и запутанно, но надеюсь общая идея понятна. Хотелось бы от уважаемого All узнать, где еще можно было бы применить такую штуку с пользой.


kl>Хм, может я уже сплю, но не очень понятно. Метод hoo() ты сделал public, т.е. подразумевается, что где-то в коде, ты явно кастуешь Algorithm к AlgorithmDecorator для того чтобы вызвать hoo? А зачем? Обертка она тем и хороша, что прозрачная и в идеале никто ничего про имплементацию знать не должен. Это во-первых.


Вы бы видели в каком состоянии я это реализовал (в 3 часа ночи)

hoo() это расширение функциональности интерфейса Algorithm, не более того, я мог его вообще не писать.

kl>Потом, я не очень понял, а как будет реализовываться это: "однако вызов AlgorithmImpl->goo() из AlgorithmImpl->foo() мы "поймать" уже не можем"?


Перехват вызова AlgorithmImpl->goo() из AlgorithmImpl->foo() в AlgorithmDecorator->foo() можно сделать (на CGLIB), проверял, но я пока не хотел его постить так как с самой идеей не все чисто Разумеется класс AlgorithmImpl не знает, что его методы перехватывают.

kl>А главное — складывается ощущение, что основная мотивация выражена здесь:


kl>

kl>И при использовании шаблона Observer, когда есть базовый класс и наследники базового класса, и мы хотим иметь возможность получать уведомления о событиях от всех классов, но при этом не включать код генерации событий (fireBlaBla) в этот самый базовый класс, т.е. добавить "аспект" получения уведомлений по необходимости, так чтобы базовый класс об этом не знал, не нагружать его лишним кодом.


kl>Но тогда причем тут вообще наследование vs агрегация — создал InvocationHandler для интерфейса Algorithm, далее динамические прокси радостно генерятся на все его имплементации.


Мне такой подход не нравится тем, что он менее объектно-ориентированный что ли, т.е. мне придется в интерцептор метода запихивать код бизнес-логики, а если он сложный и отличается для каждого перехватываемого метода (не просто вызов скажем logger.log()), то придется писать развесистые if чтобы определять какой метод мы перехватили. Или можно еще как-то (в рамках синтаксиса java)?

kl>Уведомления "по-необходимости" — тоже просто: нужны уведомления — сгенерил проксю для конкретного экземпляра, не нужны — не сгенерил. Совершенно прозрачно для всех.

kl>Или я что-то не понял?

Как с минимумом усилий это сделать, если добавляемая в прокси-методах логика разная для разных перехватываемых методов. Как сделать красиво и остаться в рамках java (без отдельного компилятора как в AspectJ). Вот если бы аннотации можно было бы для генерации кода использовать...
Re[3]: Наследование vs аггрегация -> Наследование + аггрегац
От: kl Германия http://stardog.com
Дата: 03.08.06 22:46
Оценка:
Здравствуйте, Trean, Вы писали:


kl>>Хм, может я уже сплю, но не очень понятно. Метод hoo() ты сделал public, т.е. подразумевается, что где-то в коде, ты явно кастуешь Algorithm к AlgorithmDecorator для того чтобы вызвать hoo? А зачем? Обертка она тем и хороша, что прозрачная и в идеале никто ничего про имплементацию знать не должен. Это во-первых.


T>Вы бы видели в каком состоянии я это реализовал (в 3 часа ночи)


Лучше на ты. Это у тебя 3 ночи, а у меня на 8 часов меньше и спать еще рано

T>hoo() это расширение функциональности интерфейса Algorithm, не более того, я мог его вообще не писать.


Вопрос в том, является ли это расширением функциональности оборачиваемого объекта. Если да — то код работающий с объектом теперь обязан знать о существовании некой обертки с дополнительными методами. Это ломает всю идею прозрачности...


kl>>Но тогда причем тут вообще наследование vs агрегация — создал InvocationHandler для интерфейса Algorithm, далее динамические прокси радостно генерятся на все его имплементации.


T>Мне такой подход не нравится тем, что он менее объектно-ориентированный что ли, т.е. мне придется в интерцептор метода запихивать код бизнес-логики, а если он сложный и отличается для каждого перехватываемого метода (не просто вызов скажем logger.log()), то придется писать развесистые if чтобы определять какой метод мы перехватили. Или можно еще как-то (в рамках синтаксиса java)?


Насчет "менее объектно-ориентированный" — согласен. Не согласен насчет бизнес-логики. Мне кажется, что ее в перехватчиках вообще быть не должно. Приведу пример:
Был у меня реализован валидатор для персистенс-слоя веб-приложения. Т.е. есть сложное дерево бизнес-объектов, которые пришли из View и должны быть отвалидированы прежде отправлены в БД через ORM. Причем валидация не простая (а-ля проверить формат даты) а сложная, может включать запросы к БД и т.д. При этом были религиозные убеждения почему это все должно было быть в java а не в stored procedures — но не суть.
Как я это сделал (исходники не покажу — они принадлежат моей бывшей конторе):
Был главный класс Validator который поддерживал внутреннюю структуру данных в которой хранил список бизнес-объектов для валидации. После чего рекурсивно пробегал по списку и для каждого объекты вызывал сконфигуренный список бизнес-правил (каждон правило — отдельный класс). Идея же в том — КАК валидатор узнавал, какие объекты надо валидировать. Через прокси. Благодаря тому, что все бизнес-объекты обязаны были быть java bean'ами, считалось, что любой вызов setter'а ведет к изменению состояния объекта. Invocation handler перехватывал все сеттеры и помечал объекты как требующие валидации. При это если при валидации один объект поменял состояние другого — тот ревалидируется опять. И т.д. Идея частично заимствована у BC4J.
Но почему я тут об этом пишу. Все эти декораторы/перехватчики и т.д. не должны содержать бизнес-логику! Они просто выполняют некое очень просто и короткое действие — типа пометить объект, записать в лог и т.д. Все остальное — не их дело. А если насчет огромных if'ов в перехватчиках — так может стоит разделить интерфейсы на меньшие и создать перехватчики на каждый интерфейс?
no fate but what we make
Re: Наследование vs аггрегация -> Наследование + аггрегация
От: Andrei N.Sobchuck Украина www.smalltalk.ru
Дата: 04.08.06 06:17
Оценка:
T>Немного сумбурно и запутанно, но надеюсь общая идея понятна. Хотелось бы от уважаемого All узнать, где еще можно было бы применить такую штуку с пользой.

Именно такая схема работает в ООП языках основанных не на классах, а на прототипах. Например, в Self применяется схема с множеством родительских (parent) объектов (то что ты обозвал superObject), то есть множественное наследование, и с возможностью менять родительские объекты "на лету". В JavaScript может быть один родитель, и, afaik, "на лету" его не поменяеш.

Как это ни странно, но, оказалось, что есть либа для Java реализующая полноценное делегирование (то, что тебе хочется) — Delegator.
http://www.smalltalk.ru | << RSDN@Home 1.1.4 stable SR1 rev. 568>>
Я ненавижу Hibernate
Автор: Andrei N.Sobchuck
Дата: 08.01.08
!
Re[4]: Наследование vs аггрегация -> Наследование + аггрегац
От: Trean Беларусь http://axamit.com/
Дата: 04.08.06 07:19
Оценка:
Здравствуйте, kl, Вы писали:

kl>Здравствуйте, Trean, Вы писали:



kl>>>Хм, может я уже сплю, но не очень понятно. Метод hoo() ты сделал public, т.е. подразумевается, что где-то в коде, ты явно кастуешь Algorithm к AlgorithmDecorator для того чтобы вызвать hoo? А зачем? Обертка она тем и хороша, что прозрачная и в идеале никто ничего про имплементацию знать не должен. Это во-первых.


T>>Вы бы видели в каком состоянии я это реализовал (в 3 часа ночи)


kl>Лучше на ты. Это у тебя 3 ночи, а у меня на 8 часов меньше и спать еще рано


ok

T>>hoo() это расширение функциональности интерфейса Algorithm, не более того, я мог его вообще не писать.


kl>Вопрос в том, является ли это расширением функциональности оборачиваемого объекта. Если да — то код работающий с объектом теперь обязан знать о существовании некой обертки с дополнительными методами. Это ломает всю идею прозрачности...


Зачем? Был у меня интерфейс Window, у которого был метод move(), и допустим 10 других. Было две реализации WindowLinux и WindowWindows. Теперь я захотел добавить метод collapse() я создаю декоратор который данное действие позволяет выполнять для обоих реализаций. При этом они не знают о том, что их декорировали Декоратор может не только просто что-то куда-то делегировать, а может кроме того добавлять функциональность, которую по ряду причин не хочется добавлять в базовый класс (например, там уже 50 методов). Разумеется контракт базового класса он поддерживает полностью (http://www.citforum.ru/SE/project/pattern/#3.1.2 см. КонкретныйДекоратор2). Здесь, конечно, можно обойтись обычным декоратором.

kl>>>Но тогда причем тут вообще наследование vs агрегация — создал InvocationHandler для интерфейса Algorithm, далее динамические прокси радостно генерятся на все его имплементации.


T>>Мне такой подход не нравится тем, что он менее объектно-ориентированный что ли, т.е. мне придется в интерцептор метода запихивать код бизнес-логики, а если он сложный и отличается для каждого перехватываемого метода (не просто вызов скажем logger.log()), то придется писать развесистые if чтобы определять какой метод мы перехватили. Или можно еще как-то (в рамках синтаксиса java)?


kl>Насчет "менее объектно-ориентированный" — согласен. Не согласен насчет бизнес-логики. Мне кажется, что ее в перехватчиках вообще быть не должно. Приведу пример:


[skipped]

kl>Но почему я тут об этом пишу. Все эти декораторы/перехватчики и т.д. не должны содержать бизнес-логику! Они просто выполняют некое очень просто и короткое действие — типа пометить объект, записать в лог и т.д. Все остальное — не их дело. А если насчет огромных if'ов в перехватчиках — так может стоит разделить интерфейсы на меньшие и создать перехватчики на каждый интерфейс?


А я вот не соглашусь опять же: http://www.citforum.ru/SE/project/pattern/#3.1.2

Вот мой код (нужна CGLIB и ASM):

public interface Algorithm {

  /** Does something **/
  void foo();

  /** Does something **/
  void goo();

}



public class AlgorithmImpl implements Algorithm {

  /**
   * Prints out string and calls {@code goo} method.
   */
  public void foo() {
    System.out.println("AlgorithmImpl->foo {");
    goo();
    System.out.println("}");
  }

  /**
   * Prints out a message.
   */
  public void goo() {
    System.out.println("AlgorithmImpl->goo {}");
  }

}



public class AlgorithmDecorator implements Algorithm {

  private Algorithm superObject;

  /**
   * Constructs AlgorithmDecorator.
   */
  public AlgorithmDecorator() {
    this(AlgorithmImpl.class);
  }

  /**
   * Constructs AlgorithmDecorator.
   * 
   * @param clazz super class to enhance
   */
  public AlgorithmDecorator(Class clazz) {
    // this can be removed from AlgorithmDecorator class
    SuperInterceptor interceptor = new SuperInterceptor(clazz, this);
    superObject = (Algorithm)interceptor.getEnhanced();
  }

  /**
   * Prints out message and calls super's method {@code foo}
   */
  public void foo() {
    System.out.println("AlgorithmDecorator->foo {");
    superObject.foo();
    System.out.println("} ");
  }

  /**
   * Prints out message and calls super's method {@code goo}
   */
  public void goo() {
    System.out.println("AlgorithmDecorator->goo {");
    superObject.goo();
    System.out.println("} ");
  }

}


public class Test {
  public static void main(String[] args) {

    /** Adapter object we want to delegate calls */
    Algorithm adapter = new AlgorithmDecorator();
    adapter.foo();
    adapter.goo();
  }
}




package com.axamit.test;

import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodProxy;
import net.sf.cglib.core.Signature;

import java.lang.reflect.Method;
import java.util.Map;
import java.util.HashMap;

/**
 * <p>SuperIncidentor</p>
 * <p/>
 * <p>Copyright (c) 2006 Axamit</p>
 *
 * @author Peter Melnikov
 * @version 1.0
 * <p/>
 * Date: Aug 3, 2006
 * Time: 2:07:33 AM
 */
public class SuperInterceptor implements MethodInterceptor {

  private Object adapter; // Adapter object
  private final Map<Signature, Method> map = new HashMap<Signature, Method>(4);
  private Object enhanced; // enhanced object

  /**
   * Constructs SuperInterceptor.
   *
   * @param clazz      original ("super") class to enhance
   * @param interfaces interfaces of original class which we would like to intercept
   *                   (we could extract them from {@code clazz} object as well )
   * @param adapter    object we want to delegate methods calls
   */
  public SuperInterceptor(Class clazz, Class[] interfaces, Object adapter) {

    this.adapter = adapter;
    Enhancer enhancer = new Enhancer();
    enhancer.setSuperclass(clazz);
    enhancer.setCallback(this);

    HelpInterceptor helpIntrcpt = new HelpInterceptor(enhancer.create());
    enhancer = new Enhancer();
    enhancer.setInterfaces(interfaces);
    enhancer.setCallback(helpIntrcpt);
    enhanced = enhancer.create();
  }

  public SuperInterceptor(Class clazz, Object adapter) {
    this(clazz, clazz.getInterfaces(), adapter);
  }

  /**
   * Gets enhanced object.
   *
   * @return enhanced object
   */
  public Object getEnhanced() {
    return enhanced;
  }

  public Object intercept(Object object, Method method, Object[] params, MethodProxy methodProxy) throws Throwable {
    try {
      // Invoke delegate's method
      Signature sig = methodProxy.getSignature();
      Method m = map.get(sig);
      if(m == null) {
        m = adapter.getClass().getMethod(method.getName(), method.getParameterTypes());
        map.put(sig, m); 
      }
      return m.invoke(adapter, params);
    } catch (NoSuchMethodException e) {
      return methodProxy.invokeSuper(object, params);
    }
  }

  /**
   * Helper class
   */
  private class HelpInterceptor implements MethodInterceptor {

    private Object enhanced; // Enhanced object
    private final Map<Signature, MethodProxy> map = new HashMap<Signature, MethodProxy>(4);

    private HelpInterceptor(Object enhanced) {
      this.enhanced = enhanced;
    }

    public Object intercept(Object object, Method method, Object[] params, MethodProxy methodProxy) throws Throwable {
      try {
        Signature sig = methodProxy.getSignature();
        MethodProxy proxy = map.get(sig); // try to get MethodProxy for enhanced object
        if (proxy == null) { // if not cached yet
          proxy = MethodProxy.find(enhanced.getClass(), sig); // find from Class object
          map.put(sig, proxy); // and cache it
        }
        return proxy.invokeSuper(enhanced, params); // call to enhanced object's super method
      } catch (NoSuchMethodException e) {
        return methodProxy.invokeSuper(object, params);
      }
    } // intercept
  } // HelpInterceptor

}


Copyright (c) 2006 Axamit
Author Peter Melnikov
Re[2]: Наследование vs аггрегация -> Наследование + аггрегац
От: Trean Беларусь http://axamit.com/
Дата: 04.08.06 07:20
Оценка:
Здравствуйте, Andrei N.Sobchuck, Вы писали:

T>>Немного сумбурно и запутанно, но надеюсь общая идея понятна. Хотелось бы от уважаемого All узнать, где еще можно было бы применить такую штуку с пользой.


ANS>Именно такая схема работает в ООП языках основанных не на классах, а на прототипах. Например, в Self применяется схема с множеством родительских (parent) объектов (то что ты обозвал superObject), то есть множественное наследование, и с возможностью менять родительские объекты "на лету". В JavaScript может быть один родитель, и, afaik, "на лету" его не поменяеш.


ANS>Как это ни странно, но, оказалось, что есть либа для Java реализующая полноценное делегирование (то, что тебе хочется) — Delegator.



Ага, спасибо.
Вот моя реализация:
http://www.rsdn.ru/Forum/Message.aspx?mid=2042053&amp;only=1
Автор: Trean
Дата: 04.08.06
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.