소프트웨어 마에스트로 과정에서 멘토님, 팀원들과 디자인패턴 세미나를 진행하고 있습니다. 이번에는 Abstract Factory 패턴에 대해 설명드리겠습니다.
💡 Java언어로 배우는 디자인 패턴 입문 책을 참고하여 포스팅하였습니다.
Abstract는 추상적인이라는 의미이고 Factory는 공장이라는 의미입니다. 즉, abstract factory 패턴은 추상적인 공장이라는 뜻입니다. 추상적이라는 단어에 대해 생각해 봅시다. 추상적이란 단어는 구체적으로 어떻게 구현되고 있는지에 대해 생각하지 않고 인터페이스만을 생각하는 상태입니다. 예시로, 추상 메소드는 메소드의 본체는 기술되어 있지 않고 이름과 파라미터만이 정해져 있는 메소드입니다.
Abstract Factory 패턴에서는 추상적인 공장이 등장하고 추상적인 부품을 조합해서 추상적인 제품을 만듭니다. 즉, 부품의 구체적인 구현에는 주목하지 않고 인터페이스에 주목합니다. 그리고 그 인터페이스만을 이용해 부품을 조립하고 제품으로 완성합니다.
책에서는 계층구조를 가진 Link 페이지를 HTML 파일로 만들기라는 예제를 보여줍니다.
아래 사진은 예시에서 나오는 클래스 다이어그램입니다.
클래스 종류에 대해 설명드리겠습니다.
factory 패키지
Factory 클래스: 추상적인 공장을 나타내는 클래스입니다. Link, Tray, Page를 만듭니다.
Item 클래스: Link와 Tray를 통일적으로 취급하기 위한 클래스입니다.
Link 클래스: (추상적인 부품) HTML과 Link를 나타내는 클래스입니다.
Tray 클래스: (추상적인 부품) Link나 Tray를 모은 클래스입니다.
Page 클래스: (추상적인 제품) HTML의 Page를 나타내는 클래스입니다.
listfactory 패키지
ListFactory 클래스: 구체적인 공장을 나타내는 클래스입니다. ListLink, ListTray, ListPage를 만듭니다.
ListLink 클래스: (구체적인 부품) HTML과 Link를 나타내는 클래스입니다.
ListTray 클래스: (구체적인 부품) Link나 Tray를 모은 클래스입니다.
ListPage 클래스: (구체적인 제품) HTML의 Page를 나타내는 클래스입니다.
package factory;
public abstract class Item {
protected String caption;
public Item(string caption) {
this.caption = caption;
}
public abstract String makeHTML();
}
package factory;
public abstract class Link extends Item {
protected String url;
public Link(String caption, String url) {
super(caption);
this.url = url;
}
}
package factory;
import java.util.ArrayList;
public abstract class Tray extends Item {
protected ArrayList tray = new ArrayList();
public Tray(String caption) {
super(caption);
}
public void add(Item item) {
tray.add(item);
}
}
package factory;
import java.io.*;
import java.util.ArrayList;
public abstract class Page {
protected String title;
protected String author;
protected ArrayList content = new ArrayList();
public Page(String title, String author) {
this.title = title;
this.author = author;
}
public void add(Item item) {
content.add(item);
}
public void output() {
try {
String filename = title + ".html";
Writer writer = new FileWriter(filename);
writer.write(this.makeHTML());
writer.close();
System.out.println(filename + "을 작성했습니다.");
} catch (IOException e) {
e.printStackTrace();
}
}
public abstract String makeHTML();
}
package factory;
public abstract class Factory {
public static Factory getFactory(String classname) {
Factory factory = null;
try {
factory = (Factory)Class.forName(classname).newInstance();
} catch (ClassNotFoundException e) {
System.err.println("클래스 " + classname + " 이 발견되지 않습니다.");
} catch (Exception e) {
e.printStackTrace();
}
return factory;
}
public abstract Link createLink(String caption, String url);
public abstract Tray createTray(String caption);
public abstract Page createPage(String title, String author);
}
import factory.*;
public class Main {
public static void main(String[] args) {
if (args.length != 1) {
System.out.println("Usage: java Main class.name.of.ConcreteFactory");
System.out.println("Example 1: java Main listfactory.ListFactory");
System.out.printlnt("Example 2: java Main tablefactory.TableFactory");
System.exit(0);
}
Factory factory = Factory.getFactory(args[0]);
Link joins = factory.createLink("중앙일보", "http://www.joins.com/");
Link chosun = factory.createLink("조선일보", "http://www.chosun.com/");
Link us_yahoo = factory.createLink("Yahoo!", "http://www.yahoo.com/");
Link kr_yahoo = factory.createLink("Yahoo!Korea", "http://www.yahoo.co.kr/");
Link excite = factory.createLink("Excite", "http://www.excite.com/");
Link google = factory.createLink("Yahoo!Korea", "http://www.google.com/");
Tray traynews = factory.reateTray("신문");
traynews.add(joins);
traynews.add(chosun);
Tray trayyahoo = factory.creaetTray("Yahoo!");
trayyahoo.add(us_yahoo);
trayyahoo.add(kr_yahoo);
Tray traysearch = factory.createTray("검색엔진");
traysearch.add(trayyahoo);
traysearch.add(excite);
traysearch.add(google);
Page page = factory.createPage("LinkPage", "영진닷컴");
page.add(traynews);
page.add(traysearch);
page.output();
}
}
package listffactory;
import factory.*;
public class ListFactory extends Factory {
public Link createLink(String caption, String url) {
return new ListLink(caption, url);
}
public Tray createTray(String caption) {
return new ListTray(caption);
}
public Page createPage(String title, String author) {
return new ListPage(title, author);
}
}
package listfactory;
import factory.*;
public class ListLink extends Link {
public ListLink(String caption, String url) {
susper(caption, url);
}
public String makeHTML() {
return " <li><a href=\"" + url + "\">" + caption + "</a></li>\n";
}
}
package listfactory;
import factory.*;
import java.util.Iterator;
public class ListTray extends Tray {
public ListTray(String caption) {
super(caption);
}
public String makeHTML() {
StringBuffer buffer = new StringBuffer();
buffer.append("<li>\n");
buffer.append(caption + "\n");
buffer.append("<ul>\n");
Iterator it = tray.iterator();
while (it.hasNext()) {
Item item = (Item)it.next();
buffer.append(item.makeHTML());
}
buffer.append("<\ul>\n");
buffer.append("<\li>\n");
return buffer.toString();
}
}
package listfactory;
import factory.*;
import java.util.Iterator;
public class ListPage extends Page {
public ListPage(String title, String author) {
super(title, author);
}
public String makeHTML() {
StringBuffer buffer = new StringBuffer();
buffer.append("<html><head><title>" + title + "</title></head>\n");
buffer.append("<body>\n");
buffer.append("<h1> + title + "<\h1>\n");
buffer.append("<ul>\n");
Iterator it = content.iterator();
while (it.hasNext()) {
Item item = (Item)it.next();
buffer.append(item.makeHTML());
}
buffer.append("</ul>\n");
buffer.append("<hr><address>" + author + "</address>");
buffer.append("</body></html>\n");
return buffer.toString();
}
}
위 예시에서 구체적인 공장인 TableFactory클래스를 추가해봅시다.
package tablefactory;
import factory.*;
public class TableFactory extends Factory {
public Link createLink(String caption, String url) {
return new TableLink(caption, url);
}
public Tray createTray(String caption) {
return new TableTray(caption);
}
public Page createPage(String title, String author) {
return new TablePage(title, author);
}
}
package tablefactory;
import factory.*;
public class TableLink extends Link {
public TableLink(String caption, String url) {
super(caption, url);
}
public String makeHTML() {
return "<td><a href=\"" + url + "\">" + caption + "</a></td>\n";
}
}
package tablefactory;
import factory.*;
import java.util.Iterator;
public class TableTray extends Tray {
public TableTray(String caption) {
super(caption);
}
public String makeHTML() {
StringBuffer buffer = new StringBuffer();
buffer.append("<td>");
buffer.append("<table width=\"100%\" border=\"1\"><tr>");
buffer.append("<td bgcolor=\"#cccccc\" align=\"center\" colspan=\"" + tray.size() + "\"><b>" + caption + "</b></td>");
buffer.append("</tr>\n");
buffer.append("<tr>\n");
Iterator it = tray.iterator();
while (it.hasNext()) {
Item item = (Item)it.next();
buffer.append(item.makeHTML());
}
buffer.append("<\tr><\table>");
buffer.append("</td>");
return buffer.toString();
}
}
(makeHTML 제외하고 ListPage와 거의 유사하므로 생략하겠습니다.)
여기서 간단하다는 말은 어떤 클래스를 만들고 어떤 메소드를 구현하면 좋은지가 확실하다는 의미입니다. 예시로, 예제 프로그램에서 새로운 구체적인 공장을 추가한다고 가정해봅시다. 해야할 일은 Factory, Link, Tray, Page의 하위 클래스를 만ㄴ들고 각각의 추상 메소드를 구현하는 일입니다. 이때 아무리 구체적인 공장을 추가하거나 구체적인 공장의 버그를 수정해도 추상적인 공장이나 Main 부분을 수정할 필요가 없습니다.
예를 들어, factory 패키지에 화상을 표시하는 Picture 부품이 추가되었다고 가정해봅시다. 그러면 이미 존재하는 구체적인 공장 전부에 Picture에 대응하는 수정을 해야 합니다. 이렇듯, 이미 만들어진 구체적인 공장이 많을수록 번거롭게 됩니다.