What is a dialectical variable?
Someone may say: "What is common to philosophy and programming?". A short-handed person will say: “Nothing!”, And we will think about it.
There is such a section of philosophy - the dialectic. Three laws of dialectics were formulated:
1. The law of transition of quantitative changes into qualitative
2. The law of unity and struggle of opposites
3. The law of negation of negation
')
From the definition of the first law the concept of a dialectical variable was born.
A dialectical variable is a property of an object, the value of which determines the internal state of an object, the
belonging of an object to a certain class and, as a result, its behavior. For example, temperature is a dialectical variable for the Water and Ice classes, and age for Child and Mature, etc.
In this article I want to describe a library for working with dialectic variables hegel4j and show an example of working with this library.
Introduction to Hegel4j
Hegel4j is a Java library that allows you to describe dialectical variables. In order to declare such a variable, you must declare the
@Dialectic annotation before the field of some class. For example:
public class Water{
@Dialectic(expr="<0",target=Ice. class )
private float tempetarute;
...
}
* This source code was highlighted with Source Code Highlighter .
In the example above, the dialectic temperature variable in the Water class is described.
The first parameter in the annotation is
expr - an expression that determines when the conversion should occur. The format of the expression is: <relation> <value>. Attitude is one of the comparison operations: <, <=, ==,! =,>,> =. Value is an integer or real number. It is as if the value of the variable is implied to the left of the expression. Here are some examples of expressions: "> 100", "! = 7", "> 3.1415", "<= 2.61".
The second parameter in the annotation is
target — as if to say that if the condition is fulfilled, this object should behave like an object of this class.
There is also a third parameter:
transformer - this parameter sets the transforming class that is responsible for transforming the object. By default, the value is SimpleObjectTransformer. However, if you want to implement your “transformer”, then there are no problems with this, you just need to implement the
ObjectTransformer interface and pass the resulting class as a parameter to the annotation.
How it works?
At the moment, the implementation of the library is not without some flaws, and, first of all, therefore, it would be desirable for the person who uses this library to imagine how it works. And in any case, I think Habra community, it will be interesting to look at how the library is arranged from the inside.
First, it is worth noting that in the current version of the library it is assumed that the classes are written in compliance with the JavaBeans specification, that is, for each variable x there are setX () and getX () methods. Therefore, if the value of a variable changes in some other method, the object does not change its class and state.
Next, we consider how the object's behavior is implemented. All magic begins with an aspect (see Aspect-Oriented Programming), which intercepts (at the moment almost all) the calls of designers and analyzes whether there are dialectical variables in this class. If the class contains dialectical variables, then a proxy object is created and the proxy is returned to the context of the constructor's call, which, however, behaves like an object of the original class.
When creating a proxy, the MethodInterceptor is set as a parameter. It is an interceptor of calls to the proxy method. And inside this interceptor there is already a real object to which calls are redirected.
How to use it?
The library works with Java version 1.6 or higher.
In order to use the features of this library, you should add this library to the java build path, as well as to the AspectJ build path.
The following libraries will also be required for work:
cglib-nodepaspectjrtAs an illustration, I will give an example of working with this library.
Suppose we have an IOrder interface:
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 .
Pay attention to the getTotal () method, it returns the final cost of the order.
There is also an Order class:
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 .
As well as VIPOrder, which provides a 15% discount when ordering more than 1000 cu.
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 .
Now let's see what happens if you specify a different price in the order:
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 .
We start the application and see that depending on the value of the price field, the final price is calculated differently, and in fact methods of different classes are called, despite the fact that the object of the Order class was created and the link to the object did not change:
simple order total 154
VIP order total 1020
simple order total 504
Please note that 2 conversions took place here: first there was a regular order with a sum of 154 cu, then it became a VIP order, and the final price turned out to be discounted, and then, when the price was set at 504, the order became an ordinary order again.
What is this for?
As you probably all know, there are three pillars of OOP: polymorphism, encapsulation and inheritance. However, if you look at real-world applications, you can see that polymorphism is not used so often. One of the reasons for this is that if there is a dialectic variable in a class, then sometimes you don’t want to write the logic of converting objects to other classes, when the application is still small it seems much easier to embed the logic of behavior inside the method. For example:
getTotal(){
if (price>=1000){
return price*0.85;
} else {
return price;
}
}
* This source code was highlighted with Source Code Highlighter .
However, the more polymorphic methods, the more difficult it will be to manage the logic of the application. Therefore, in such cases it is better to use polymorphism. And in order to remove from the shoulders of the programmer the heavy burden of writing the logic of tracking the value of variables and the transformation of objects, this library was written.
Finally
I would like to hear your opinions and suggestions. Plans for the near future add support for arbitrary types of objects in expressions using JSON ("<{id =" 123 ", name =" sd "}") and complex expressions like "<sin (90) /4.3", as well as improve productivity.
Download and try the library in action
here .