[CISYSTEM] [JAVA] [REFLECTION]
Today I woke up with an idea how to solve a repeating-myself-problem with code reflection.
I did not work with reflection yet, but I know what it can do for some time now. Since CISystem is my private project to learn new things I decided to give it a try.
In CISystem I have a code structure I call my statemachine. It is a very simple statemachine, it only knows about the two values of type
to decide which function call, but this structure is growing with the number of values of these types.
Before this structure my code was a little bit unorganized, the code I show You here is a snapshot of my approach to refactor it.
I know this refactoring is not done yet as I now see a nice structure, but lots of repeated code statements.
You will see what I mean, it is trivial.
There is this function deciding to process a response or request :
public CIData process(CIData data) throws Exception {
CIData response = null;
switch (data.messageType) {
case Response:
response = processResponse(data);
break;
case Request:
response = processRequest(data);
break;
default:
throw new Exception("CIStateMachine does not process this type of message");
}
return response;
}
and it is calling one of these two, which are growing with every new DataType :
private CIData processRequest(CIData data) throws Exception {
CIData response;
switch (data.dataType) {
case ConfigNetworkProposal:
response = processConfigNetworkProposalRequest(data);
break;
case ConfigNetworkData:
response = processConfigNetworkDataRequest(data);
break;
case Dice:
response = processDiceRequest(data);
break;
case Message:
response = processMessageRequest(data);
default:
throw new Exception("processRequest does not support this DataType");
}
return response;
}
private CIData processResponse(CIData data) throws Exception {
CIData response;
switch (data.dataType) {
case ConfigNetworkProposal:
response = processConfigNetworkProposalResponse(data);
break;
case ConfigNetworkData:
response = processConfigNetworkDataResponse(data);
break;
case Dice:
response = processDiceResponse(data);
break;
case Message:
response = processMessageResponse(data);
default:
throw new Exception("processResponse does not process this DataType");
}
return response;
}
I guess you have already noticed there is a pattern how I use MessageType and DataType to define the function names and that this is hard to read when it grows, as it is dull, boring.
Don't misunderstand me, the code is not bad, but imagine another 30 cases which all look the same and you would be the one who has to take care for maintaining this ... wouldn't you want something final here, something which does not grow linear with the number of actions to be implemented?
My idea was :
Uncle Bob says in his Clean Code book a class shall only do one thing. It will take some time to make CISystem fit this requirement, but there are some obvious violations which can be repaired.
I had already planned to have one class for every function called in my statemachine, they just need to implement the same interface, so I only need to create the corresponding object and call a common process-function which would be defined by the interface.
For every class I only need to add the corresponding name in the DataType enumeration.
The CIData class, where MessageType and DataType are defined, is
package com.wartbar.data;
import java.io.Serializable;
public class CIData implements Serializable {
public enum DataType {
Dice,
ConfigNetworkProposal,
ConfigNetworkData,
Message
}
public enum MessageType {
Request,
Response
}
public static int getDataTypeLength() {
return DataType.values().length;
}
public static int getMessageTypeLength() {
return MessageType.values().length;
}
public static String getDataTypeName(int position) {
return DataType.values()[position].name();
}
public static String getMessageTypeName(int position) {
return MessageType.values()[position].name();
}
public void setResponseMessage(String message) {
messageType = MessageType.Response;
this.message = message;
}
public Integer diceValue = 0;
public DataType dataType = null;
public MessageType messageType = null;
public String message = null;
public CIConfigNetwork configNetwork = null;
}
I have implemented it for one case, for the request of a message.
The classes doing the real work, which replace the process-functions, implement the interface CIAction :
package com.wartbar.action;
import com.wartbar.data.CIData;
public interface CIAction {
public CIData process(CIData data);
}
and this is my example implementation for an CIAction class :
package com.wartbar.action;
import com.wartbar.data.CIData;
public class RequestMessage implements CIAction {
public CIData process(CIData data) {
System.out.println(data.message);
CIData response = new CIData();
response.messageType = CIData.MessageType.Response;
response.dataType = CIData.DataType.Message;
response.message = "Hello there, Requester!";
return response;
}
}
I have decided to add a cache for the CIAction objects as they are stateless and don't need to created more than once.
Yes, static functions would have done the job as well, but let's just guess this will not be the last change here...
Now the statemachine looks much different :
package com.wartbar.state;
import com.wartbar.action.CIAction;
import com.wartbar.data.CIData;
import java.lang.reflect.Constructor;
import java.util.HashMap;
public class CIStateMachine {
private static final String actionPackage = "com.wartbar.action.";
private HashMap<String, CIAction> map = new HashMap<>();
private String getActionName(CIData data) {
return data.messageType.name() + data.dataType.name();
}
public CIData process(CIData data) throws Exception {
return map.get(getActionName(data)).process(data);
}
private CIAction createActionObject(String fullName) {
CIAction action = null;
try {
Class<?> classType = Class.forName(fullName);
Constructor<?> ctor = classType.getConstructor();
action = (CIAction)ctor.newInstance();
} catch (Exception e) {
System.out.println("Exception in createActionObject : " + e.getMessage());
}
return action;
}
private void initMap(){
for (int m = 0; m < CIData.getMessageTypeLength(); m++) {
for (int d = 0; d < CIData.getDataTypeLength(); d++) {
String name = CIData.getMessageTypeName(m) + CIData.getDataTypeName(d);
String fullName = actionPackage + name;
CIAction action = createActionObject(fullName);
map.put(name, action);
}
}
}
public CIStateMachine() {
initMap();
}
}
This is my code for running it, just to see it works :
public static void testReflection() {
CIStateMachine stateMachine = new CIStateMachine();
CIData data = new CIData();
data.messageType = CIData.MessageType.Request;
data.dataType = CIData.DataType.Message;
data.message = "This is a request";
try {
CIData response = stateMachine.process(data);
System.out.println(response.message);
} catch (Exception e) {
System.out.println(e.getMessage());
}
}
and this is the output, as expected with exceptions for the unimplemented classes :
Exception in createActionObject : com.wartbar.action.RequestDice
Exception in createActionObject : com.wartbar.action.RequestConfigNetworkProposal
Exception in createActionObject : com.wartbar.action.RequestConfigNetworkData
Exception in createActionObject : com.wartbar.action.ResponseDice
Exception in createActionObject : com.wartbar.action.ResponseConfigNetworkProposal
Exception in createActionObject : com.wartbar.action.ResponseConfigNetworkData
This is a request
Hello there, Requester!
and this is the part which does the reflection :
CIAction action = null;
try {
Class<?> classType = Class.forName(fullName);
Constructor<?> ctor = classType.getConstructor();
action = (CIAction)ctor.newInstance();
} catch (Exception e) {
System.out.println("Exception in createActionObject : " + e.getMessage());
}
I have added a small explanation how it works here in my Java blog.
These are the sources which have helped me this week :
Stack Overflow: Creating an instance using the class name and calling constructor
The Blog
|
|
|
|
My Technical Blogs
|
|
|
|
|
|
|
|
|
|
|
|
Projects
|
|
|
|
Blogs Of Friends
|
|
|
|
|
|
CV/About
|
|
|