이 글을 더 잘 이해하고 싶다면 템플릿메소드 패턴, 팩토리메소드 패턴 두가지 패턴을 알고 보는것을 추천한다.
템플릿 메소드 패턴은 상속혹은 구현하는 클래스의 흐름을 미리 추상화해서 흐름을 설계하고 구현하도록 하는 패턴이고
팩토리 메소드 패턴은 템플릿 메소드 패턴에서 더 나아가 클래스의 인스턴스 생성흐름까지 설계하고 고정하는 패턴이었다.
추상 팩토리 패턴은 여기서 조금 더 확장된 개념이라고 볼 수 있다.
추상 팩토리 패턴은 여러개의 클래스를 임의의 단위로 묶어서 추상화하는 방식으로 여러개의 객체의 흐름과 형태를 고정하는 방식이다.
예를들면 모니터+키보드+메인보드+배터리+운영체제가 합쳐진 노트북이라는 패키지가 있을때 이를 애플이라는 클라이언트가 구현하면 맥북, 삼성이 구현하면 갤럭시북, 레노버가 구현하면 씽크패드 등등 이러한 SW구조에 적용되는 패턴이라고 생각하면 이해가 쉬울것이다.
정확한 설명은 아니지만 패키지 단위로 추상화하는 패턴이라고 생각하면 편할 수 있다.
기본적인 형태는 팩토리 메소드 패턴과 비슷하다고 볼 수 있다.
다만 클래스 한개의 흐름을 지정하는게 아니라 앞서 말했듯이 여러개의 클래스의 흐름을 결정한다.
이러한 형태의 장점은 여러개의 클래스가 하나의 특정 클래스에 의존하지 않게 해준다는 장점이 있고
기능 확장이 용이하며 형태가 정해져 있어서 유지보수가 용이하다는 점이 장점이 있다.
클래스 이름을 사용하지 않고 관련 객체들의 패밀리를 생성하기 위한 인터페이스 제공하기 위함
- 객체들이 다른 부류의 객체들과 섞이지 않고 관련 객체들의 패밀리들만 사용될 수 있도록 할 때
- 시스템이 여러 패밀리들 중에서 하나만 사용하여 형상화되어야 할 때
- AbstractProduct
- AbstractFactory에서 생성할 제품들에 대한 인터페이스를 선언함
- ConcreteProduct
- ConcreteProduct1, 2, 3는 각각 AbstractProduct1, 2, 3의 인터페이스를 구현함
- AbstractFactory
- AbstractProduct 타입의 인스턴스를 생성하기 위한 인터페이스를 선언함
- ConcreteFactory
- AbstractFactory의 인터페이스를 구현함
- Client
- 예제에서는, Main 클래스가 해당됨
- AbstractFactory와 AbstractProduct의 인터페이스만 사용함
- AbstractProduct 타입의 객체를 생성하려면 우선 적절한 ConcreteFactory 객체를 얻어야 함
예제코드의 클래스 다이어그램
public abstract class Factory {
public static Factory getFactory(String classname) {
Factory factory = null;
try {
factory = (Factory)Class.forName(classname).getDeclaredConstructor().newInstance();
} catch (ClassNotFoundException e) {
System.out.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);
public Page createNaverPage() {
Link link = createLink("Naver", "https://www.naver.com/");
Page page = createPage("Naver", "Naver");
page.add(link);
return page;
}
}
public abstract class Item {
protected String caption;
public Item(String caption) {
this.caption = caption;
}
public abstract String makeHTML();
}
public abstract class Link extends Item {
protected String url;
public Link(String caption, String url) {
super(caption);
this.url = url;
}
}
public abstract class Tray extends Item {
protected List<Item> tray = new ArrayList<>();
public Tray(String caption) {
super(caption);
}
public void add(Item item) {
tray.add(item);
}
}
public abstract class Page {
protected String title;
protected String author;
protected List<Item> 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(String filename) {
try {
Files.writeString(Path.of(filename), makeHTML(),
StandardOpenOption.CREATE,
StandardOpenOption.TRUNCATE_EXISTING,
StandardOpenOption.WRITE);
System.out.println(filename + " 파일을 작성했습니다.");
} catch (IOException e) {
e.printStackTrace();
}
}
public abstract String makeHTML();
}
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);
}
}
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";
}
}
public class ListPage extends Page {
public ListPage(String title, String author) {
super(title, author);
}
@Override
public String makeHTML() {
StringBuilder sb = new StringBuilder();
sb.append("<!DOCTYPE html>\n");
sb.append("<html><head><title>");
sb.append(title);
sb.append("</title></head>\n");
sb.append("<body>\n");
sb.append("<h1>");
sb.append(title);
sb.append("</h1>\n");
sb.append("<ul>\n");
for (Item item: content) {
sb.append(item.makeHTML());
}
sb.append("</ul>\n");
sb.append("<hr><address>");
sb.append(author);
sb.append("</address>\n");
sb.append("</body></html>\n");
return sb.toString();
}
}
public class ListTray extends Tray {
public ListTray(String caption) {
super(caption);
}
@Override
public String makeHTML() {
StringBuilder sb = new StringBuilder();
sb.append("<li>\n");
sb.append(caption);
sb.append("\n<ul>\n");
for (Item item: tray) {
sb.append(item.makeHTML());
}
sb.append("</ul>\n");
sb.append("</li>\n");
return sb.toString();
}
}
public class Main {
public static void main(String[] args) {
if (args.length != 2) {
System.out.println("Usage: java Main filename.html class.name.of.ConcreteFactory");
System.out.println("Example 1: java Main listNaver.html listfactory.ListFactory");
System.out.println("Example 2: java Main divNaver.html divfactory.DivFactory");
System.exit(0);
}
String filename = args[0];
String classname = args[1];
Factory factory = Factory.getFactory(classname);
// Page
Page page = factory.createNaverPage();
page.output(filename);
}
}
swift에서는 추상클래스가 없어서 java의 추상클래스를 따라하려면 프로토콜이나 일반 클래스로 구현후 접근 제한을 걸어야 한다.
(코드 구현중임...)
class AbstractFactory{
//Main에서 호출되는 클래스 이름에 따라 하위에서 생성된 concreateFactory클래스 인스턴스를 연결해주는 기능을 수행함
//클래스 동적할당
static func getFactory(_ classname: String) -> AbstractFactory{
if classname == "ListFactory"{
return ListFactory()
}
return AbstractFactory()
}
func createLink(_ caption: String, _ url: String) -> AbstractLink {
return AbstractLink(caption: caption, url: url)
}
func createTray(_ caption: String) -> AbstractTray{
return AbstractTray(caption: caption)
}
func createPage(_ title: String, _ author: String) -> AbstractPage{
return AbstractPage(title: title, author: author)
}
func createNaverPage() -> AbstractPage{
let link = createLink("Naver", "https://www.naver.com/")
let page = createPage("Naver", "Naver")
page.add(item: link)
return page
}
}
class AbstractTray: Item{
var tray: [Item] = []
init(caption: String, _ tmp: String = "") {
super.init(caption: caption)
}
func add(item: Item) {
tray.append(item)
}
}
class AbstractPage {
var title:String
var author:String
var content: [Item] = Array<Item>()
init(title: String, author: String) {
self.title = title
self.author = author
}
func add(item: Item){
content.append(item)
}
func output(filename: String){
print("file을 생성했습니다.")
}
func makeHTML() -> String{
return ""
}
}
class AbstractLink: Item{
var url:String
init(caption: String, url: String) {
self.url = url
super.init(caption: caption)
}
}
class Item{
var caption: String
init(caption: String) {
self.caption = caption
}
func makeHTML() -> String {
return ""
}
}
class ListFactory : AbstractFactory{
override func createLink(_ caption: String, _ url: String) -> AbstractLink {
return ListLink(caption: caption, url: url)
}
override func createTray(_ caption: String) -> AbstractTray {
return ListTray(caption: caption)
}
override func createPage(_ title: String, _ author: String) -> AbstractPage {
return ListPage(title: title, author: author)
}
}
class ListTray: AbstractTray{
init(caption: String){
super.init(caption: caption)
}
override func makeHTML() -> String {
var result:String = ""
result += "<li>\n"
result += caption
result += "\n<ul>\n"
for item in tray{
result += item.makeHTML()
}
result += "</ul>\n"
result += "</li>\n"
return result
}
}
class ListLink: AbstractLink {
init(caption: String, url: String, _ tmp: Int = 0) {
super.init(caption: caption, url: url)
}
override func makeHTML() -> String {
return " <li><a href=\"" + url + "\">" + caption + "</a></li>\n"
}
}
class ListPage: AbstractPage {
init(title: String, author: String, _ tmp: Int = 0) {
super.init(title: title, author: author)
}
override func makeHTML() -> String {
var result:String = ""
result += "<!DOCTYPE html>\n"
result += "<html><head><title>"
result += title
result += "</title></head>\n"
result += "<body>\n"
result += "<h1>"
result += title
result += "</h1>\n"
for item in content {
result += item.makeHTML()
}
result += "</ul>\n"
result += "<hr><address>"
result += author
result += "</address>\n"
result += "</body></html>\n"
return result
}
@main
struct Main {
static func main() {
let args:[String] = readLine()!.split(separator: " ").map{ String($0) }
if args.count != 2 {
print("Usage: java Main filename.html class.name.of.ConcreteFactory")
print("Example 1: java Main listNaver.html listfactory.ListFactory")
print("Example 2: java Main divNaver.html divfactory.DivFactory")
exit(0)
}
var filename:String = args[0]
var classname:String = args[1]
var factory:AbstractFactory = AbstractFactory.getFactory(classname)
var page:AbstractPage = factory.createNaverPage()
page.output(filename: filename)
}
}
// AbstractFactory클래스의 파일 출력 부분 구현 안됨.
// AbstractFactory클래스의 동적 클래스 생성부 if로 구현
템플릿 메소트 패턴과 팩토리 메소드 패턴에서 발전한 추상 팩토리 패턴이 있다.
하나의 클래스의 동작 흐름이나 인스턴스 생성흐름의 구조를 설계하는 두 패턴과는 달리 여러개의 클래스 흘름을 전체적으로 설계하는 패턴이 추상 팩토리 메소드 패턴이다.
여러개의 단위 객체들을 묶어서 추상화하고 기능 확장에 용이한 패턴이다.