new 연산자를 사용하면 구상 클래스의 인스턴스가 만들어진다. 특정 구현을 바탕으로 코딩하면 나중에 코드를 수정해야 할 가능성이 커지고, 유연성이 떨어지게 된다.
Duck duck = new MallardDuck();
인터페이스 Duck을 써서 코드를 유연하게 만들려고 하지만 그럼에도 구상 클래스의 인스턴스를 만들어야 한다. 만약 일련의 구상 클래스가 있다면 어쩔 수 없이 다음과 같이 코드를 만들어야 한다.
Duck duck;
if (picnic) {
duck = new MallardDuck();
} else if (hunting) {
duck = new DecoyDuck();
} else if (inBathTub) {
duck = new RubberDuck();
}
이 코드를 보면 구상 클래스의 인스턴스가 여러 개 있으며, 실행 시에 주어진 조건에 따라 인스턴스의 형식이 결정된다는 것을 알 수 있다. 이런 코드를 변경 또는 확장하려고 하면 새로운 코드를 추가하거나 기존 코드를 삭제해야 한다. 관리와 갱신이 어려워지는 것이다.
하지만 팩토리 메서드 패턴을 사용하면 확장에는 열려있지만 변경에는 닫혀있게 만들 수 있다. 쉽게 말하면 새로운 기능 추가에는 열려있고, 기능 추가로 인한 기존 코드의 변경에는 닫혀있다고 생각하면 된다.
팩토리 메서드 패턴은 객체 생성을 공장(
Factory) 클래스로 캡슐화 처리하여 대신 생성하게 하는 객체 생성 관련 디자인 패턴이다.
즉, new 연산자를 통해 특정 제품 객체를 생성하는 것이 아닌, 제품 객체들을 도맡아 생성하는 공장 클래스를 만들고, 이를 상속하는 서브 공장 클래스의 메서드에서 여러가지 제품 객체 생성을 각각 책임지는 것이다.

빨간 구역이 공장 객체이고, 노란 구역이 제품 객체이다. 공장 객체와 제품 객체가 느슨한 결합 구조로 되어있다.
Creator: 최상위 공장 클래스로서, 팩토리 메서드를 추상화하여 서브 클래스로 하여금 구현하도록 한다.someOperation(): 객체 생성에 관한 전/후처리를 템플릿화한 메서드createProduct(): 서브 공장 클래스에서 재정의할 객체 생성 추상 메서드ConcreteCreator: 각 서브 공장 클래스들은 이에 맞는 제품 객체를 반환하도록 생성 추상 메서드를 재정의한다. 즉, 제품 객체 하나마다 그에 맞는 생산 공장 객체가 위치된다.Product: 제품 구현체를 추상화ConcreteProduct: 제품 구현체
public interface Product {
void doSomething();
}
public class ConcreteProductA implements Product {
@Override
public void doSomething() {
System.out.println("ConcreteProductA.doSomething()");
}
}
public class ConcreteProductB implements Product {
@Override
public void doSomething() {
System.out.println("ConcreteProductB.doSomething()");
}
}
인터페이스인 Product의 구현체인 ConcreteProductA와 ConcreteProductB 클래스이다.
public interface Creator {
default Product someOperation() {
Product product = createProduct(); // 서브 클래스에서 구체화한 팩토리 메서드 실행
product.doSomething();
return product;
}
Product createProduct();
}
// 공장 객체 A
public class ConcreteCreatorA implements Creator {
@Override
public Product createProduct() {
return new ConcreteProductA(); // 제품 구현 객체 A를 생성한 뒤 반환
}
}
// 공장 객체 B
public class ConcreteCreatorB implements Creator {
@Override
public Product createProduct() {
return new ConcreteProductB(); // 제품 구현 객체 B를 생성한 뒤 반환
}
}
Creator 인터페이스를 구현하고 있는 ConcreteCreatorA와 ConcreteCreatorB이다. 여기서 Creator를 추상 클래스가 아닌 인터페이스로 만들었는데, 자바 8이상이라면 default 메서드를 사용해서 인터페이스 안에 공통적으로 돌아가는 메서드를 구현할 수 있기 때문이다. 자바 9이상부터는 private도 사용할 수 있다.
코드를 잠깐 살펴보면 Creator 안 someOperation()을 보면 createProduct()를 사용해서 Product 인터페이스 타입의 객체를 생성하고 있는 것을 볼 수 있다. 그리고 createProduct()는 추상 메서드이기 때문에 하위 객체에서 무조건 구현해야 한다.
즉, Creator를 구현하는 구현체에서 Product 타입의 구현 객체를 지정해서 생성한 뒤 반환할 수 있게 되는 것이다. 그리고 해당 객체에 맞는 추가적인 메서드(doSomething())가 실행되는 것이다.
public class Client {
public static void main(String[] args) {
Creator creatorA = new ConcreteCreatorA();
Creator creatorB = new ConcreteCreatorB();
Product productA = creatorA.createOperation();
Product productB = creatorB.createOperation();
}
}
ConcreteProductA.doSomething()
ConcreteProductB.doSomething()
ConcreteCreatorA, ConcreteCreatorB)로 Creator 구현createOperation() 호출Product를 구현하고 있는 제품을 생성하고, 추가적인 공정(doSomething())을 지나 새로운 객체를 반환Product와 Creator간의 결합도를 느슨하게 가져갔기 때문이다.위의 내용을 이해하기 쉽게 예제로 다시 한 번 보자.

public class Ship {
private String name;
private String email;
private String logo;
private String color;
// getter & setter
@Override
public String toString() {
return "Ship{" +
"name='" + name + '\'' +
", logo='" + logo + '\'' +
", color='" + color + '\'' +
'}';
}
}
public class ShipFactory {
public static Ship orderShip(String name, String email) {
// validate
if (name == null || name.isBlank()) {
throw new IllegalArgumentException("배 이름을 지어주세요");
}
if (email == null || email.isBlank()) {
throw new IllegalArgumentException("연락처를 남겨주세요");
}
prepareFor(name);
Ship ship = new Ship();
ship.setName(name);
// Customizing for specific name
if (name.equalsIgnoreCase("whiteship")) {
ship.setLogo("⛴");
} else if (name.equalsIgnoreCase("blackship")) {
ship.setLogo("⚓");
}
// coloring
if (name.equalsIgnoreCase("whiteship")) {
ship.setColor("white");
} else if (name.equalsIgnoreCase("blackship")) {
ship.setColor("black");
}
// notify
sendEmailTo(email, ship);
return ship;
}
private static void prepareFor(String name) {
System.out.println(name + " 만들 준비 중...");
}
private static void sendEmailTo(String email, Ship ship) {
System.out.println(ship.getName() + " 다 만들었습니다.");
}
}
public class Client {
public static void main(String[] args) {
Ship whiteShip = ShipFactory.orderShip("WhiteShip", "myemail123@email.com");
System.out.println(whiteShip);
Ship blackShip = ShipFactory.orderShip("BlackShip", "myemail123@email.com");
System.out.println(blackShip);
}
}
WhiteShip 만들 준비 중...
WhiteShip 다 만들었습니다.
Ship{name='WhiteShip', logo='⛴', color='white'}
BlackShip 만들 준비 중...
BlackShip 다 만들었습니다.
Ship{name='BlackShip', logo='⚓', color='black'}

public class Ship {
private String name;
private String email;
private String logo;
private String color;
// getter & setter
@Override
public String toString() {
return "Ship{" +
"name='" + name + '\'' +
", logo='" + logo + '\'' +
", color='" + color + '\'' +
'}';
}
}
public class WhiteShip extends Ship {
public WhiteShip() {
setName("WhiteShip");
setColor("white");
setLogo("⛴");
}
}
public class BlackShip extends Ship {
public BlackShip() {
setName("BlackShip");
setColor("black");
setLogo("⚓");
}
}
public interface ShipFactory {
Ship createShip();
default Ship orderShip(String name, String email) {
validate(name, email);
prepareFor(name);
Ship ship = createShip();
sendEmailTo(email, ship);
return ship;
}
private void validate(String name, String email) {
// validate
if (name == null || name.isBlank()) {
throw new IllegalArgumentException("배 이름을 지어주세요");
}
if (email == null || email.isBlank()) {
throw new IllegalArgumentException("연락처를 남겨주세요");
}
}
private static void prepareFor(String name) {
System.out.println(name + " 만들 준비 중...");
}
private static void sendEmailTo(String email, Ship ship) {
System.out.println(ship.getName() + " 다 만들었습니다.");
}
}
public class WhiteShipFactory implements ShipFactory {
@Override
public Ship createShip() {
return new WhiteShip();
}
}
public class BlackShipFactory implements ShipFactory {
@Override
public Ship createShip() {
return new BlackShip();
}
}
public class Client {
public static void main(String[] args) {
Client client = new Client();
client.print(new WhiteShipFactory(), "whiteship", "myemail@email.com");
client.print(new BlackShipFactory(), "blackship", "myemail@email.com");
}
private void print(ShipFactory shipFactory, String name, String email) {
System.out.println(shipFactory.orderShip(name, email));
}
}
whiteship 만들 준비 중...
WhiteShip 다 만들었습니다.
Ship{name='WhiteShip', logo='⛴', color='white'}
blackship 만들 준비 중...
BlackShip 다 만들었습니다.
Ship{name='BlackShip', logo='⚓', color='black'}