Reputation: 321
I have some code where there is such object as Event, it has a collection of Attributes, which has values which anyone can get by calling the Event method: Double getAttributeValue(Integer index). Until now AtributeValue as in the method's signature was always Double. Now it can be other new Classes and I need to generalize the code so everywhere where any operators applied on Double should work on my new system of classes. Moreover the classes should operate not only as in the old code, one to same class, but also interact with same operators between one to another. for exmaple, the old code operated like this:
private Double calculateDelta(Event event) {
Double firstValue = (Double)event.getAttributeValue(StockEventTypesManager.firstStockMeasurementIndex);
Double secondValue = (Double)event.getAttributeValue(StockEventTypesManager.firstStockMeasurementIndex + 1);
return Math.abs(firstValue - secondValue);
}
The new code should operate something like this:
private AttributeValue calculateDelta(Event event) {
AttributeValue firstValue = (AttributeValue )event.getAttributeValue(StockEventTypesManager.firstStockMeasurementIndex);
AttributeValue secondValue = (AttributeValue )event.getAttributeValue(StockEventTypesManager.firstStockMeasurementIndex + 1);
return Math.abs(AttrValueFunctional.minus(firstValue,secondValue));
}
or something like this (those just suggestions, because I don't know how to design it):
private AttributeValue calculateDelta(Event event) {
AttributeValue firstValue = (AttributeValue )event.getAttributeValue(StockEventTypesManager.firstStockMeasurementIndex);
AttributeValue secondValue = (AttributeValue )event.getAttributeValue(StockEventTypesManager.firstStockMeasurementIndex + 1);
return (firstValue.minus(secondValue)).abs();
}
where this code can be corresponding to each one of those pairs of statements:
boolean isDoubleWrapper = firstValue isinstanceof DoubleWrapper; //true
boolean isDoubleList = secondValue isinstanceof DoublePairsList; //true
boolean isDoubleList = firstValue isinstanceof DoublePairsList; //true
boolean isDoubleWrapper = secondValue isinstanceof DoubleWrapper; //true
boolean isDoubleWrapper = firstValue isinstanceof DoubleWrapper; //true
boolean isDoubleWrapper = secondValue isinstanceof DoubleWrapper; //true
boolean isDoublePairsList = firstValue isinstanceof DoublePairsList; //true
boolean isDoublePairsList = secondValue isinstanceof DoublePairsList; //true
it also could be:
boolean isStrangeObject = firstValue isinstanceof StrangeObject; //true
boolean isDoubleList = secondValue isinstanceof DoublePairsList; //true
boolean isDoubleList = firstValue isinstanceof DoublePairsList; //true
boolean isStrangeObject = secondValue isinstanceof StrangeObject; //true
boolean isStrangeObject = firstValue isinstanceof StrangeObject; //true
boolean isStrangeObject = secondValue isinstanceof StrangeObject; //true
boolean isDoublePairsList = firstValue isinstanceof DoublePairsList; //true
boolean isDoublePairsList = secondValue isinstanceof DoublePairsList; //true
My great design goal is to make it possible to any coder in the future easily add some new classes which could "hide" in AttributeValue, add the needed operational functionality (add, subtract, abs), so there should be good scalability to new classes.
I tried some implementations, one of them generally works (the one showed below), but I don't think it's following the best java design patterns or it has good scalability and easy to add new classes ass I mentioned.
public class Main {
public interface Operand {}
public interface Combiner<T> {
T subtract(T a, T b);
}
public static abstract class MyFunctional {
public static<T extends Operand> T
compute(Operand a, Operand b, Subtracter combiner) {
return (T) combiner.subtract(a, b);
}
private static A subtractAA(A a, A b){
return new A(a.getFirst() - b.getFirst(), a.getSecond()-b.getSecond());
}
private static A subtractAB(A a, B b){
return new A(a.getFirst() - b.getFirst(), a.getSecond()-b.getSecond()-b.getThird());
}
static class
Subtracter implements Combiner<Operand> {
@Override
public Operand subtract(Operand x, Operand y) {
if(x instanceof A && y instanceof A){
return subtractAA((A)x,(A)y);
}
if(x instanceof A && y instanceof B){
return subtractAB((A)x,(B)y);
}
return null;
}
}
}
public static class A implements Operand{
private int a;
private int b;
public A(int a, int b){
this.a=a;
this.b=b;
}
public int getFirst(){
return a;
}
public int getSecond() {
return b;
}
@Override
public String toString() {
return "("+a+" "+b+")";
}
}
public static class B implements Operand {
private int a;
private int b;
private int c;
public B(int a, int b, int c){
this.a=a;
this.b=b;
this.c = c;
}
public int getFirst(){
return a;
}
public int getSecond() {
return b;
}
public int getThird() {
return b;
}
@Override
public String toString() {
return "("+a+" "+b+" "+c+")";
}
}
public static void main(String[] args) {
Operand e = new A(1, 2);
Operand f = new A(3,4);
Operand g = new B(3,4,5);
System.out.println("Hello World");
System.out.println((Object)MyFunctional.compute(e,f, new MyFunctional.Subtracter()));
Operand o = MyFunctional.compute(f,g, new MyFunctional.Subtracter());
System.out.println("Bye");
}
}
That's a kind of a small example of what I tried to do. Can anyone try to change my code or suggest his own code for those simple A,B classes, which will be easy to adjust to new classes (for example if will want to add some simple class C with 4 fields and the addition and subtraction operations between C and itself and between A and C and B and C), which will work as I described here and in the begging (pretty much the same actually).
Redact after a few answers:
The real domain of the new types:
So in the old problem, the Double type meant a deterministic value (its means that this value is known in real world with probability 1.0).
Now I need to replace it with the system of types in the picture.
Now about the operation and operands functionality:
Every two object in the same branch should be operated as it first ancestor with him self.
Two objects of different branches should act like their first ancestors acts with same operation.
So eventually I want to refactor the code to the new assumption that:
event.getAttributeValue(index);
can return any of the classes in the described possible hierarchy. Of course right now I need to write a code that will implement only the two left most high objects and all the operations between them, as subtract, add, and so on. And my goal is to design the correct skeleton of interfaces and classes for this problem.
Another clarification due to the last updated answer:
what I want to do is some kind of wrapper and Functional as Strategy design pattern, something as in the two codes in the next links:
first (not templated example which I need to generalize according to the last updated answer): first
second (something like this, but using Class, Class and both BiFunction, and BinaryOperator according to the suggested solution): second
Upvotes: 1
Views: 153
Reputation: 44328
There is already a class for this: BinaryOperator.
Forget about a wrapper class like AttributeValue. Just let the Event continue to hold simple values like it did before, including Doubles, and pass in the combining operation as a BinaryOperator.
Casting to a generic type (like T
) is an unsafe operation due to type erasure, so you will want to pass the type as well:
private <V> V calculateDelta(Event event,
Class<V> valueType,
BinaryOperator<V> operation) {
V firstValue = valueType.cast(
event.getAttributeValue(
StockEventTypesManager.firstStockMeasurementIndex));
V secondValue = valueType.cast(
event.getAttributeValue(
StockEventTypesManager.firstStockMeasurementIndex + 1));
return operation.apply(firstValue, secondValue);
}
A call to that method would look like this:
Double delta =
calculateDelta(eventToProcess, Double.class, (a, b) -> a - b);
Update:
You point out in a comment that the operands aren’t always of the same type. In that case, you would use BiFunction instead of BinaryOperator:
private <V, U, R> R calculateResult(Event event,
Class<V> firstValueType,
Class<U> secondValueType,
BiFunction<V, U, R> operation) {
V firstValue = firstValueType.cast(
event.getAttributeValue(
StockEventTypesManager.firstStockMeasurementIndex));
U secondValue = secondValueType.cast(
event.getAttributeValue(
StockEventTypesManager.firstStockMeasurementIndex + 1));
return operation.apply(firstValue, secondValue);
}
However, it turns out you can do both! You can have the above method, and also the first version as a convenience, for cases where you expect both values, and the result, to be the same type:
private <V> V calculateResult(Event event,
Class<V> valueType,
BinaryOperator<V> operation) {
return calculateResult(event, valueType, valueType, operation);
}
Clarifications:
BiFunction and BinaryOperator are each functional interfaces—that is, an interface with exactly one abstract method. (static
and default
methods are not abstract methods.)
This means a lambda can be used to represent it. (a, b) -> a - b)
is identical to this:
new BinaryOperator<Double>() {
@Override
public Double apply(Double a, Double b) {
return a - b;
}
}
So, the lambda is in fact the BiFunction or BinaryOperator.
It doesn’t have to be a lambda. It can also be a method reference:
private Double subtract(Double a, Double b) {
return a - b;
}
// ...
Double delta =
calculateResult(eventToProcess, Double.class, this::subtract);
You can, of course, make such a method more complex:
private Double probabilityFor(DiscreteDistribution d, Integer x) {
Map<Integer, Double> probabilities = d.getProbabilities();
return probabilities.getOrDefault(x, 0);
}
// ...
Double probability =
calculateResult(eventToProcess,
DiscreteDistribution.class,
Integer.class,
this::probabilityFor);
You can also define operations on the complex objects themselves:
public class DiscreteDistribution {
private final Map<Integer, Double> probabilities = new HashMap<>();
// ...
public Double probabilityOf(int x) {
return probabilities.getOrDefault(x, 0);
}
}
public class ValueCalculator {
// ...
Double probability =
calculateResult(eventToProcess,
DiscreteDistribution.class,
Integer.class,
DiscreteDistribution::probabilityOf);
}
Upvotes: 2