본문 바로가기

소프트웨어/디자인패턴

[소프트웨어/디자인패턴] 추상 팩토리 패턴 (Abstract Factory Pattern)

Abstract Factory Pattern

Context

스마트폰을 조립하는 공장을 생각해보면 아래와 같은 모델이 어느정도 연상될 것이다.

기본적으로 AP, Battery는 스마트폰을 구성하는 중요한 요소이며 갤럭시는 SamsungExynosSamsungBattery를, 애플의 아이폰은 AppleAXLG 배터리를 사용한다. 그리고 이들의 인스턴스는 각각의 팩토리에서 생산된다. 어느 제품에 대한 배터리나 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패턴은 특정한 객체(제품)를 생성함에 있어 그것을 구성하는 부분을 추상화하여 여러 서브클래스가 상속/구현하여 객체의 구성을 동일시하여 일관되게 관리할 수 있도록 도와준다.