목차
짝이 맞아야 하는 것들을 따로따로 만든다
화면에 버튼과 체크박스를 그린다. 테마는 라이트와 다크 둘이다. 그런데 만드는 코드가 여기저기 흩어져 있으면 이렇게 된다.
Button button = new LightButton();
Checkbox checkbox = new DarkCheckbox(); // 실수로 다크 - 테마가 섞였다버튼은 라이트인데 체크박스는 다크다. 한 화면에 두 테마가 섞였다. 아무도 이걸 막아주지 않는다. 각자 따로 new로 만들어지니, 짝이 맞는지는 만드는 사람이 매번 신경 써야 한다.
버튼·체크박스처럼 서로 맞아야 하는 것들이 따로따로 만들어지는 게 문제다.
추상 팩토리 = 관련된 것들을 한 세트로 만든다
추상 팩토리 패턴은 이렇게 말한다.
서로 관련되어 함께 쓰이는 객체들을, 구체 클래스를 지정하지 않고 한 세트로 만들어라.
버튼 하나를 만드는 게 아니라, **“라이트 세트를 만들어라”**라고 하면 라이트 버튼과 라이트 체크박스가 짝으로 나온다. 세트로 만드니 섞일 수가 없다.
제품군을 인터페이스로 세운다
먼저 제품들을 인터페이스로 세운다.
public interface Button { void render(); }
public interface Checkbox { void render(); }
public class LightButton implements Button { public void render() {} }
public class LightCheckbox implements Checkbox { public void render() {} }
public class DarkButton implements Button { public void render() {} }
public class DarkCheckbox implements Checkbox { public void render() {} }Button과 Checkbox가 한 제품군이고, 라이트/다크가 그 군의 두 변형이다.
세트를 만드는 팩토리
이제 세트를 통째로 만드는 팩토리를 인터페이스로 둔다. 만들 제품마다 메서드가 하나씩이다.
public interface ThemeFactory {
Button createButton();
Checkbox createCheckbox();
}
public class LightThemeFactory implements ThemeFactory {
public Button createButton() { return new LightButton(); }
public Checkbox createCheckbox() { return new LightCheckbox(); }
}
public class DarkThemeFactory implements ThemeFactory {
public Button createButton() { return new DarkButton(); }
public Checkbox createCheckbox() { return new DarkCheckbox(); }
}LightThemeFactory는 라이트만, DarkThemeFactory는 다크만 만든다. 한 팩토리 안에서는 같은 군의 제품만 나온다.
한 팩토리가 일관된 세트를 보장한다
쓰는 쪽은 팩토리 하나를 받아서 필요한 걸 다 만든다.
public class Page {
private final Button button;
private final Checkbox checkbox;
public Page(ThemeFactory factory) { // 팩토리 하나를 받는다
this.button = factory.createButton();
this.checkbox = factory.createCheckbox(); // 같은 팩토리 → 같은 테마
}
}LightThemeFactory를 넘기면 버튼도 체크박스도 라이트다. 라이트 버튼에 다크 체크박스가 섞이는 일이 일어날 수가 없다. 짝을 맞추는 책임이 팩토리로 넘어가서, 쓰는 쪽은 그냥 “이 세트로 만들어줘”만 하면 된다.
팩토리 메서드와 무엇이 다른가
이름이 비슷해 헷갈리기 쉬우니 갈라 두자.
- 팩토리 메서드는 제품을 하나 만든다. 하위 클래스가 그 메서드를 오버라이드해 무엇을 만들지 정한다(상속).
- 추상 팩토리는 관련 제품 **여럿(세트)**을 만든다. 팩토리 객체에 만들 제품마다 메서드가 있다(조합).
사실 추상 팩토리의 각 메서드(createButton·createCheckbox)가 하나하나는 팩토리 메서드다. 그러니 추상 팩토리는 여러 팩토리 메서드를 한 객체에 묶어, 그 결과들이 서로 맞도록 보장한 것으로 볼 수 있다. 초점이 “무엇을 만드나”에서 **“함께 쓰는 것들이 맞아떨어지나”**로 옮겨간 것이다.
강점과 약점
추상 팩토리에는 뚜렷한 강점과 약점이 있다.
- 강점 - 새 제품군은 쉽다. 고대비(High Contrast) 테마를 더하고 싶으면
HighContrastThemeFactory하나만 만들면 된다. 기존 팩토리도 쓰는 코드도 안 바뀐다. - 약점 - 새 제품 종류는 어렵다. 세트에 슬라이더를 추가하려면,
ThemeFactory인터페이스에createSlider()를 넣고 모든 구현 팩토리를 다 고쳐야 한다.
즉 추상 팩토리는 제품군을 늘리는 데는 열려 있고, 제품 종류를 늘리는 데는 닫혀 있다. 세트 구성이 안정적이고, 그 세트의 변형이 늘어나는 상황에 잘 맞는다.
실무: 세트로 갈리는 곳
실무에서 추상 팩토리는 **“이것들이 통째로 한 벌로 갈린다”**는 구조에서 쓴다. 테마·OS별 UI, 운영/테스트 환경별 리소스 세트, 특정 벤더에 맞춘 클라이언트 묶음 같은 것들이다.
반대로, 만들 제품이 하나뿐이거나 세트 사이의 일관성이 중요하지 않다면 과하다. 그럴 땐 제품 하나를 다루는 더 가벼운 방법이 낫다. 추상 팩토리의 값어치는 오직 **“짝이 어긋나면 안 된다”**는 제약이 실재할 때 나온다.
정리
| 추상 팩토리란 | 서로 맞아야 하는 관련 객체들을 한 세트로 만든다 |
| 문제 | 따로 만들면 짝이 어긋난다(라이트 버튼 + 다크 체크박스) |
| 고침 | 세트를 만드는 팩토리를 두고, 한 팩토리가 같은 군만 만들게 한다 |
| 보장 | 한 팩토리를 쓰면 제품들이 자동으로 일관된다 - 섞일 수 없다 |
| 팩토리 메서드와 | FM은 제품 하나, AF는 관련 제품 세트 |
| 강점/약점 | 새 제품군은 쉽고, 새 제품 종류는 모든 팩토리를 고쳐야 |
함께 쓰는 것들이 어긋나지 않게, 통째로 한 세트로 만드는 것. 짝을 맞추는 책임을 팩토리에 몰아주는 것 - 그게 추상 팩토리다.