So I have some classes like BooleanLogger
, IntLogger
etc., which implement my own Logger
interface.
This is the Logger
interface:
public interface Logger {
String serialise();
Object deserialise(String value);
void setValue(Object value);
}
Here's a snippet of BooleanLogger
:
public class BooleanLogger extends MutableBoolean implements Logger {
private MutableBoolean value;
// ...
@Override
public String serialise() {
return serialise(this.value.booleanValue());
}
public static String serialise(Boolean value) {
return Boolean.toString(value);
}
@Override
public Object deserialise(String value) {
return Boolean.parseBoolean(value);
}
@Override
public void setValue(Boolean value) {
this.value = new MutableBoolean(value);
logChange();
}
@Override
public void setValue(Object value) {
this.value = new MutableBoolean((Boolean) value);
logChange();
}
private void logChange() {
System.out.println(this.value); // temporary
}
}
So the basic idea is essentially to override any setter methods within MutableBoolean
such that we log the new value whenever it's modified. I've implemented that part trivially so far, but that's not what I'm concerned about.
Elsewhere in my code, I have a map Map<String, Logger> loggerMap
. I want to be able to get a Logger
from this map and set its value, given a serialised String value
. For example:
Logger logger = loggerMap.get("myLogger1");
logger.setValue(logger.deserialise(value));
It's done this way because I don't know the type of Logger
I'm getting, so everything needs to work regardless.
Note the reason for this serialising/deserialising is these values are coming from Redis and hence are stored as Strings.
The problem I'm having is that in the individual loggers like BooleanLogger
, it says:
'setValue(T)' in 'org.apache.commons.lang3.mutable.Mutable' clashes with 'setValue(Object)' in 'myLogger.BooleanLogger'; both methods have same erasure, yet neither overrides the other
The only solution I can think of is to no longer extend from MutableBoolean
, MutableInt
etc., and instead just copy all the methods into my own BooleanLogger
, IntLogger
classes. However, I have these classes for a fair few types and it would quickly become cumbersome and frustrating to do it this way. Right now I only have to deal with any methods that change the underlying value.
As far as the logic goes, I've already successfully implemented everything with my public class ArrayLogger<T> extends ArrayList<T> implements Logger {}
as this is the only one that does not extend from a Mutable type that clashes on the setValue
method.
EDIT: I've realised that even if I make my own class, I'll have the same issue just
'setValue(Object)' in 'myLogger.BooleanLogger' clashes with 'setValue(T)' in 'org.apache.commons.lang3.mutable.Mutable'; both methods have same erasure, yet neither overrides the other
Assuming public class BooleanLogger implements Mutable<Boolean>, Serializable, Comparable<MutableBoolean>, Logger {}
So unless I make my own Mutable<T>
type (and possibly others...), I may need a different solution.
The root problem is that your code doesn't actually make sense.
This part, specifically:
You've broken the type system here. That says to me I can call
setValue(whateverObjectIPlease)
on any logger of any stripe and it should just work. Turns out that is incorrect - if I invoke.setValue(x)
on any Logger whose actual type isBooleanLogger
, andx
is anything but aBoolean
instance, it fails.Hence, this is not great API. Now also consider that it fundamentally clashes with apache's Mutable hierarchy and it gets worse.
There's a second fundamental problem in your design.
Your BooleanLogger class is confused about itself and is its own factory.
Specifically, the
deserialize
method has nothing to do with a BooleanLogger instance. It doesn't interact with any field whatsoever, and could have beenstatic
, other than the fact that you want it to participate in class hierarchies and static methods don't.That is what factories are for. They let you abstract non-instance parts of class structures (so, constructors, and static methods).
One way to go is to make a
LoggerFactory
orLoggerType
interface, have exactly 1 instance for each type. You'd have something like:Voila - you can abstract that way to your hearts content.
But, if you find that too complicated, just 'collapse' your setValue and deserialize methods.
Right now your
deserialize
method is lying in the sense that it says it is an instance method but it really isn't.. and yoursetValue
method is lying in the sense that it indicates any object of any stripe will do when that isn't the case either.But... combine the two... and both lies disappear in a puff of smoke. Behold:
Problem solved. If you really insist on having that
setValue
method be part ofLogger
itself, introduce generics. It was your choice to sign up to apache's Mutable type hierarchy, and it uses generics, so - you signed up for that too: