🧵 Design Pattern
디자인 패턴 - Abstract Factory 패턴
date
Sep 22, 2022
slug
abstract-factory-pattern
author
status
Public
tags
디자인패턴
Abstract Factory 패턴
summary
디자인 패턴 - Abstract Factory 패턴에 대해 알아보자
type
Post
thumbnail
category
🧵 Design Pattern
updatedAt
Nov 22, 2023 01:04 AM
언어
개요
소프트웨어 마에스트로 과정에서 멘토님, 팀원들과 디자인패턴 세미나를 진행하고 있습니다. 이번에는 Abstract Factory 패턴에 대해 설명드리겠습니다.
💡 Java언어로 배우는 디자인 패턴 입문 책을 참고하여 포스팅하였습니다.
Abstract Factory pattern?
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를 나타내는 클래스입니다.
추상적인 부품
1. Item 클래스
package factory; public abstract class Item { protected String caption; public Item(string caption) { this.caption = caption; } public abstract String makeHTML(); }
- Item 클래스는 Link와 Tray의 상위클래스입니다.
- Link와 Tray를 동일시하기 위한 클래스입니다.
- caption 필드는 이 항목의 목차를 표시합니다.
- makeHTML은 추상 메소드로 하위 클래스에서 구현되도록 합니다.
2. Link 클래스
package factory; public abstract class Link extends Item { protected String url; public Link(String caption, String url) { super(caption); this.url = url; } }
- List 클래스는 HTML의 하이퍼링크를 추상적으로 표현한 클래스입니다.
- url 필드는 링크되는 곳의 URL을 저장하기 위한 것입니다.
- 상위 클래스의 추상 메소드(makeHTML)가 구현되어 있지 않으므로 Link클래스도 추상 클래스입니다.
3. Tray 클래스
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); } }
- Tray 클래스는 복수의 Link나 Tray를 모아서 합친 것을 표시한 클래스입니다.
- add 메소드를 사용해 Link와 Tray (item)를 모읍니다.
- 따라서 Link와 Tray의 상위 클래스인 Item을 인수로 갖습니다.
- 상위 클래스인 추상 메소드(makeHTML)가 구현되어 있지 않으므로 Tray클래스도 추상 클래스입니다.
추상적인 제품
Page 클래스
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(); }
- Page 클래스는 HTML 페이지 전체를 추상적으로 표현한 클래스입니다.
- Link나 Tray가 추상적인 부품이라면 Page클래스는 추상적인 제품입니다.
- 페이지에서 add 메소드를 사용하여 Item(Link 혹은 Tray)을 추가합니다.
- output 메소드에서는 makHTML의 메소드를 사용하여 자신의 HTML의 내용을 파일에 기술합니다.
추상적인 공장
Factory 클래스
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); }
- getFactory 안에서 Class의 forName 메소드를 사용하여 해당 클래스를 동적으로 읽습니다. 이를 newInstance 메소드를 이용해 인스턴스를 하나 작성해줍니다.
- getFactory 메소드의 안에서는 구체적인 공장의 인스턴스를 만들지만 반환값은 추상적인 공장입니다.
- createLink, createTray, createPage 메소드는 추상적인 공장에서 부품 혹은 제품을 작성할 때 이용하는 추상 메소드입니다.
제품 만들기
Main 클래스
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(); } }
- 메인 클래스에서는 추상적인 공장을 사용해 추상적인 부품을 제조하고 추상적인 제품을 조립합니다.
- 메인 크래스에서는 구체적인 부품, 제품, 공장을 전혀 이용하지 않습니다.
- 구체적인 공장의 클래스 이름은 커맨드 라인에서 지정합니다.ex) java Main listfactory.ListFactory
구체적인 공장
ListFactory 클래스
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); } }
- ListFactory 클래스에서는 Factory 클래스의 추상 메소드인 createLink, createTray, createPage를 구현합니다.
구체적인 부품
ListLink 클래스
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"; } }
- ListLink 클래스는 Link 클래스의 하위 클래스입니다.
- 따라서 상위 클래스에서 추상 메소드였던 makeHTML을 해당 클래스에서 작성해줍니다.
ListTray 클래스
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(); } }
- makeHTML에서 차례대로 tray array 원소들을 makeHTML 해줍니다.
- 원소들은 모두 Item 형이므로 변수 item의 내용이 실제로 무엇인지 조사할 필요가 없습니다.
구체적인 제품
ListPage 클래스
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(); } }
- Listpage는 Page클래스의 하위 클래스입니다.
- makeHTML에서 while문 안에 item.makeHTML 메소드를 호출함으로써 ListLink와 ListTray의 makeHTML 메소드가 실행됩니다.
예제 프로그램에 별도의 구체적인 공장 추가하기
위 예시에서 구체적인 공장인 TableFactory클래스를 추가해봅시다.
구체적인 공장
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); } }
- TableFactory 클래스는 Factory 클래스의 하위 클래스입니다.
- createLink, createTray, createPaged에서는 각각 TableLink, TableTray, TablePage 라는 클래스의 인스턴스를 만듭니다.
구체적인 부품
TableLink 클래스
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"; } }
- TableLink클래스는 Link 클래스의 하위 클래스입니다.
- makeHTML에서 테이블을 구성하는 태그를 사용하고 있습니다.
TableTray 클래스
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(); } }
- TableTray 클래스는 Tray 클래스의 하위 클래스입니다.
- makeHTML 메소드에서 Item들을 출력하고 있습니다.
구체적인 제품
TablePage 클래스
(makeHTML 제외하고 ListPage와 거의 유사하므로 생략하겠습니다.)
Abstract Factory 패턴의 등장인물
- Abstract Product (추상적인 제품)
- Abstract Product는 Abstract Factory 역할에 의해 만들어지는 추상적인 부품이나 제품의 인터페이스를 결정합니다.
- 예제 프로그램에서는 Link 클래스, Tray 클래스, Page 클래스가 이 역할을 합니다.
- Abstract Factory (추상적인 공장)
- Abstract Factory는 Abstract Product 역할의 인스턴스를 만들어 내기 위한 인터페이스를 결정합니다.
- 예제 프로그램에서는 Factory 클래스가 이 역할을 합니다.
- Client
- Client는 Abstract Factory 역할과 Abstract Product 역할의 인터페이스만을 사용해서 주어진 역할을 실행합니다.
- Client는 구체적인 부품이나 제품, 공장에 대해서는 모릅니다.
- 예제 프로그램에서는 Main 클래스가 이 역할을 합니다.
- Concrete Product (구체적인 제품)
- Concrete Product는 Abstract Product 역할의 인터페이스를 구현합니다.
- 예제 프로그램에서는 ListLink 클래스, ListTray 클래스, ListPage클래스 등이 이 역할을 합니다.
- Concrete Factory
- Concrete Factory는 Abstract Factory 역할의 인터페이스를 구현합니다.
- 예제 프로그램에서는 ListFactory 클래스 등이 이 역할을 합니다.
더 나아가
구체적인 공장을 새로 추가하는 것은 간단합니다.
여기서 간단하다는 말은 어떤 클래스를 만들고 어떤 메소드를 구현하면 좋은지가 확실하다는 의미입니다. 예시로, 예제 프로그램에서 새로운 구체적인 공장을 추가한다고 가정해봅시다. 해야할 일은 Factory, Link, Tray, Page의 하위 클래스를 만ㄴ들고 각각의 추상 메소드를 구현하는 일입니다. 이때 아무리 구체적인 공장을 추가하거나 구체적인 공장의 버그를 수정해도 추상적인 공장이나 Main 부분을 수정할 필요가 없습니다.
부품을 새로 추가하는 것은 곤란합니다.
예를 들어, factory 패키지에 화상을 표시하는 Picture 부품이 추가되었다고 가정해봅시다. 그러면 이미 존재하는 구체적인 공장 전부에 Picture에 대응하는 수정을 해야 합니다. 이렇듯, 이미 만들어진 구체적인 공장이 많을수록 번거롭게 됩니다.