How to define common operations between different objects in java?
I have a number of operands of different types and want to define how these operands can operate on each other. This seems like a generic problem but I fail to find a good approach for implementing it.
Say for a start that I have scalars (i.e. numbers), matrix, and sets of matrix or scalars and that I want to define the plus operation on these:
scalar1.plus(scalar2) returns a scalar equal to scalar1+scalar2,
matrix.plus(scalar1) or scalar.plus(matrix) both return a matrix where 'scalar1' each element has been added to each element of the matrix,
matrixSet.plus(matrix1) returns a set where each matrix has been added to matrix1
And so on... Note that some operations may throw exceptions, such as adding matrix of different sizes or adding sets that do not have the same number of elements.
My goals are the following:
1- reuse as much code as possible (important because I will have many more operations and data types),
2- do as much validation at compile time as possible
3- make it easy to add new data types or operations in the future (for example, I may want to add a vector data type later on)
I initially thought I would define an Operand interface, which my scalar, matrix, and set of these elements would implement, and which would include methods like:
public Operand plus(Operand op);
This would allow be to define the plus operation from the bottom up, starting with the simplest element (scalar):
public Operand plus(Operand operand) {
if (Scalar.class.equals(operand.getClass())) {
return new Scalar(this.value + ((Scalar) operand).getValue());
} else {
// assume the other operand will define the addition for me
// since add is commutative
return operand.plus(this);
}
}
And then for matrix:
public Operand plus(Operand operand) {
if (Scalar.class.equals(operand.getClass())) {
// return a matrix where we add scalar value to all matrix elements
} else if (Matrix.class.equals(operand.getClass())) {
开发者_开发知识库 // return a matrix where we perform a scalar addition between each element
} else {
// assume the other operand will define the addition for me
// since add is commutative
return operand.plus(this);
}
}
The idea being that whenever I introduce a new class implementing the Operand type (say a vector), I will only have to define the plus operation betwen that new operand and all the other existing operands. I.e. I wouldn't have to rewrite the plus method of the Scalar and Matrix classes.
This approach meets goals 1 and 3 but not 2: all operations return Operand objects, and forces me to do a lot of casting and class checking at run time, which doesn't seem like a good practice.
Any better way of approach my problem?
Sounds like you need to use the adapter pattern. You will basically define a generic interface and then adapt the interface in as many ways as needed to suit your needs.
I'm not sure I understand why you're using the Operand interface at all. It seems like your main code-reuse technique is to call an operation on an operand when that operand has already defined the operation. But the use of an interface doesn't help you with that. What am I missing? Doesn't doing it this way involve the same amount of code, but allow nice things like specific return types and exceptions?
Scalar:
public Scalar plus(Scalar operand) {
return new Scalar(this.value + operand.getValue());
}
public Matrix plus(Matrix operand) {
return operand.plus(this);
}
Matrix:
public Operand plus(Scaler operand) {
return new Scalar(this.value + operand.getValue());
}
public Operand plus(Matrix operand){
// return matrix plus matrix
}
After some back and forth, I decided that I wasn't approaching the problem correctly by defining operations for each type of data that I created. It just wasn't working well no matter what I tried.
What I ended up doing was to have one class per operation defined on integers. E.g. Addition, Multiply, Addition8BitSat, etc. In addition, I have one Evaluator class where I define how to derive an operation between two blocks, between a block and an integer, between sets of blocks and integer, etc. based on the result of the operations between on solely integers.
This approach addresses the three issues I listed above: zero redundancy in my code, methods that return results of the proper type, and easily expandable to other types.
The right way of doing this with java is defining operation methods in your data classes and overloading as necessary. If you have a matrix class, that class needs to know how to add another matrix to itself and a scalar to itself. I don't know if you have a particular reason for separating operands and operators but otherwise that's the right way to go. Here's some code to clarify the concept:
class Matrix{
Matrix add(Matrix m){
//add matrix to self
return this;
}
Matrix add(Scalar s){
//add scalar to self
return this;
}
}
精彩评论