I have the following class structure:
a base class 'Message' which contains some common members (/fields),
'Imessage' interface that has some methods that all messages should implement,
a lot of different message classes that extends (inherits) the base 'Message' class and have a lot of fields, an enum for each message type and a factory class which given an enum creates an instance of the proper message class.
The issue is that I'm not sure where/how to implement the setting of values for each of the message classes.
It can't be in it's constructor because in the factory method i need to build generic instances.
Should i implement a 'Create' method for each of the messages that will set all of it's members?
public static Message buildMessage(MessageType messageType)
{
Message message = null;
switch (messageType) //TODO: add all messages!
{
case CONNECT:
message = new ConnectMessage();
break;
case CONNECT_RESPONSE:
message = new ConnectResponseMessage();
break;
case DISCONNECT:
message = new DisconnectMessage();
break;
case FLOWSTART:
message = new FlowStartMessage();
break;
default: return null;
}
return message;
}
The factory pattern should return a valid, fully populated object so adding a Create method for each type of Message object would not be ideal (unless it is called from within buildMessage).
I don't know what you mean when you say:
It can't be in it's constructor because in the factory method i need
to build generic instances.
It's completely valid to initialise an objects like this:
Message message = new ComplexMessage(type, value, something, somethingElse);
Message message = new LessComplexMessage(type, value);
In which case your buildMessage method can take in all objects required to build all the sub types of your message.
If this becomes too complex because there are too many variations of required fields then it might be worth upgrading to the Builder Pattern:
http://javarevisited.blogspot.co.uk/2012/06/builder-design-pattern-in-java-example.html
This is one possible solution. You can write a constructor in Message and override it in the subclasess.
public class Message {
private final String commonField;
public Message(String commonField){
this.commonField = commonField;
}
}
And in the subclassess
public ConnectMessage(String commonField){
super(commonField);
//initialize
}
And in the factory do
public static Message buildMessage(MessageType messageType, String commonValue)
{
Message message = null;
switch (messageType) //TODO: add all messages!
{
case CONNECT:
message = new ConnectMessage(commonValue);
break;
case CONNECT_RESPONSE:
message = new ConnectResponseMessage(commonValue);
break;
case DISCONNECT:
message = new DisconnectMessage(commonValue);
break;
case FLOWSTART:
message = new FlowStartMessage(commonValue);
break;
default: return null;
}
return message;
}
If message definitions are rigid and you are not creating combinations of messages or configuring their properties, then why have a factory class ?
On another note, The enum alone is capable of much:
public class Main {
public static interface IMessage{
public String getName();
}
public static class GoodMessage implements IMessage{
#Override
public String getName() {
return "Good";
}
}
public static class BadMessage implements IMessage{
#Override
public String getName() {
return "Bad";
}
}
public static interface IMsgGen{
public IMessage create();
}
public static enum Messages implements IMsgGen {
GOOD_MSG(GoodMessage.class),
BAD_MSG(BadMessage.class),
CUSTOM_MSG(null){
#Override
public IMessage create() {
return new IMessage() {
#Override
public String getName() {
return "Custom";
}
};
}
};
private final Class<? extends IMessage> mClass;
private Messages(Class<? extends IMessage> aClass) {
mClass = aClass;
}
#Override
public IMessage create() {
try {
return mClass.newInstance();
} catch (Exception e) {
throw new RuntimeException(e.getMessage(),e);
}
}
}
public static void main(String[] args){
IMessage msg = Messages.GOOD_MSG.create();
System.out.println(msg.getName());
msg = Messages.BAD_MSG.create();
System.out.println(msg.getName());
msg = Messages.CUSTOM_MSG.create();
System.out.println(msg.getName());
}
}
Related
You have to design the classes for building a notification system that supports multiple channels such as email, SMS, Whatsapp. It should be easily extensible.
My design :
class Message {
NotificationType type ; //email, sms
msgId;
String content ;
}
MessagingServiceImpl {
static {
//map from notification type to the respective handler
map.put("SMS",new SMSHandler());
map.put("Email",new EmailHandler();
}
void processMessage(Message message) {
Handler handler = map.get(message.getNotificationType();
handler.handleMessage();
}
}
public abstract class Handler {
public abstract void handle(Mesage message) ;
}
public EmailHandler extends Handler {
public void handle(Message message) {
System.out.println("Sending email"): // similar class for phone.
}
Note: This design was rejected in the interview.
Questions:
Should we make Message abstract - EmailMessage,SMSMessage etc ?
Note that the content of the message varies depending on the channel. For eg. In email,the content will be large. For SMS,it is much smaller. So,should Message be astract?
How can you support a new channel eg..telegram messages with minimal changes?
Of course I'm not in the mind of your reviewer, but he could argue about you breaking the "O" of the "SOLID" principles, which is the Open-Closed principle and states that:
"software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification".
The problem with your solution is that if you need to support a new channel you need to add another instance to the NotificationType Enum.
Let's start from the interfaces:
public interface Message {
//no need to define any methods here
}
public interface MessageHandler {
void handleMessage(Message message);
Class<?> getSupportedMessageType();
}
public interface MessageService {
void processMessage(Message message);
}
It is already possible to define a definitive version of the MessageServiceImpl class which will not need to change each time we need to support a new channel.
public class MessageServiceImpl implements MessageService
{
private final Map<Class<?>, MessageHandler> handlers = new HashMap<>();
public MessageServiceImpl(ICollection<MessageHandler> handlers) {
foreach(MessageHandler handler : handlers) {
this.handlers.put(handler.getSupportedMessageType(), handler);
}
}
public void processMessage(Message message) {
if (!this.handlers.containsKey(message.getType())) throw new InvalidArgumentException();
this.handlers.get(message.getType()).handleMessage(message);
}
With the help of a dependency injection framework it might be also possible to let DI handle new message handlers implementations.
Then we can define an abstract class to abstract the message handler:
public abstract class AbsMessageHandler<T implements Message> implements MessageHandler {
private final Class<T> supportedMessageType;
protected AbsMessageHandler(Class<T> supportedMessageType) {
this.supportedMessageType = supportedMessageType;
}
protected abstract void handleMessageInternal(T message);
public Class<?> getSupportedMessageType() { return supportedMessageType; }
public void handleMessage(Message message)
{
if (message == null) throw new InvalidArgumentException();
if (message.getType() != getSupportedMessageType()) throw new InvalidArgumentException();
handleMessageInternal((T) message);
}
}
The only thing left to define are the messages and message handlers implementations:
public EmailMessage implements Message {
private string from;
private string to;
private string cc;
private string subject;
private string messageBody;
//getters and setters
}
public EmailMessageHandler extends AbsMessageHandler<EmailMessage> {
public EmailMessage() {
super(EmailMessage.class);
}
protected void handleMessageInternal(EmailMessage message) {
// do what you like
}
}
Each time you need to add support for a new channel you only need to add a new couple of classes, one to implement the Message interface and one to extend the AbsMessageHandler abstract class.
I would like to create an abstract factory. here is what I tried.
//abstract class Worker
public abstract class Worker {
String phoneNumber;
String firstName;
String lastName;
String workerType;
String ifu;
String imageParth;
//....
public String getWorkerType() {
return workerType;
}
}
// Electrician class which extends worker
package worker.domain.worker;
public class Electrician extends Worker{
public Electrician() {}
public Electrician(String phoneNumber, String firstName, String lastName, String ifu, String workerType,
String imageParth) {
super(phoneNumber, firstName, lastName, ifu,workerType, imageParth);
}
public String getWorkerType() {
return "Electrician";
}
}
//Mason class
package worker.domaine.worker;
public class Mason extends Worker{
public Mason() {};
public Mason(String phoneNumber, String firstName, String lastName, String ifu,String workerType,
String imageParth) {
super(phoneNumber, firstName, lastName, ifu, workerType, imageParth);
}
String getworkerType() {
return "Mason";
}
}
// interface WorkerAbstractFactory
package worker.domaine.worker;
public interface WorkerAbstractFactory {
Worker createWorker(String typeWorker);
}
//
public class WorkerFactory implements WorkerAbstractFactory{
#Override
public Worker createWorker(String typeWorker) {
Worker worker = null;
if(worker != null) {
switch (typeWorker) {
case "Electrician":
Electrician electrician =new Electrician();
electrician = new Electrician (electrician.getPhoneNumber(), electrician.getFirstName(), electrician.getLastName(), electrician.getIfu(), electrician.getWorkerType(),electrician.getImageParth());
case "Mason":
Mason mason =new Mason();
mason = new Mason (mason.getPhoneNumber(), mason.getFirstName(), mason.getLastName(), mason.getIfu(), mason.getworkerType(),mason.getImageParth());
}}
//app class
public class WorkerFactoryProvider {
public static WorkerAbstractFactory getWorkerFactory(String workerCategory) {
//WorkerFactory workerFactory = new WorkerFactory();
WorkerFactory workerFactory = new WorkerFactory();
if (workerCategory != null) {
switch (workerCategory) {
case "Electrician":
Worker worker1 = workerFactory.createWorker("Electrician");
worker1.getWorkerType();
String a=worker1.getWorkerType();
System.out.println(a);
case "Mason":
Worker worker2 = workerFactory.createWorker("Mason");
worker2.getWorkerType();
String b=worker2.getWorkerType();
System.out.println(b);
}
}
return null;
}
do you think it could work like that? now, if I really want a concrete object, how could it be done? because I would like to write for example a method to calculate the pay of each worker according to type for example how could I use my abstract Factory in the method to return me each type.
You have a single class hierarchy of Worker types. To instantiate those you can just use a standalone factory class, you don't need an abstract factory here. For example this would be sufficient:
public class WorkerFactory {
public Worker createWorker(String workerType) {
switch (workerType) {
case "Electrician": return new Electrician();
case "Mason": return new Mason();
}
}
}
The abstract factory pattern is more elaborate, and allows injecting different concrete factories for related hierarchies of objects, so that the client doesn't need to be aware of the difference. For example you could have an abstract TransportationFactory:
interface Transportation {
void travelTo(String destination);
}
interface TransportationFactory {
Transportation simple();
Transportation luxurious();
}
And two concrete implementations (matching two different but similar class hierarchies):
class WaterTransportationFactory {
Transportation simple() {
return new Kayak();
}
Transportation luxurious() {
return new Yacht();
}
}
And:
class LandTransportationFactory {
Transportation simple() {
return new Bike();
}
Transportation luxurious() {
return new RaceCar();
}
}
The benefit of this pattern is that the client can be configured to use water or land transportation (or a new air transportation that is added later) without the need to undergo any changes:
class Client {
private TransportationFactory transportationFactory;
public Client(TransportationFactory transportationFactory) {
this.transportationFactory = transportationFactory;
}
public void travel(String destination) {
transportationFactory.simple().travelTo(destination);
}
public void travelInStyle(String destination) {
transportationFactory.luxurious().travelTo(destination);
}
}
EDIT: You could change the simple/luxurious methods to match the style of your example with the getWorkerType method. I prefer to avoid the conditional logic if possible and let the created classes determine their availability themselves. This decouples even further, allowing hierarchy members to be added with minimal code changes:
enum TransportationType {
SIMPLE, LUXURIOUS
}
interface Transportation {
void travelTo(String destination);
// allow the class to specify its own type
TransportationType getType();
}
// intermediate interface to distinguish Water from Land
interface WaterTransportation extends Transportation {
}
class Kayak implements WaterTransportation {
void travelTo(String destination) {
// splash splash
}
TransportationType getType() {
return TransportationType.SIMPLE;
}
}
class WaterTransportationFactory {
private WaterTransportation[] waterTransportations;
// Inject all available beans implementing WaterTransportation
// e.g. using Spring or some other dependency injection mechanism
public WaterTransportationFactory(WaterTransportation[] waterTransportations) {
this.waterTransportations = waterTransportations;
}
public Transportation create(TransportationType type) {
for(WaterTransportation waterTransportation : waterTransportations) {
if (waterTransportation.getType() == type) {
// we are returning the same instance every time
// this could be ok for singleton beans
// but if you really need a fresh instance you could use builders (see below)
return waterTransportation;
}
}
throw new IllegalArgumentException("No implementation for WaterTransportation type=" + type);
}
}
An alternative with builders:
KayakBuilder implements WaterTransportationBuilder {
KayakBuilder name(String name) { ... };
KayakBuilder weight(String weightInKg) { ... };
KayakBuilder year(String yearBuilt) { ... };
KayakBuilder speed(String averageSpeed) { ... };
Kayak build() { return kayak; }
}
For more on Builders see this full exposition of the Builder pattern
class WaterTransportationFactory {
private WaterTransportationBuilder[] builders;
// Inject all available WaterTransportationBuilders
// e.g. using Spring or some other dependency injection mechanism
public WaterTransportationFactory(WaterTransportationBuilder[] builders) {
this.builders = builders;
}
// extra arguments can be passed to build the instance
public Transportation create(TransportationType type, String name, int weightInKg, int yearBuilt, int averageSpeed) {
for(WaterTransportationBuilder builder: builders) {
if (builder.getType() == type) {
return builder
.name(name)
.weight(weightInKg)
.year(yearBuilt)
.speed(averageSpeed)
.build();
}
}
throw new IllegalArgumentException("No implementation for WaterTransportation type=" + type);
}
}
I have a class:
#Getter
#Setter
public class Message {
private String sender;
private Set<String> receivers;
private String text;
}
The Message class can be extended by EmailMessage and SMSMessage having their own additional fields.
There are 2 services - EmailService and SMSService.
They both do the following operations:
Take the message object which contains sender and receiver user ids.
EmailService calls another service to transform the user ids to email ids. SMSService does the same to transform the user ids to phone numbers.
The Message object should be transformed to EmailMessage and SMSMEssage in their respective services.
Send the EmailMessage and SMSMessage.
I've been going through many design patterns to solve this problem. But I didn't find any such pattern to transform the fields of an object and/or convert the parent object to a child object.
The only design patterns that come close are Strategy and Decorator.
I'm using Strategy Pattern for using either EmailService or SMSService at runtime. It is solving just one part of my problem. Will Decorator Pattern help for solving the other?
Edit: Adding the current implementation
public abstract class AbstractMessageService<T extends Message> {
#KafkaHandler(isDefault = true)
public final void consume(Message message) {
T convertedMessage = getMessageConverterStrategy().convert(message);
send(convertedNotificationMessage);
}
protected abstract void send(T message) throws Exception;
protected abstract MessageConverterStrategy getMessageConverterStrategy();
}
#KafkaListener(
topics = "topicName",
groupId = "email-group")
public class EmailService extends AbstractMessageService<EmailMessage> {
#Override
protected void send(EmailMessage message) {
// Some logic to send email.
}
#Override
protected MessageConverterStrategy getMessageConverterStrategy() {
return new EmailMessageConverterStrategy();
}
}
#KafkaListener(
topics = "topicName",
groupId = "sms-group")
public class SMSService extends AbstractMessageService<SMSMessage> {
#Override
protected void send(SMSMessage message) {
// Some logic to send sms.
}
#Override
protected MessageConverterStrategy getMessageConverterStrategy() {
return new SMSMessageConverterStrategy();
}
}
public interface MessageConverterStrategy<T extends Message> {
T convert(Message message);
}
In the SMSMessageConverterStrategy and EmailMessageConverterStrategy, I want to use other design pattern to do the conversion of Message to SMSMessage and EmailMessage respectively.
Because you have to convert almost everything, I think Message and Email and SMS are not related. I think you should use Factory method like this:
public class Test {
public static void main(String[] args) throws Exception {
Message m = new Message();
Email email = Email.from(m);
EmailService.sendMail(email);
}
}
class Message {
private String sender;
private Set<String> receivers;
private String text;
}
class Email {
private String senderMailId;
private String text;
private Set<String> receiverMailIds;
//make it private
private Email() {
}
public static Email from(Message m) {
Email email = new Email();
email.senderMailId = ""; //call MappingService.toEmailId(m.getSender());
//do rest of the conversion and processing
return email;
}
}
class EmailService {
public static void sendMail(Email e) {
//send the mail
}
}
I would suggest the use of Factory method pattern.
A MessageService which would be implemented by SmsService and EmailService.
interface MessageService {
Message transform(Message message);
}
class SmsService implements MessageService {
#Override
SmsMessage transform(Message message) {
// do appropriate transformation and return.
}
}
class EmailService implements MessageService {
#Override
EmailMessage transform(Message message) {
// do appropriate transformation and return.
}
}
Then use a factory to create the appropriate service. (Assuming an enum MessageType):
public class MessageServiceFactory {
public MessageService getService(Message m, MessageType type){
switch(type) {
case SMS:
return new SmsService(m);
case EMAIL:
return new EmailService(m);
default:
// throw some exception.
}
}
}
Then to do the actual transformation:
Message convertMessage(Message m, MessageType type) {
return new MessageServiceFactory().getService(m, type)
.transform();
}
I am using the below mentioned code to publish into a topic. It is of the format convertAndSend(Destination destination, Object message)
Event event;
jmsTemplate.convertAndSend(topic, event);
My current Event interface is something like this,
public interface Event {
public boolean isEmpty();
public AcEventDatafileTransaction getDatafileTransaction();
public AcEventObjectTransaction getObjectTransaction();
boolean isDatafileTransaction();
boolean isObjectTransaction();
boolean isRdbmsTransaction();
String getTransactionId();
}
Its implementation is something like this,
public class EventPublisherImpl implements Event {
private final AcTransactionRecord transactionRecord;
private final Ac ac;
private final String[] actualTemplates;
private final String[] curveTemplates;
public AcEventPublisherImpl(final Ac ac,
final String[] actualTemplates,
final String[] curveTemplates,
final AcTransactionRecord acTransactionRecord) {
this.ac = ac;
this.transactionRecord = acTransactionRecord;
this.actualTemplates = actualTemplates;
this.curveTemplates = curveTemplates;
}
#Override
public boolean isEmpty() {
return transactionRecord.isEmpty();
}
#Override
public AcEventDatafileTransaction getDatafileTransaction() {
if (isDatafileTransaction()) {
return new AcEventDatafileTransactionPublisherImpl(transactionRecord.getDatafileTransaction());
}
return null;
}
#Override
public AcEventObjectTransaction getObjectTransaction() {
if (isObjectTransaction()) {
return new AcEventObjectTransactionPublisherImpl(ac, actualTemplates, curveTemplates, transactionRecord.getObjectOperations());
}
return null;
}
#Override
public boolean isDatafileTransaction() {
return transactionRecord.getType() == AcTransactionRecord.DATAFILE_TRANS;
}
#Override
public boolean isObjectTransaction() {
return transactionRecord.getType() == AcTransactionRecord.OBJECT_TRANS;
}
#Override
public boolean isRdbmsTransaction() {
return transactionRecord.getType() == AcTransactionRecord.RDBMS_TRANS;
}
#Override
public String getTransactionId() {
if (transactionRecord != null) {
return Integer.toString(transactionRecord.getNumber());
}
return "";
}
}
How do I convert it into a serializable form?
My whole objective is to publish Event object into topic. For that I am using convertAndSend method. That method requires an object of a serialized class, which I currently dont have. It is what I am trying to achieve.
I have looked at examples where a class with few attributes is serialized using message converter https://examples.javacodegeeks.com/enterprise-java/spring/spring-framework-jmstemplate-example/
Please suggest a method through code or point to the appropriate example and how that helps me.
The overloaded methods convertAndSend() and receiveAndConvert() in JmsTemplate delegate the conversion process to an instance of the MessageConverter interface. This interface defines a simple contract to convert between Java objects and JMS messages. The default implementation SimpleMessageConverter supports conversion between String and TextMessage, byte[] and BytesMesssage, and java.util.Map and MapMessage.
So, to support custom type we have to create custom converter which implements MessageConverter to convert Event object to one of supported message type. The simplest solution is to register MappingJackson2MessageConverter provided by spring which support converting object to TextMessage in form of json.
#Bean // Serialize message content to json using TextMessage
public MessageConverter jacksonJmsMessageConverter() {
MappingJackson2MessageConverter converter = new MappingJackson2MessageConverter();
converter.setTargetType(MessageType.TEXT);
converter.setTypeIdPropertyName("_type");
return converter;
}
You can take a look how the serialization process is done on the source code in case you want to create your own converter.
To receive the message, the easiest way is to use #JmsListener annotation, spring will implicitly convert the message to its java type using the registered converter
#Component
public class Receiver {
#JmsListener(destination = "dest")
public void receiveMessage(Event event) {
// do whatever you need with the event
}
}
another way is to use javax.jms.MessageListener but you need to manually convert the message yourself.
#Component
public class ExampleListener implements MessageListener {
#Autowired
private ObjectMapper objectMapper;
public void onMessage(Message message) {
if (message instanceof TextMessage) {// we set the converter targetType to text
try {
String json = ((TextMessage) message).getText(); // here you have your event object as json string
Event event = objectMapper.readValue(json, Event.class); // convert back to event object
}
catch (JMSException ex) {
throw new RuntimeException(ex);
}
}
else {
throw new IllegalArgumentException("Message must be of type TextMessage");
}
}
Ref:
MappingJackson2MessageConverter
https://spring.io/guides/gs/messaging-jms/
I have a Java client which wants to communicate with a device through messages over serial communication. The client should be able to use a clean API, abstracting the ugly details of the serial communication. The client can send many types of messages through that API and gets responses. I'm searching for advice which way is best to implement this API.
For simplicity, say we have only two message types: HelloMessage which triggers a HelloResponse and InitMessage which triggers an InitResponse (in reality, there are many more)
Designing the API (that is, the Java abstraction of the device) I could have:
One method per message type:
public class DeviceAPI {
public HelloResponse sendHello(HelloMessage){...}
public InitResponse sendInit(InitMessage){...}
... and many more message types ....
This is nicely type safe. (It could also be many times the same send() method, overloaded, but that's about the same). But it is very explicit, and not very flexible - we cannot add messages without modification of the API.
I could also have a single send method, which takes all message types:
class HelloMessage implements Message
class HelloResponse implements Response
...
public class DeviceAPI {
public Response send(Message msg){
if(msg instanceof HelloMessage){
// do the sending, get the response
return theHelloResponse
} else if(msg instanceof ...
This simplifies the API (only one method) and allows for additional Message types to be added later without changing the API. At the same time, it requires the Client to check the Response type and cast it to the right type.
Client code:
DeviceAPI api = new DeviceAPI();
HelloMessage msg = new HelloMessage();
Response rsp = api.send(msg);
if(rsp instanceOf HelloResponse){
HelloResponse hrsp = (HelloResponse)rsp;
... do stuff ...
This is ugly in my opinion.
What do you recommend? Are there other approaches which give cleaner results?
References welcome! How did others solve this?
Here is a way to do it in type-safe (and extensible) way using generics:
public interface MessageType {
public static final class HELLO implements MessageType {};
}
public interface Message<T extends MessageType> {
Class<T> getTypeClass();
}
public interface Response<T extends MessageType> {
}
public class HelloMessage implements Message<MessageType.HELLO> {
private final String name;
public HelloMessage(final String name) {
this.name = name;
}
#Override
public Class<MessageType.HELLO> getTypeClass() {
return MessageType.HELLO.class;
}
public String getName() {
return name;
}
}
public class HelloResponse implements Response<MessageType.HELLO> {
private final String name;
public HelloResponse(final String name) {
this.name = name;
}
public String getGreeting() {
return "hello " + name;
}
}
public interface MessageHandler<T extends MessageType, M extends Message<T>, R extends Response<T>> {
R handle(M message);
}
public class HelloMessageHandler
implements MessageHandler<MessageType.HELLO, HelloMessage, HelloResponse> {
#Override
public HelloResponse handle(final HelloMessage message) {
return new HelloResponse(message.getName());
}
}
import java.util.HashMap;
import java.util.Map;
public class Device {
#SuppressWarnings("rawtypes")
private final Map<Class<? extends MessageType>, MessageHandler> handlers =
new HashMap<Class<? extends MessageType>, MessageHandler>();
public <T extends MessageType, M extends Message<T>, R extends Response<T>>
void registerHandler(
final Class<T> messageTypeCls, final MessageHandler<T, M, R> handler) {
handlers.put(messageTypeCls, handler);
}
#SuppressWarnings("unchecked")
private <T extends MessageType, M extends Message<T>, R extends Response<T>>
MessageHandler<T, M, R> getHandler(final Class<T> messageTypeCls) {
return handlers.get(messageTypeCls);
}
public <T extends MessageType, M extends Message<T>, R extends Response<T>>
R send(final M message) {
MessageHandler<T, M, R> handler = getHandler(message.getTypeClass());
R resposnse = handler.handle(message);
return resposnse;
}
}
public class Main {
public static void main(final String[] args) {
Device device = new Device();
HelloMessageHandler helloMessageHandler = new HelloMessageHandler();
device.registerHandler(MessageType.HELLO.class, helloMessageHandler);
HelloMessage helloMessage = new HelloMessage("abhinav");
HelloResponse helloResponse = device.send(helloMessage);
System.out.println(helloResponse.getGreeting());
}
}
To add support for a new message type, implement MessageType interface to create a new message type, implement Message, Response and MessageHandler interfaces for the new MessageType class and register the handler for the new message type by calling Device.registerHandler.
I've got a fully working example now of what you want:
To define the types of messages:
public interface MessageType {
public static class INIT implements MessageType { }
public static class HELLO implements MessageType { }
}
Base Message and Response classes:
public class Message<T extends MessageType> {
}
public class Response<T extends MessageType> {
}
Create custom init messages and responses:
public class InitMessage extends Message<MessageType.INIT> {
public InitMessage() {
super();
}
public String getInit() {
return "init";
}
}
public class InitResponse extends Response<MessageType.INIT> {
public InitResponse() {
super();
}
public String getInit() {
return "init";
}
}
Create custom hello messages and responses:
public class HelloMessage extends Message<MessageType.HELLO> {
public HelloMessage() {
super();
}
public String getHello() {
return "hello";
}
}
public class HelloResponse extends Response<MessageType.HELLO> {
public HelloResponse() {
super();
}
public String getHello() {
return "hello";
}
}
The DeviceAPI:
public class DeviceAPI {
public <T extends MessageType, R extends Response<T>, M extends Message<T>> R send(M message) {
if (message instanceof InitMessage) {
InitMessage initMessage = (InitMessage)message;
System.out.println("api: " + initMessage.getInit());
return (R)(new InitResponse());
}
else if (message instanceof HelloMessage) {
HelloMessage helloMessage = (HelloMessage)message;
System.out.println("api: " + helloMessage.getHello());
return (R)(new HelloResponse());
}
else {
throw new IllegalArgumentException();
}
}
}
Note that it does require an instanceof-tree, but you need that to handle what kind of message it is.
And a working example:
public static void main(String[] args) {
DeviceAPI api = new DeviceAPI();
InitMessage initMsg = new InitMessage();
InitResponse initResponse = api.send(initMsg);
System.out.println("client: " + initResponse.getInit());
HelloMessage helloMsg = new HelloMessage();
HelloResponse helloResponse = api.send(helloMsg);
System.out.println("client: " + helloResponse.getHello());
}
Output:
api: init
client: init
api: hello
client: hello
UPDATE: Added example on how to get input from the messages the client wants to send.
You could have a system of message handlers, and your DeviceAPI could choose which handler is suitable for the incoming message; and delegate it to the appropriate message handler:
class DeviceAPI {
private List<Handler> msgHandlers = new ArrayList<Handler>();
public DeviceAPI(){
msgHandlers.add(new HelloHandler());
//Your other message handlers can be added
}
public Response send(Message msg) throws Exception{
for (Handler handler : msgHandlers) {
if (handler.canHandle(msg)){
return handler.handle(msg);
}
}
throw new Exception("No message handler defined for " + msg);
}
}
The HelloHandler would look like:
interface Handler<T extends Message, U extends Response> {
boolean canHandle(Message message);
U handle(T message);
}
class HelloHandler implements Handler<HelloMessage, HelloResponse> {
#Override
public boolean canHandle(Message message) {
return message instanceof HelloMessage;
}
#Override
public HelloResponse handle(HelloMessage message) {
//Process your message
return null;
}
}
Ditto for your other messages. I'm sure you could make it more elegant, but the idea still remains the same - donot have one monster method with ifs; instead use polymorphism.
I don't think this is ugly at all:
if(rsp instanceOf HelloResponse){
HelloResponse hrsp = (HelloResponse)rsp;
...
else if ...
as long as you don't have like 100 different responses. You can encapsulate many kind of responses in one, depending on the data they have. For example:
class GenericResponse{
private String message;
private int responseType;
...
}
I have developed some multiplayer games and this is a good way to do it.
In case you have too many different types of messages, you just can use generic java types, like the example of skiwi above.
hope it helps