Originally I was just thinking that I prefer that adjective over something hifalutin, empty, (and often undeserved), like elegance. But after starting to grok what they were talking about, it really has a more meaningful purpose. Attractive software:
- Attracts other code to it.
- Attracts developers to use it.
- Attracts developers to share attractive patterns.
- Fights entropy.
Let's start with two simple Java classes:
public class User { private String emailAddress; private String name; public User(String name, String emailAddress){ if (!isValid(emailAddress)){ throw new IllegalArgumentException("Not a valid email address."); } this.name = name; this.emailAddress = emailAddress; } public void sendWelcomeMessage(EmailSender sender){ sender.send("greeter@mysite.com", emailAddress, "welcome", "Welcome to the site " + name + "!"); } private Boolean isValid(String emailAddress){ // do some validation return valid; } } public interface EmailSender { void send(String fromAddress, String toAddress, String subject, String body); }
Ok, so if we're developers responsible for working on this User class, we'll notice a few smells:
- It has too many responsibilties, modeling a user but also email validation.
- It has some knowledge about whom is supposed to send the welcome message.
And maybe it doesn't make sense to send emails from the User, but I like this API as it doesn't expose the internals of the User- it's telling it to send an email, not asking for its emailAddress. I guess that's an example of a curried object.
Let's address the first by introducing a Value Object for an email address. I know I could use the javax.mail.InternetAddress, but I want something simpler and easier to work with. In fact I'll use it for validation and weaken its checked AddressException down to the more friendly unchecked IllegalArgumentException.
import javax.mail.InternetAddress; import javax.mail.AddressException; public class EmailAddress { private InternetAddress value; public EmailAddress(String address){ try { this.value = new InternetAddress(address); // validates for us } catch (AddressException ae){ throw new IllegalArgumentExcpetion(ae); } } public String value(){ return value.toString(); } }
Now let's refactor the User class, using this new abstraction.
public class User { private String name; private EmailAddress emailAddress; public User(String name, EmailAddress emailAddress){ this.emailAddress = emailAddress; this.name = name; } public void sendWelcomeMessage(EmailSender sender){ sender.send("greeter@mysite.com", emailAddress.value(), "welcome", "Welcome to the site " + name + "!"); } }Ok, this is better, we've removed one of the smells, that the class had too many responsibilities. Now it only deals with sending a welcome message.
But I'd argue this is the stage where some attraction starts happening. The EmailAddress class might be a better place to hang the greeter email address. Perhaps it won't be the final place in our application, I could certainly think of better places to put it, but I'd argue it's better than in the User class.
public class EmailSender { public static EmailSender GREETER = new EmailSender("greeter@mysite.com"); ... }And refactoring the User class to use it...
public void sendWelcomeMessage(EmailSender sender){ sender.send(EmailAddress.GREETER.value(), emailAddress.value(), "welcome", "Welcome to the site!"); }
Ok, that's better, but there' still more attraction going on. If someone responsible for the EmailSender interface were to look at it, might they realize that (re)using our EmailAddress object makes their API better too?
public interface EmailSender { void send(EmailAddress from, EmailAddress to, String subject, String body); }
So we'd refactor our User to be even better...
... public void sendWelcomeMessage(EmailSender sender){ sender.send(EmailAddress.GREETER, emailAddress, "welcome", "Welcome to the site!"); } ...But now I think I see another type of attraction going on: this Value Object being extended to other parts of the EmailSender API. Value object's are a great place to put validation- so why not make some for email Body's and Subject's, where (for example), we could isolate the logic for an email subject's maximum length?
public interface EmailSender { void send(EmailAddress from, EmailAddress to, Subject subject, Body body); }
This was intentionally a trivial and contrived example application, and I'd never call this code elegant. Nor would anyone else. But perhaps they might call it attractive. Or at the least would they say it's more attractive than it was.
And we would have made some small steps fighting some entropy in our application, don't you think?
1 comment:
What about...?
EmailMessage.addSender(greeterEmailAddress).addToRecipient(userEmailAddress).addBody(theMessage).send();
Post a Comment