We had a factory method like below, which worked fine, but when the case statements increased, We had issues with SONAR.
class MessageFactory{
public Message create(int uuid) {
switch (uuid) {
case FIRST_MESSAGE_ID:
return new FirstMessage();
.
case SECOND_MESSAGE_ID:
return new SecondMessage();
.
default:
return null;
}}}
class Main{
MessageFactory factory = new MessageFactory();
Message message1=factory.get(FIRST_MESSAGE_ID); // needs a new instance everytime
.
.
MessageFactory factory = new MessageFactory();
Message message2=factory.get(FIRST_MESSAGE_ID); // needs a new instance everytime
}
To avoid the SONAR complaints we moved the object creation to a map.
public Message create(int uuid) {
Map<Integer, Message> map = new HashMap<>();
map.put(FIRST_MESSAGE_ID, new FirstMessage());
.
.
.
map.put(SECOND_MESSAGE_ID, new SecondMessage());
}
if(map.containsKey(uuid)){
return map.get(uuid);
}
return null;
}
The only drawback of this is that every time when I try creating objects using create(int uuid), the whole "map" is initialized every time with all my 100 objects. Is there any way to defer creating objects only when the key is fetched? Or any other optimized way to solve this issue.
NOTE: I need a new instance of objects everytime I cannot use a static map, as it will return the same object instance every time, instead of creating a new object for that key when asked for.
Can we use lambda functions here to fetch new object everytime only when the key is fetched?
CodePudding user response:
You may create a map of uuid to Supplier
instead, so that you only need to create a readonly static unmodified map once. After geting the supplier by the uuid from the map you simply call the get()
method to get a new instance. I think this matches the "defer creating object" you expect.
Supplier references:
- https://docs.oracle.com/javase/8/docs/api/java/util/function/Supplier.html
- https://www.google.com/amp/s/www.geeksforgeeks.org/supplier-interface-in-java-with-examples/amp/
CodePudding user response:
The only drawback of this is that every time when I try creating objects using create(int uuid), the whole "map" is initialized every time with all my
100
objects
Firstly, declare the map as a static
field (it seems like its contents is unrelated to a particular instance, if so it should be static). It would be generated only once when the class gets loaded into memory.
Secondly, extract the logic responsible for initializing the map into a separate method.
NOTE: I need a new instance of objects everytime
To meat this requirement, the Map
should store not actual objects, but Supplier
s for every kind of Message
.
That's how it might be implemented:
public static final Map<Integer, Supplier<Message>> MESSAGE_BY_UUID = init();
public static Map<Integer, Supplier<Message>> init() {
Map<Integer, Supplier<Message>> map = new HashMap<>();
// your initialization logic
map.put(FIRST_MESSAGE_ID, FirstMessage::new);
map.put(SECOND_MESSAGE_ID, SecondMessage::new);
...
return map;
}
public Message create(int uuid) {
return MESSAGE_BY_UUID.get(uuid).get();
}
Note that your if-else
statement with map.containsKey
check is redundant. Method get()
would return null
in case if provided key is not present in the map.
Spiking of other alternatives, the map can be initialized inline using method Map.ofEntries()
which expects varargs of Map.Entry
objects.
The downside of this approach is that it would be basically a switch
in disguise, meaning that it would iterate over its keys in linear fashion. That's how it's implemented internally - it isn't hash-based, but maintains an array of alternating keys and values. With every invocation of the get()
an immutable map returned by Map.ofEntries()
would need to check up to 100
UUID, in contrast with HashMap
which would need to check only a couple (I said couple because collisions might occur).
CodePudding user response:
If there are fixed set of uuid and Message object pairs, make the Map a static final variable and move out of the method, instead of placing it inside method. Like below:
class Main {
static final Map<String, Message> messageMap = Map.of(
FIRST_MESSAGE_ID, new FirstMessage(),
.
.
.
SECOND_MESSAGE_ID, new SecondMessage()
);
}
usage:
Main.messageMap.get(uuid);
CodePudding user response:
Perhaps the following will work for you:
- create an instance of the map but initialize it upon first call of
create()
. - If the message isn't found, return a default message. This acts as a safeguard in cased you specify an incorrect
uuid
. - This will work whether the map is declared static or not, but it does defer the creation as required.
- you can also move the creation code outside the
create()
method but it will be created when the class is initialized.
Map<Integer, Message> map = null;
public Message create(int uuid) {
if (map == null) {
map = new HashMap<>();
map.put(FIRST_MESSAGE_ID, new FirstMessage());
.
map.put(SECOND_MESSAGE_ID, new SecondMessage());
...
}
return map.getOrDefault(uuid, someDefaultMessage);
}