Simple Factory
December 21, 2021你想寫個 Java 程式,透過 Gmail 寄送郵件。
最初的設計
如果直接使用 Java Mail 的話,你會透過一個固定的 Gmail 帳號來發送郵件,因此大概會是長這樣的程式片段:
var props = new Properties();
props.put("mail.smtp.host", "smtp.gmail.com");
props.put("mail.smtp.auth", "true");
props.put("mail.smtp.starttls.enable", "true");
props.put("mail.smtp.port", 587);
var session = Session.getInstance(props,
new Authenticator() {
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(username, password);
}
});
var message = new MimeMessage(session);
message.setFrom(new InternetAddress(from));
message.setRecipient(Message.RecipientType.TO, new InternetAddress(to));
message.setSubject(subject);
message.setSentDate(new Date());
message.setText(text);
Transport.send(message);
建立連線 Session
當然是必要的,只不過因為你是透過固定的 Gmail 帳號來發送郵件,如果在應用程式中,每當想寄送郵件,就得寫那些建立連線 Session
的程式碼,就會形成一種重複。
關心的事?
另一方面,你真正關心的,並不是如何建立連線,撰寫程式時實際上關心的是郵件址址、內容等,既然如此,將不關心的部份,從視野中移除,就會是必要的,想這麼做的話,可以考慮如下建立 mimeMessage
方法:
public class Mail {
public static Message mimeMessage() {
var props = new Properties();
props.put("mail.smtp.host", "smtp.gmail.com");
props.put("mail.smtp.auth", "true");
props.put("mail.smtp.starttls.enable", "true");
props.put("mail.smtp.port", 587);
var session = Session.getInstance(props,
new Authenticator() {
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(username, password);
}
});
return new MimeMessage(session);
}
}
在這邊有個 Mail
類別,而 mimeMessage
設計為 static
,這只是因為 Java 沒辦法直接定義函式,如果是在其他可以直接定義函式的語言中,或許直接定義為函式也能解決需求,在上面的片段中 Mail
只是作為一個名稱空間罷了,如果是在 Python,或許在 mail
模組中定義個 mime
函式,也可以滿足需求。
總之,如果有了以上的 Mail.mimeMessage
,你就可以這麼寫程式了:
var message = Mail.mimeMessage();
message.setFrom(new InternetAddress(from));
message.setRecipient(Message.RecipientType.TO, new InternetAddress(to));
message.setSubject(subject);
message.setSentDate(new Date());
message.setText(text);
Transport.send(message);
如果你需要一個物件,建立該物件的過程繁瑣,而且也不是你關心的事情,最簡單的方式,就是如上將建立物件的過程封裝起來,最簡單的方式就是建立一個方法(函式),把不關心的事塞進去,該方法(函式)就像一個工廠,直接給你想要的物件。
這種方式很簡單,簡單到基本上每個人都會,看多了就會發現不少地方都會這麼做,形成一種模式,若要溝通方便,就將這個方式取為 Simple Factory 好了。
關注分離
簡單到這種程度,有必要特別談這個模式嗎?其實就因為簡單,也就能簡單地表現出關注分離(separation of concerns)的精神,在寫下一段程式碼時,你關心什麼?不關心什麼?是一個很好的思考方式,這個思考方式可以引導你重構,而不是想著要實現某種模式,如果重構過後的程式碼,有點像是某種模式,那也只是剛好而已!
模式中有 factory 之名的,基本上都有個目的,將物件的建立與物件的使用分離,因為物件的建立是一個值得關心的地方,然而物件的使用往往又是另一個關注的範疇,若不想要建立與使用兩種關注的邏輯被混在一起,思考的方向,往往會是 factory 的相關模式。
Simple Factory 的實際例子很多,例如 Java 的 Integer.valueOf
,它可以傳回 Integer
實例,而且在一定整數範圍內,傳回的實例會是同一個,也就是說 Simple Factory 因為關注分離,可以將不關心的事隱藏起來,從而實作上就可以有很多可能性,像是 Integer.valueOf
快取一定整數範圍內的 Integer
實例,只是其中一例。
因為概念上很簡單,視語言特性或實作方式而定,Simple Factory 可以有更多變化,例如 Java 可以定義 static
方法,Simple Factory 也就常被稱為 Static Factory,Java 可以有 private
建構式,可以搭配 static
方法,達到隱藏建構式,透過 static
方法生成實例的作用,進一步地達到實現單例(singleton)、原型(prototype)、依需求傳回子類實例等效果。