Question

UPDATED: Added one more question (Question #4).

Hi all,

I'm building myself a custom emailing utility. Now, to obey Single Responsibility Principle, I want to have the following classes: MailerSender, MailProvider and EmailObject. The MailSender is more of a delegate, check it out below:

public class MailSender {
    private IMailProvider mailProvider;

    public void setMailProvider (IMailProvider provider) {
        this.mailProvider = provider;
    }

    // option to set it up during construction
    public MailSender (IMailProvider provider) {
        this.mailProvider = provider;
    }

    public void sendEmail(EmailObject obj) {
        if(mailProvider == null)
            throw new RuntimeException("Need a mail provider to send email.");

        try {
            mailProvider.send(obj);
        } catch (Exception e) {
            // do something here
        }
    }
}

The MailSender requires an IMailProvider email provider that does the work of sending the email. Find it below:

public interface IMailProvider {
    public void sendEmail(EmailObject obj);
}

public class SMTPEmailProvider implements IMailProvider {
    public void sendEmail(EmailObject obj) {
        // use SMTP to send email using passed-in config
    }
}

public class JMSEmailProvider implements IMailProvider {
    public void sendEmail(EmailObject obj) {
        // push emails to JMS queue to be picked up by another thread
    }
}

I have defined a few strategies above, but it can be extended to any number. Since the MailSender can change it's provider at any time, it effectively implements the strategy pattern right?

The EmailObject is a POJO containing relavent email information:

public class EmailObject {
    private String to;
    private String from;
    private String cc;
    private String subject;
    private String body;

    // setters and getters for all
}

Client code will then look like:

MailSender sender = new MailSender(new SMTPMailProvider());
sender.send(new EmailObject());
sender.setMailProvider(new JMSMailProvider());
sender.send(new EmailObject());

My questions are:

1. Have I implemented the Strategy Pattern?
2. Is this design good? Does it make sense for a MailProvider to be aware of an EmailObject?
3. What if I had a new EmailObject later on that required an attachment?
4. The client code now needs to acquire a specific MailProvider before creating a MailSender ... does this make sense?

Was it helpful?

Solution

Ok, let's go through your questions.

1) Vaguely, yes. You could argue that you have "algorithms" or "strategies" and that you can pick between them. However, I prefer to think of the strategy pattern as something that is relevant to algorithms. For example, different ways to get a search result. Here you are dealing with different agents to which you delegate the role of sending email. That's a common design, but I am not sure I would necessarily call it strategy. Either way, design patterns are meant to help you think, not to lock you to a specific name.

2) I think the design is reasonable. I would use interfaces rather than actual classes, especially for the EMailObject. In addition, there should be a factory for email object, don't just new them. It is also very likely that each provider will provide it's own "email object" that includes package detail. You are sending the contents, not the "envelope".

3) That's another good reason to use interfaces rather than a class. And you may want to include getters/setters for metadata and potentially attachments because they are a legitimate part of your domain (an email).

OTHER TIPS

The most important questions here are in my opinion:

  1. Can you test your component without sending actual emails? Yes:

    MailSender sender = new MailSender(new FakeMailProvider());
    sender.send(new EmailObject());
    
  2. Can you test your email providers without the rest of the application? Yes:

    SMTPMailProvider provider = new SMTPMailProvider();
    provider.send(new EmailObject());
    

You have succesfully decoupled providers from senders.

EDIT: Q4. The client needs to pass the specific MailProvider to the MailSender before sending EmailObject. This statement can be transformed into something like this: "client asks the emailing service to send the email, passing email data and choosing a transport (a way to send an email)". I think it is OK, but if you don't want to specify the transport every time, you may change it to "... the service then sends the email using the configured transport" and move provider instantiation to the configuration.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top