I have a hierarchy, which I'll simplify greatly, of implementations of interface Value. Assume that I have two implementations, NumberValue, and StringValue.
There is an average operation which only makes sense for NumberValue, with the signature
NumberValue average(NumberValue numberValue){ ... }
At some point after creating such variabl开发者_如何学Pythones and using them in various collections, I need to average a collection which I know is only of type NumberValue, there are three possible ways of doing this I think:
- Very complicated generic signatures which preserve the type info in compile time (what I'm doing now, and results in hard to maintain code)
- Moving the operation to the Value level, and: throwing an unsupportedOperationException for StringValue, and casting for NumberValue.
- Casting at the point where I know for sure that I have a NumberValue, using slightly less complicated generics to insure this.
Does anybody have any better ideas, or a recommendation on oop best practices?
As @tafa said, it seems to me an interface would be a good choice. Based on your signature for average
, I came up with the below.
AveragableValue
public interface AveragableValue<T> extends Value
{
public T average(T value);
}
NumberValue
public class NumberValue implements AveragableValue<NumberValue>
{
private int _n;
public NumberValue(int n)
{
this._n = n;
}
@Override
public void doSomething()
{
// from Value interface
}
@Override
public NumberValue average(NumberValue value)
{
return new NumberValue((this._n + value._n) / 2);
}
}
Then you can have your collection be of type AveragableValue
. Already in your code you must have some kind of if/else
clause somewhere to differentiate NumberValue
and StringValue
to figure out whether to call average
or not. So I don't see how this would be more complicated. The hierarchy make sense - AveragableValue
s are a subtype of Value
, and a NumberValue
is a type of AveragableValue
.
However, that signature for average
doesn't look right. It only takes 2 values (this
and the argument) and averages them. You then lose the total count of things that have been averaged before. So assuming integers as the values (as I did), something like this:
(new NumberValue(4)).average(new NumberValue(8)).average(new NumberValue(12));
would give you the value 9
instead of 8
. Is this what you want? It makes it bad for many calculations done iteratively, as you may be doing with collections.
If you show us some of your code - how these classes are used, the collections holding them, how you are doing averaging right now - I can maybe give a better answer.
I would have create another interface IAveragable which contains the average operation which derives from Value . Then StringValue would implement just Value interface and NumberValue would implement IAveragable.
Then when it is required to use the average operation I would check if the object implements IAveragable.
I'm unable to comment, therefore I'll just post a new answer.
Create an interface for value:
public interface Value<T> {
public T getValue();
}
And one for averagable:
public interface Averagable<T> {
public T average(T value);
}
Then a number value would be something like:
public class NumberValue implements Averagable<Number>, Value<Number>{
public Number average(Number value) {
// do your stuff
}
public Number getValue() {
// do your stuff
}
}
There is no need to let Averagable extend from Value.
精彩评论