Abstract Factory

Muzi·2023년 4월 28일
0

디자인 패턴

목록 보기
5/14
post-thumbnail

추상 팩토리 패턴

  • 구체적인 클래스에 의존하지 않고 서로 연관되거나 의존적인 객체들의 조합을 만드는 인터페이스를 제공하는 패턴

    • 즉, 관련성있는 여러 종류의 객체를 일관된 방식으로 생성하는 경우에 유용하다

    • 싱글톤 패턴, 팩토리 메서드 패턴을 사용

    • 생성(Creational)패턴의 하나 - 객체 생성에 관련된 패턴이라는 뜻

    • 즉, 부품의 구체적인 구현에는 주목하지 않고 인터페이스에 주목해 부품을 조립하고 제품으로 완성

  • AbstractProduct(추상적인 제품) - 제품의 공통 인터페이스

  • AbstractFactory(추상적인 공장) - 실제 팩토리 클래스의 공통 인터페이스

  • Client(의뢰자) - AbstractFactory와 AbstractProduct만을 사용해 주어진 역할을 실행

    • Client는 구체적인 부품이나 제품, 공장에 대해서는 모른다.
  • ConcreteFactory(구체적인 공장) - 구체적인 팩토리 클래스로 AbstractFactory 클래스의 추상 메서드를 오버라이드함으로써 구체적인 제품을 생성한다.

  • ConcreteProduct(구체적인 제품) - ConcreteFactory에서 생성되는 구체적인 제품

팩토리 메서드 패턴과의 차이

이름반 봐서는 두 패턴이 비슷해보이지만, 명확한 차이점이 있다

  • 팩토리 메서드 패턴
    • 조건에 따른 객체 생성을 팩토리 클래스로 위임하여, 팩토리 클래스에서 객체를 생성하는 패턴

    • 팩토리를 구현하는 방법에 초점을 둔다

    • 구체적인 인스턴스 생성 과정을 하위또는 구체적인 클래스로 옮기는 것이 목적

  • 추상 팩토리 패턴
    • 서로 관련이 있는 객체들을 통째로 묶어서 팩토리 클래스로 만들고, 이를 팩토리를 조건에 따라 생성하도록 다시 팩토리를 만들어서 객체를 생성하는 패턴

    • 팩토리를 사용하는 방법에 초점을 둔다

    • 관련있는 여러 객체를 구체적인 클래스에 의존하지 않고 만들 수 있게 해주는 것이 목적

예시

계층구조로 된 링크페이지를 HTML 형식으로 출력하는 예제 프로그램

1) 추상적인 부품: Item 클래스

  • Link와 Tray의 상위 클래스로, 둘을 묶기 위한 클래스.
  • caption 필드는 이 항목의 '목차'를 표시
  • makeHTML()는 추상 메소드로 하위 클래스에서 구현되도록한다. 이 메소드를 호출하면 HTML의 문자열이 반환값이 된다.
package factory;

public abstract class Item {
    protected String caption; // 목차를 의미
    public Item(String caption) {
        this.caption = caption;
    }
    public abstract String makeHTML();
}
  • HTML의 하이퍼링크를 추상적으로 표현한 클래스.
  • url 필드는 링크되는 곳의 URL을 저장하는 곳.
  • Link 클래스에서는 추상 메소드가 전혀 등장하지 않는 것처럼 보이지만, 상위 클래스(Item)의 추상 메소드(makeHTML)가 구현(오버라이드)되어 있지 않으므로 Link 클래스도 추상 클래스이다.
package factory;

public abstract class Link extends Item {
    protected String url;
    public Link(String caption, String url) {
        super(caption);
        this.url = url;
    }
}

3) 추상적인 부품: Tray 클래스

  • 복수의 Link나 Tray를 모아서 합친 것을 표시한 클래스.
  • Link와 Tray는 add()를 사용해서 모은다.
  • ‘Link나 Tray’라는 부분을 표현하기 위해 add 메소드에서는 Link와 Tray의 상위 클래스인 Item을 인수로 갖는다.
  • Tray 클래스도 Item 클래스의 추상 메소드 makeHTML을 상속하고 있지만 구현은 하지 않는다. 그러므로 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);
    }
}

4) 추상적인 제품: Page 클래스

  • Html 페이지 전체를 추상적으로 표현한 클래스
  • Link나 Tray가 추상적인 ‘부품’이라면 Page 클래스는 추상적인 ‘제품'
  • title은 페이지의 제목, author는 페이지의 저자를 표현하기 위한 필드
  • 페이지에는 add 메소드를 사용해서 Item(즉, Link 혹은 Tray)을 추가
    • 추가한 것이 이 페이지에서 표시됨
  • output 메소드 안에서는 제목을 기초로 파일명을 결정하고 makeHTML()를 사용해서 자신의 HTML의 내용을 파일에 기술
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();
}

5) 추상적인 공장: Factory 클래스

  • getFactory()는 클래스의 이름을 지정해서 구체적인 공장(ConcreteFactory)의 인스턴스를 작성

  • getFactory 안에서는 Class 클래스의 forName 메소드를 사용해서, 그 클래스를 동적으로 읽는다

    • Class 클래스는 java.lang 패키지에 속하는 클래스로서 ‘클래스를 표현하는 클래스'
    • java 표준 라이브러리에 포함되어 있다
  • newInstance 메소드를 이용해서, 그 클래스의 인스턴스를 한 개 작성하고 이것이 getFactory()반환값이 된다

    • forName은 java.lang.Class의 클래스 메소드(static 메소드)이고, newInstance는 java.lang.Class의 인스턴스 메소드
  • 주의해야할건 getFactory()안에는 ConcreteFactory의 인스턴스를 만들지만 반환값은 추상적인 공장이라는 점

package factory;

public abstract class Factory {
    public static Factory getFactory(String classname) { // 구체적인 공장의 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);
}

6) 공장을 사용해서 부품을 조합하고 제품을 만든다: Main 클래스

  • 추상적인 공장을 사용해, 추상적인 부품을 조합, 제품을 제조.
  • import되어있는게 factory패키지뿐인 점에서 알 수 있듯이 구체적인 부품, 제품, 공장을 전혀 이용하지 않는다
  • 구체적인 공장의 클래스이름은 커맨드 라인에서 지정
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.println("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("Google", "http://www.google.com/");

        Tray traynews = factory.createTray("신문"); // 신문 트레이
        traynews.add(joins);
        traynews.add(chosun);

        Tray trayyahoo = factory.createTray("Yahoo!"); // 야후 트레이
        trayyahoo.add(us_yahoo);
        trayyahoo.add(kr_yahoo);

        Tray traysearch = factory.createTray("검색엔진"); // 검색엔진 트레이
        traysearch.add(trayyahoo); // link뿐 아니라 tray도 들어가는 모습
        traysearch.add(excite);
        traysearch.add(google);

        Page page = factory.createPage("LinkPage", "영진닷컴");
        page.add(traynews);
        page.add(traysearch);
        page.output();
    }
}

7) 구체적인 공장: ListFactory 클래스

  • Factory 클래스의 추상 메소드 createLink, createTray, createPage를 구현
  • 단순하게 new 하고있음
package listfactory;
import factory.*;

public class ListFactory extends Factory {
	@Override
    public Link createLink(String caption, String url) {
        return new ListLink(caption, url);
    }
    @Override
    public Tray createTray(String caption) {
        return new ListTray(caption);
    }
    @Override
    public Page createPage(String title, String author) {
        return new ListPage(title, author);
    }
}
  • Link 클래스의 하위 클래스
  • 구현해야할 메소드는 상위 클래스(Link)에서 추상 메소드 였던 makeHtml()
package listfactory;
import factory.*;

public class ListLink extends Link {
    public ListLink(String caption, String url) {
        super(caption, url);
    }
    @Override
    public String makeHTML() {
        return "  <li><a href=\"" + url + "\">" + caption + "</a></li>\n";
    }
}

9) 구체적인 부품: ListTray 클래스

  • Tray 클래스의 하위 클래스
  • Tray안에는 Html로 출력해야할 item들이 모여있다
  • 여기서 구현한 makeHtml은 item들을 html태그로 표현한다
  • 중요한건 item의 내용이 실제로 무엇인지 조건문을 사용해 확인하면 안된다
    • 매우 비객체지향적인 프로그램이 됨
  • 변수 item은 Item형이고 Item 클래스에서는 makeHTML이라는 메소드가 선언되고 있다, 또한 Listlink, Listtray 모두 item 클래스의 하위 클래스로 안심하고 호출한다.
package listfactory;
import factory.*;
import java.util.Iterator;

public class ListTray extends Tray {
    public ListTray(String caption) {
        super(caption);
    }
    @Override
    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();
    }
}

10) 구체적인 제품: ListPage 클래스

  • Page 클래스의 하위 클래스
  • 저자 이름(author)은 <address> 태그를 사용해 표현하고 있다.
  • while hasNext() 를 ul태그 안에 둬서 while문 안에서 append되는 item.makeHTML()의 출력결과가 ul안에 들어가는것을 전제로한다
  • content는 Page클래스에서 상속받은 필드
package listfactory;
import factory.*;
import java.util.Iterator;

public class ListPage extends Page {
    public ListPage(String title, String author) {
        super(title, author);
    }
    @Override
    public String makeHTML() {
        StringBuffer sb = new StringBuffer();
        sb.append("<html><head><title>" + title + "</title></head>\n");
        sb.append("<body>\n");
        sb.append("<h1>" + title + "</h1>\n");
        sb.append("<ul>\n");
        sb it = content.iterator();
        while (it.hasNext()) {
            Item item = (Item)it.next();
            sb.append(item.makeHTML());
        }
        sb.append("</ul>\n");
        sb.append("<hr><address>" + author + "</address>");
        sb.append("</body></html>\n");
        return sb.toString();
    }
}

다른 구체적인 공장 추가

단순히 HTML 링크 페이지를 만드는 것이 목적이라면 너무 거창하다.
구체적인 공장이 하나뿐이라면 추상적/구체적으로 나눌 필요가 전혀 없다.

그래서 아래에서는 예시에 다른 구체적인 공장을 추가해보자(다른 형식의 html)

ListFactory 패키지는 <ul>을 하용한 디자인, 이번에는 css를 사용한 디자인을 한다

11) 구체적인 공장: DivFactory 클래스

package divfactory;
import factory.*;

public class DivFactory extends Factory {
  	@Override
    public Link createLink(String caption, String url) {
        return new DivLink(caption, url);
    }
  	@Override
    public Tray createTray(String caption) {
        return new DivTray(caption);
    }
  	@Override
    public Page createPage(String title, String author) {
        return new DivPage(title, author);
    }
}
package divfactory;
import factory.*;

public class DivLink extends Link {
    public DivLink(String caption, String url) {
        super(caption, url);
    }
  	@Override
    public String makeHTML() {
        return "<td><a href=\"" + url + "\">" + caption + "</a></td>\n";
    }
}

13) 구체적인 부품: DivTray 클래스

package divfactory;
import factory.*;
import java.util.Iterator;

public class DivTray extends Tray {
    public DivTray(String caption) {
        super(caption);
    }
  	@Override
    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();
    }
}

14) 구체적인 제품: DivPage 클래스

package tablefactory;
import factory.*;
import java.util.Iterator;

public class DivPage extends Page {
    public DivPage(String title, String author) {
        super(title, author);
    }
  	@Override
    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("<table width=\"80%\" border=\"3\">\n");
        Iterator it = content.iterator();
        while (it.hasNext()) {
            Item item = (Item)it.next();
            buffer.append("<tr>" + item.makeHTML() + "</tr>");
        }
        buffer.append("</table>\n");
        buffer.append("<hr><address>" + author + "</address>");
        buffer.append("</body></html>\n");
        return buffer.toString();
    }
}

확장

구체적인 공장을 새로 추가하는것은 간단하다

  • 여기서 간단은 어떤 클래스를 만들고, 어떤 메소드를 구현하면 좋은지가 확실하다는 의미

부품을 새로 추가하는 것은 어렵다

예를 들어 factory 패키지에 화상을 표시하는 Picture라는 부품을 추가했다고 가정한다. 그러면 이미 존재하는 구체적인 공장 전부에 Picture에 대응하는 수정을 해야한다. 이미 만들어진 구체적인 공장이 많으면 많을수록 힘든 작업이 된다

profile
좋아하는걸 열심히

0개의 댓글