Pull to refresh

Hegel4j — диалектические переменные на Java

Reading time6 min
Views1.2K

Что такое диалектическая переменная?


Кто-то возможно скажет: «Что общего у философии и программирования?». Человек недалекий скажет: «Ничего!», а мы с вами задумаемся.

Есть такой раздел философии — диалектика. Было сформулировано три закона диалектики:

1. Закон перехода количественных изменений в качественные
2. Закон единства и борьбы противоположностей
3. Закон отрицания отрицания

Из определения первого закона и родилось понятие диалектической переменной.
Диалектическая переменная — некоторое свойство объекта, от значения которого зависит внутреннее состояние объекта, принадлежность объекта к некоторому классу и, как следствие, его поведение. Так, например, температура является диалектической переменной для классов Water и Ice, а возраст для Child и Mature и т.д.

В данной статье я хочу описать библиотеку для работы с диалектическими переменными hegel4j и показать пример работы с данной библиотекой.

Введение в Hegel4j


Hegel4j это Java-библиотека, которая позволяет описывать диалектические переменные. Для того, чтобы объявить такую переменную надо перед полем некоторого класса объявить аннотацию @Dialectic. Например так:
public class Water{
  @Dialectic(expr="<0",target=Ice.class)
  private float tempetarute;
  ...
}  


* This source code was highlighted with Source Code Highlighter.

В приведенном выше примере описана диалектическая переменная temperature в классе Water.

Первый параметр в аннотации это expr – выражение, которое определяет когда должно происходить преобразование. Формат выражения следующий: <отношение><значение>. Отношение это одна из операций сравнения: <,<=,==,!=,>,>=. Значение это целочисленное значение или действительное число. Как бы подразумевается, что значение переменной стоит слева от выражения. Вот несколько примеров выражений: ">100", "!=7", ">3.1415","<=2.61".

Второй параметр в аннотации это target – как бы говорит, что в случае выполнения условия, данный объект должен вести себя как объект данного класса.

Есть так же третий параметр: transformer – этот параметр задает класс-трансформер, который отвечает за преобразование объекта. По-умолчанию значение равно SimpleObjectTransformer. Однако если Вы хотите реализовать свой «трансформер» то в этом нет никаких проблем, надо лишь реализовать интерфейс ObjectTransformer и передать полученный класс в качестве параметра в аннотацию.

Как это работает?


На данный момент реализация библиотеки не лишена некоторых недостатков, и, в первую очередь, поэтому хочется чтобы, человек, который использует эту библиотеку, представлял, как она устроена. Да и в любом случае, я думаю Хабра сообществу, будет интересно посмотреть на то, как библиотека устроена изнутри.
Во-первых, стоит отметить, что в текущей версии библиотеки предполагается, что классы написаны с соблюдением спецификации JavaBeans то есть для каждой переменной x есть методы setX() и getX(). Поэтому если значение переменной поменяется в каком-либо другом методе объект не поменяет своего класса и состояния.

Далее рассмотрим, как реализовано поведение объекта. Начинается вся магия с аспекта(см. Аспектно-ориентированное программирование), который перехватывает (на данный момент почти все) вызовы конструкторов и анализирует, есть ли в этом классе диалектические переменные. Если класс содержит диалектические переменные, то создается прокси-объект и в контекст вызова конструктора возвращается уже прокси, который, однако, ведет себя как объект исходного класса.
При создании прокси в качестве параметра задается MethodInterceptor – перехватчик вызовов методов прокси-объекта. А внутри этого перехватчика содежится уже настоящий объект, которому перенаправляются вызовы.

Как это использовать?


Библиотека работает с версией Java не ниже 1.6.
Для того чтобы использовать возможности этой библиотеки следует добавить эту библиотеку в java build path, а так же и в AspectJ build path.
Так же для работы потребуются следующие библиотеки:
cglib-nodep
aspectjrt

В качестве иллюстрации приведу пример работы с данной библиотекой.
Допустим у нас есть интерфейс IOrder:
public interface IOrder {

  public long getPrice();

  public void setPrice(long price);
  
  public String getOrderType();
  
  public long getTotal();
}


* This source code was highlighted with Source Code Highlighter.

Обратите внимание на метод getTotal(), он возвращает конечную стоимость заказа.
Еще есть класс Order:
public class Order implements IOrder{
  
  @Dialectic(expr=">=1000",target=VIPOrder.class)
  private long price;

  public long getPrice() {
    return price;
  }

  public void setPrice(long price) {
    this.price = price;
  }

  public Order(long price) {
    super();
    this.price = price;
  }
  
  public String getOrderType(){
    return "simple order";
  }
  
  public long getTotal(){
    return price;
  }
}


* This source code was highlighted with Source Code Highlighter.

А так же VIPOrder, который предоставляет скидку 15% при заказе более 1000 у.е.:
public class VIPOrder implements IOrder
{
  @Dialectic(expr="<1000",target=Order.class)
  private long price;

  public long getPrice() {
    return price;
  }

  public void setPrice(long price) {
    this.price = price;
  }

  public VIPOrder(long price) {
    super();
    this.price = price;
  }
  
  public String getOrderType(){
    return "VIP order";
  }

  @Override
  public long getTotal() {
    return Double.valueOf(price*0.85).longValue();
  }
}


* This source code was highlighted with Source Code Highlighter.


Теперь посмотрим, что будет, если указать разную цену в заказе:
public class Main {

  /**
   * @param args
   */
  public static void main(String[] args) {
    Order order = new Order(154);
    System.out.println(""+order.getOrderType()+" total "+order.getTotal());
    order.setPrice(1200);
    System.out.println(""+order.getOrderType()+" total "+order.getTotal());
    order.setPrice(504);
    System.out.println(""+order.getOrderType()+" total "+order.getTotal());
  }
  
}

* This source code was highlighted with Source Code Highlighter.

Запускаем приложение, и видим, что в зависимости от значения поля price конечная цена рассчитывается по-разному, и в самом деле вызываются методы разных классов, несмотря на то, что был создан объект класса Order и ссылка на объект не менялась:

simple order total 154
VIP order total 1020
simple order total 504


Обратите внимание, что здесь произошло 2 преобразования: сначала был обычный заказ суммой 154 у.е., потом он стал VIP-заказом, и конечная цена получилась со скидкой, а потом, когда была задана цена 504, заказ снова стал обычным заказом.

Для чего это?


Как вы наверное все знаете, есть три столпа ООП: полиморфизм, инкапсуляция и наследование. Однако если взглянуть на реальные приложения, то можно заметить, что полиморфизм используется не так часто. Одна из причин этого заключается в том, что если в классе есть диалектическая переменная, то порой не хочется писать логику преобразования объектов в другие классы, когда приложение еще небольшое кажется значительно проще логику поведения встроить внутрь метода. Например так:

getTotal(){
 if(price>=1000){
  return price*0.85;
 }else{
  return price;
 }
}


* This source code was highlighted with Source Code Highlighter.

Однако чем больше полиморфных методов, тем сложнее будет управлять логикой приложения. Поэтому в таких случаях лучше все же использовать полиморфизм. И чтобы снять с плеч программиста тяжелое бремя написания логики отслеживания значения переменных и преобразования объектов и была написана данная библиотека.

В заключение


Хочется услышать ваши мнения и предложения. В планах на ближайшее будущее добавить поддержку произвольных типов объектов в выражении с использованием JSON("<{id=«123»,name=«sd»}") и сложных выражений типа "<sin(90)/4.3", а так же улучшить производительность.

Скачать и попробовать библиотеку в действии можно тут.
Tags:
Hubs:
Total votes 40: ↑32 and ↓8+24
Comments36

Articles