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:
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.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.