-
[Design Pattern] 팩토리 패턴 (Factory Pattern)공부/디자인 패턴 2020. 2. 29. 20:15
팩토리 패턴이란?
객체를 만들어 반환하는 함수를 생성자 대신 팩토리 형태로 제공하여 초기화 과정을 외부에서 보지 못하게 숨기고 반환 타입을 제어하는 방법입니다.
일반적으로 자바에서는 객체를 아래와 같이 생성합니다.
SomeClass someClassObject = new SomeClass ();
위 선언의 문제는 SomeClass의 객체를 사용하는 부분에서 SomeClass의 구현에 의존성이 생기는 것입니다. new를 사용하여 객체를 생성하는 부분은 문제가 없지만 코드를 구체적인 구현 클래스에 결합하여 제공을 하게 됩니다. 이러한 경우는 구현 문제가 아닌 인터페이스에 대한 코드 위반입니다.
더 자세하게 나무위키에 나온 예시를 봅니다. (https://namu.wiki/w/%EB%94%94%EC%9E%90%EC%9D%B8%20%ED%8C%A8%ED%84%B4#s-3.2)
class Unit { Unit() { //생성자 } //이하 유닛의 메소드들 }
먼저 unit이라는 큰 단위가 있고 이 unit 별로 마린, 파이어뱃 등등을 추가합니다.
class Marine extends Unit { Marine() { //생성자 } //이하 마린의 메소드들 } ... class Firebat extends Unit { Firebat() { //생성자 } //이하 파이어뱃의 메소드들 }
유닛을 다 만들었다고 가정하고 저장된 파일로부터 유닛을 배치하는 맵 기능을 구현합니다.
class Map { Map(File mapFile) { while(mapFile.hasNext() == true) { String[] unit = mapFile.getNext(); if(unit[0].equals("Marine")) { Marine marine = new Marine(unit); } else if(unit[0].equals("Firebat")) { Firebat firebat = new Firebat(unit); } //기타 유닛의 생성자들 } //유닛 초기화 이후 코드 } }
작동 자체에는 문제가 없는 코드이지만, 객체 지향적으로 보면 단일 책임 원칙을 위반했습니다. Map 클래스는 맵의 구현 방법에 대해서만 작성되어야 하는데 파일을 읽는 부분에서 유닛 별로 분류하는 코드가 포함 되어 있습니다. 만약 새 유닛이 추가가 되어야 한다면 해당 유닛과 연관이 없는 Map 클래스를 수정해야 합니다.
이러한 문제를 해결하기 위해 다양한 하위 클래스들을 생성하는 팩토리 클래스를 만들어 이러한 책임을 위임합니다. 팩토리 클래스는 다음과 같이 작성하면 됩니다.
class UnitFactory { static Unit create(String[] data) { if(data[0].equals("Marine")) { return new Marine(data); } else if(data[0].equals("Firebat")) { return new Firebat(data); } //기타 유닛의 생성자들 } } ... class Map { Map(File mapFile) { while(mapFile.hasNext() == true) { Unit unit = UnitFactory.create(mapFile.getNext()); } //유닛 초기화 이후 코드 } }
이와 같이 유닛 팩토리 클래스를 만들면 유닛이 추가되어도 연관없는 Map 클래스를 수정할 필요가 없습니다.
파이썬에서..
파이썬에서도 위 자바와 코드 형태는 크게 다르지 않습니다. 파이썬에서는 DB를 예시로 들어보고 팩토리를 만드는 것까지 보여줍니다.
먼저 아래와 같이 코드가 짜여져 있으면 DB 정보가 추가될 때마다 Item 클래스의 인스턴스 생성자에 DB 정보를 읽고 알맞는 클래스를 선택하는 코드가 추가되어야 합니다.
import pathlib from abc import ABCMeta, abstractmethod class DBManager(metaclass=ABCMeta): @abstractmethod def connect(self): pass class PostgreSQL(DBManager): def connect(self): print('PostgreSQL 연결') return 'connection', [] class MySQL(DBManager): def connect(self): print('MySQL 연결') return 'connection', [] class MsSQL(DBManager): def connect(self): print('MsSQL 연결') return 'connection', [] class Item: def __init__(self): # 어떤 db를 연결할 지 정보가 저장된 파일을 읽음 path = pathlib.Path('config/db.yaml').read_text() if path == 'postgresql': self.db_object = PostgreSQL() elif path == 'mysql': self.db_object = MySQL() else: self.db_object = MsSQL() self.connect, self.cursor = self.db_object.connect() def insert(self): self.cursor.insert(0, '데이터 insert') item = Item() item.insert()
이러한 문제를 해결하기 위해 DB Factory 클래스를 만들어 분리시키면 됩니다.
import pathlib from abc import ABCMeta, abstractmethod class DBManager(metaclass=ABCMeta): @abstractmethod def connect(self): pass class PostgreSQL(DBManager): def connect(self): print('PostgreSQL 연결') return 'connection', [] class MySQL(DBManager): def connect(self): print('MySQL 연결') return 'connection', [] class MsSQL(DBManager): def connect(self): print('MsSQL 연결') return 'connection', [] class DBFactory: def __init__(self): path = pathlib.Path('config/db.yaml').read_text() if path == 'postgresql': self.db_object = PostgreSQL() elif path == 'mysql': self.db_object = MySQL() else: self.db_object = MsSQL() self.connect, self.cursor = self.db_object.connect() class Item(DBFactory): def __init__(self): super(Item, self).__init__(self) def insert(self): self.cursor.insert(0, '데이터 insert') item = Item() item.insert()
DB 팩토리를 만들고 해당 클래스를 상속을 받는 형식과 같이 구현할 수 있습니다.