views:

324

answers:

2

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?

+5  A: 

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).

Uri
+1 for this statement: "Either way, design patterns are meant to help you think, not to lock you to a specific name." It took me a while to learn that :^)
bedwyr
I'm trying to figure out a good way to do this. The MailerSender get's an EmailObject interface instead of a concrete class, how does the provider "get at" the information? The interface will provide a contract for the information in the concrete class, but what will happen when I create a new object with _added_ information?In the case of a basic email (to, from, cc, subject, body) versus an email with an attachment (byte[] data) ?
djunforgetable
You have to determine what an email has that every provider is familiar with and offer getters in the interface. The providers can generally only be expected to deal with emails, so you need to put in the email everything that you would need.
Uri
@bedwyr: Thank you. I'm still trying to convey that message in job interviews whenever I am asked to just recite the GOF book.
Uri
What would an EMailObject factory look like and why would I need it? Why can't client code just "new" one and use the mutators to populate the object?
djunforgetable
@djunforgetable: Might want to consider an abstract factory instead, so that you can implement your own factory for e-mails of type that you're aware of, and so that clients can create their own factories and supply custom e-mail to your sender. (I did this here, though in .NET, because our software sends some canned e-mails: cancellation letters, notices of collection etc. where they're all the same except for some customer data which the e-mailer doesn't need to know)
SnOrfus
@SnOrfus what would that look like?
djunforgetable
A: 

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.

bbmud
Do you think it makes sense for the client to have to pass in a MailProvider to the MailSender? Is there a way to get around this using a factory or builder pattern?
djunforgetable
Congratulations, you've just realized a need for Dependency Injection! Don't think I am trying make a joke of you here, this is really like a next level for a programmer:http://codebetter.com/blogs/jeremy.miller/archive/2008/11/11/evolution-of-a-developer-in-regards-to-di-ioc.aspx
bbmud
@bbmud cool ! but how would you work this without a DI/IoC framework ?
djunforgetable
Define a second empty constructor on MailSender class which calls the first constructor with new instance of default version of MailProvider (so called poor's man dependency injection).
bbmud