Abstract Factory Pattern
Context
스마트폰을 조립하는 공장을 생각해보면 아래와 같은 모델이 어느정도 연상될 것이다.
기본적으로 AP
, Battery
는 스마트폰을 구성하는 중요한 요소이며 갤럭시는 SamsungExynos
와 SamsungBattery
를, 애플의 아이폰은 AppleAX
와
LG
배터리를 사용한다. 그리고 이들의 인스턴스는 각각의 팩토리에서 생산된다. 어느 제품에 대한 배터리나 AP를 생산하는지는
Vender
Enum를 인자로써 제공한다. 아래는 위 모델의 코드 구현이다.
abstract class AP {
private Battery battery;
public AP(Battery battery) {
this.battery = battery;
}
public void process() throws Exception {
if(battery.getPower() > 0) {
active();
} else {
throw new Exception("battery is out");
}
}
protected abstract void active();
}
abstract class Battery {
protected abstract int getPower();
}
class SamsungExynos extends AP {
public SamsungExynos(Battery battery) {
super(battery);
}
@Override
protected void active() {
System.out.println("Samsung Exynos!");
}
}
class AppleAX extends AP {
public AppleAX(Battery battery) {
super(battery);
}
@Override
protected void active() {
System.out.println("Apple AX is running!");
}
}
class LGBattery extends Battery {
@Override
protected int getPower() {
System.out.println("spent energy of lg battery!");
return 5;
}
}
class SamsungBattery extends Battery {
@Override
protected int getPower() {
System.out.println("spent energy of samsung battery!");
return 10;
}
}
enum Vendor { Samsung, Apple, LG };
class APFactory {
static AP createAP(Vendor vendorId, Battery battery) throws Exception {
switch (vendorId) {
case Apple: return new AppleAX(battery);
case Samsung: return new SamsungExynos(battery);
default: throw new Exception("Not Found Vendors AP");
}
}
}
class BatteryFactory {
static Battery createBattery(Vendor vendorId) throws Exception {
switch (vendorId) {
case Samsung: return new SamsungBattery();
case LG: return new LGBattery();
default: throw new Exception("Not Found Vendors Battery");
}
}
}
class Client {
public static void main(String[] args) throws Exception {
APFactory.createAP(Vendor.Samsung, BatteryFactory.createBattery(Vendor.Samsung)).process();
}
}
자 이제 갤럭시와 아이폰을 생산할 모든 준비가 된 것 같다.
Problem
위와 같은 코드를 기준으로 ClientA
, ClientB
, ClientC
가 각 지역의 공장이라고 생각하고 각 공장들은 동일한 갤럭시를 생산하기위한 삼성의 부품들을 이용한다고 가정한다.
또한 기존의 AP
, Battery
뿐만 아니라 스마트폰 조립에 필요한 더 많은 Display
, Ram
과 같은 부품들이 필요한다면 아래와 같을 것이다.
각각의 Client
공장들은 각각의 재료에 대한 팩토리를 호출하고 중복되는 코드를 통해 조립한다.
class ClientA {
public static void main(String[] args) throws Exception {
Battery battery = BatteryFactory.createBattery(Vendor.Samsung);
Ram ram = RamFactory.createRam(Vendor.Samsung);
Display display = DisplayFactory.createDisplay(Vendor.Samsung);
AP ap = APFactory.createAP(Vendor.Samsung, battery);
}
}
class ClientB {
public static void main(String[] args) throws Exception {
Battery battery = BatteryFactory.createBattery(Vendor.Samsung);
Ram ram = RamFactory.createRam(Vendor.Samsung);
Display display = DisplayFactory.createDisplay(Vendor.Samsung);
AP ap = APFactory.createAP(Vendor.Samsung, battery);
}
}
class ClientC {
public static void main(String[] args) throws Exception {
Battery battery = BatteryFactory.createBattery(Vendor.Samsung);
Ram ram = RamFactory.createRam(Vendor.Samsung);
Display display = DisplayFactory.createDisplay(Vendor.Samsung);
AP ap = APFactory.createAP(Vendor.Samsung, battery);
}
}
또한 클라이언트에서 생산하는 스마트폰이 다시 갤럭시에서 아이폰으로 바뀐다면 각각의 팩토리에 요청한 Vendor
의 인자가 적절한 값으로 바뀌어야 할 것이다.
어떤 제품을 조립하는 구성요소를 사용 클래스에서 선택하도록 되어있다. 이것이 바로 좋은 설계일까? 많은 중복코드를 작성할 수 밖에 없다.
Solution
사실 아이폰, 갤럭시 또는 다른 스마트폰의 조립 과정이나 필요한 재료들은 일률적이며, 이들의 조립과정도 모두 같다. 각각의 클라이언트는 각각의 재료에 대한 팩토리가 아닌 완성되는 제품에 대한 재료 팩토리를 가지면 되는 것 아닐까? 아래와 같은 코드처럼 말이다.
SmartPhoneFactory factory = GalaxySFactory.getInstance();
Display display = factory.createDisplay();
Ram ram = factory.createRam();
Battery battery = factory.createBattery();
AP ap = factory.createAP(battery);
그리고 만약에 해당 공장(Client
)에서 생산하는 제품이 바뀌는 경우도 아래와 같이 수정해주면 된다.
SmartPhoneFactory factory = IPhoneFactory.getInstance();
기존의 여러곳에서 벤더를 적절하게 수정한 것보다 훨씬 수정의 부분이 적어졌다. 특 기존의 factory method pattern
과 다르게 생성하는 대상 객체
(제품)의 구성하는 부분을 추상화하여 제품Factory를 만든 것이다.
또한 클라이언트에서 해당 팩토리의 인스턴스를 외부에서 주입하도록 설계한다면, 의존성을 클라이언트와 구현팩토리간의 직접의존성을 완전히 끊을 수 있게 된다.
위 내용의 factory
부분만 아래와 같이 구현한다.
interface SmartPhoneFactory {
public AP createAP(Battery battery);
public Battery createBattery();
}
class GlaxySFactory implements SmartPhoneFactory {
private static final GlaxySFactory FACTORY = new GlaxySFactory();
private GlaxySFactory () {}
public static SmartPhoneFactory getInstance () {
return FACTORY;
}
@Override
public AP createAP(Battery battery) {
return new SamsungExynos(battery);
}
@Override
public Battery createBattery() {
return new SamsungBattery();
}
}
class IPhoneFactory implements SmartPhoneFactory {
private static final IPhoneFactory FACTORY = new IPhoneFactory();
private IPhoneFactory () {}
public static SmartPhoneFactory getInstance () {
return FACTORY;
}
@Override
public AP createAP(Battery battery) {
return new AppleAX(battery);
}
@Override
public Battery createBattery() {
return new LGBattery();
}
}
해당 팩토리는 여러개의 인스턴스가 필요 없으므로 싱글톤 패턴을 통해 인스턴스를 제공하도록 한다.
class ClientFactory {
private SmartPhoneFactory factory;
public SmartPhoneFactory getFactory() {
return factory;
}
public void setFactory(SmartPhoneFactory factory) {
this.factory = factory;
}
public void createPhone () {
System.out.println("Create Smart Phone");
factory.createAP(factory.createBattery());
}
}
해당 팩토리를 의존하고 있는 클라이언트 팩토리는 get
, set
을 통해 생산 주체를 변경할 수 있다. 또한 의존 클래스와 팩토리 구현 클래스간의 강 결합을 해소할 수 있었다.
public static void main(String[] args) throws Exception {
ClientFactory clientFactory = new ClientFactory();
clientFactory.setFactory(GlaxySFactory.getInstance()); // Galaxy S 생산
clientFactory.createPhone();
clientFactory.setFactory(IPhoneFactory.getInstance()); // IPhone 생산
clientFactory.createPhone();
}
실제 사용 코드에서는 ClientFactory
에 어떤 제품의 구성 요소에 대한 SmartPhoneFactory
클래스의 인스턴스를 받아 set
을 통해 설정한다.
만약 해당 ClientFactory
의 생상 주체가 Galaxy
에서 IPhone
으로 변경되었다면 실제 사용 클래스에서 set
을 통해 주입해주면 된다.
Summary
abstract factory
패턴은 특정한 객체(제품)를 생성함에 있어 그것을 구성하는 부분을 추상화하여 여러 서브클래스가 상속/구현하여 객체의 구성을
동일시하여 일관되게 관리할 수 있도록 도와준다.
'소프트웨어 > 디자인패턴' 카테고리의 다른 글
[소프트웨어/디자인패턴] 메멘토 패턴(Memento Pattern) (0) | 2018.06.13 |
---|---|
[소프트웨어/디자인패턴] 방문자 패턴(Visitor Pattern) (0) | 2018.05.21 |
[소프트웨어/디자인패턴] 컴포지트 패턴 (Composite Pattern) (0) | 2017.02.25 |
[소프트웨어/디자인패턴] 퍼사드 패턴 (Facade Pattern) (0) | 2017.02.24 |
[소프트웨어/디자인패턴] 어댑터 패턴 (Adapter Pattern) (0) | 2017.02.24 |