Abstract Factory 패턴은 관련된 객체들을 생성하는 인터페이스를 제공하여 객체 생성에 대한 책임을 분리하는 디자인 패턴입니다. 이 패턴은 서로 연관된 다수의 객체를 생성해야 할 때 유용하며, 구체적인 클래스에 의존하지 않고 객체를 생성할 수 있도록 합니다.

Abstract Factory 패턴의 개요

Abstract Factory 패턴은 클라이언트가 객체를 생성하는 대신, 추상 팩토리 인터페이스를 통해 관련 객체들을 생성합니다. 추상 팩토리는 관련된 객체들의 집합에 대한 인터페이스를 제공하며, 구체적인 팩토리 클래스는 이 인터페이스를 구현하여 실제 객체를 생성합니다. 이를 통해 클라이언트는 구체적인 클래스에 직접 의존하지 않고도 객체를 생성할 수 있습니다.

예제 코드

// 추상 제품 A
public interface AbstractProductA {
    void performAction();
}

// 추상 제품 B
public interface AbstractProductB {
    void performAction();
}

// 구체적인 제품 A1
public class ConcreteProductA1 implements AbstractProductA {
    @Override
    public void performAction() {
        System.out.println("ConcreteProductA1에서 동작 수행");
    }
}

// 구체적인 제품 A2
public class ConcreteProductA2 implements AbstractProductA {
    @Override
    public void performAction() {
        System.out.println("ConcreteProductA2에서 동작 수행");
    }
}

// 구체적인 제품 B1
public class ConcreteProductB1 implements AbstractProductB {
    @Override
    public void performAction() {
        System.out.println("ConcreteProductB1에서 동작 수행");
    }
}

// 구체적인 제품 B2
public class ConcreteProductB2 implements AbstractProductB {
    @Override
    public void performAction() {
        System.out.println("ConcreteProductB2에서 동작 수행");
    }
}

// 추상 팩토리 인터페이스
public interface AbstractFactory {
    AbstractProductA createProductA();
    AbstractProductB createProductB();
}

// 구체적인 팩토리 A
public class ConcreteFactoryA implements AbstractFactory {
    @Override
    public AbstractProductA createProductA() {
        return new ConcreteProductA1();
    }

    @Override
    public AbstractProductB createProductB() {
        return new ConcreteProductB1();
    }
}

// 구체적인 팩토리 B
public class ConcreteFactoryB implements AbstractFactory {
    @Override
    public AbstractProductA createProductA() {
        return new ConcreteProductA2();
    }

    @Override
    public AbstractProductB createProductB() {
        return new ConcreteProductB2();
    }
}

// 클라이언트 코드
public class Client {
   

 private AbstractProductA productA;
    private AbstractProductB productB;

    public Client(AbstractFactory factory) {
        productA = factory.createProductA();
        productB = factory.createProductB();
    }

    public void performActions() {
        productA.performAction();
        productB.performAction();
    }

    public static void main(String[] args) {
        AbstractFactory factoryA = new ConcreteFactoryA();
        Client clientA = new Client(factoryA);
        clientA.performActions();
        // 출력:
        // ConcreteProductA1에서 동작 수행
        // ConcreteProductB1에서 동작 수행

        AbstractFactory factoryB = new ConcreteFactoryB();
        Client clientB = new Client(factoryB);
        clientB.performActions();
        // 출력:
        // ConcreteProductA2에서 동작 수행
        // ConcreteProductB2에서 동작 수행
    }
}

Abstract Factory 패턴의 장점

  • 관련된 객체들을 생성하기 위한 일관된 인터페이스를 제공하여 객체 생성에 대한 책임을 분리합니다.
  • 클라이언트는 구체적인 클래스에 직접 의존하지 않고도 객체를 생성할 수 있습니다.
  • 객체들의 일관성과 호환성을 유지할 수 있습니다.
  • 새로운 구체 팩토리 클래스를 추가하여 다양한 객체 구성을 생성할 수 있습니다.

Factory 패턴과 Abstract Factory 패턴 비교

앞서 Factory 패턴의 글에서 말했던 것처럼 과도한 Factory 패턴의 사용이 불필요한 분기문으로 가독성이 떨어지는 상황이 발생할 수 있습니다. 아래 예제는 Factory 패턴의 과한 사용으로 코드 가독성이 떨어지는 예제입니다.

// 동물 인터페이스
public interface Animal {
    void makeSound();
}

// 고양이 클래스
public class Cat implements Animal {
    @Override
    public void makeSound() {
        System.out.println("야옹");
    }
}

// 개 클래스
public class Dog implements Animal {
    @Override
    public void makeSound() {
        System.out.println("멍멍");
    }
}

// 새 클래스
public class Bird implements Animal {
    @Override
    public void makeSound() {
        System.out.println("짹짹");
    }
}

// Animal을 생성하는 AnimalFactory 클래스
public class AnimalFactory {
    public static Animal createAnimal(String type) {
        if (type.equalsIgnoreCase("고양이")) {
            return new Cat();
        } else if (type.equalsIgnoreCase("개")) {
            return new Dog();
        } else if (type.equalsIgnoreCase("새")) {
            return new Bird();
        } else if (type.equalsIgnoreCase("토끼")) {
            return new Rabbit();
        } else if (type.equalsIgnoreCase("뱀")) {
            return new Snake();
        }
        return null;
    }
}

// 토끼 클래스
public class Rabbit implements Animal {
    @Override
    public void makeSound() {
        System.out.println("깡총깡총");
    }
}

// 뱀 클래스
public class Snake implements Animal {
    @Override
    public void makeSound() {
        System.out.println("실실");
    }
}

// 클라이언트 코드
public class Client {
    public static void main(String[] args) {
        Animal cat = AnimalFactory.createAnimal("고양이");
        cat.makeSound(); // 출력: "야옹"

        Animal dog = AnimalFactory.createAnimal("개");
        dog.makeSound(); // 출력: "멍멍"

        Animal bird = AnimalFactory.createAnimal("새");
        bird.makeSound(); // 출력: "짹짹"

        Animal rabbit = AnimalFactory.createAnimal("토끼");
        rabbit.makeSound(); // 출력: "깡총깡총"

        Animal snake = AnimalFactory.createAnimal("뱀");
        snake.makeSound(); // 출력: "실실"
    }
}

Abstract Factory 패턴을 사용하면 아래와 같이 리팩토링을 진행해 볼 수 있습니다.

// 동물 인터페이스
public interface Animal {
    void makeSound();
}

// 고양이 클래스
public class Cat implements Animal {
    @Override
    public void makeSound() {
        System.out.println("야옹");
    }
}

// 개 클래스
public class Dog implements Animal {
    @Override
    public void makeSound() {
        System.out.println("멍멍");
    }
}

// 새 클래스
public class Bird implements Animal {
    @Override
    public void makeSound() {
        System.out.println("짹짹");
    }
}

// 추상 동물 팩토리
public interface AnimalFactory {
    Animal createAnimal();
}

// 고양이 팩토리
public class CatFactory implements AnimalFactory {
    @Override
    public Animal createAnimal() {
        return new Cat();
    }
}

// 개 팩토리
public class DogFactory implements AnimalFactory {
    @Override
    public Animal createAnimal() {
        return new Dog();
    }
}

// 새 팩토리
public class BirdFactory implements AnimalFactory {
    @Override
    public Animal createAnimal() {
        return new Bird();
    }
}

// 클라이언트 코드
public class Client {
    public static void main(String[] args) {
        AnimalFactory catFactory = new CatFactory();
        Animal cat = catFactory.createAnimal();
        cat.makeSound(); // 출력: "야옹"

        AnimalFactory dogFactory = new DogFactory();
        Animal dog = dogFactory.createAnimal();
        dog.makeSound(); // 출력: "멍멍"

        AnimalFactory birdFactory = new BirdFactory();
        Animal bird = birdFactory.createAnimal();
        bird.makeSound(); // 출력: "짹짹"
    }
}

물론 Abstract Factory 패턴도 과도하게 사용하면 Factory 패턴과 마찬가지로 부작용이 생길 수 있으니 항상 주어진 상황과 해결하려는 방향에 맞게 적절하게 사용함을 기억해야 합니다.

Abstract Factory 패턴과 함께 사용되는 패턴

Abstract Factory 패턴과 함께 사용하면 좋은 패턴으로는 Singleton 패턴이 있습니다. Singleton 패턴을 사용하여 팩토리 클래스의 인스턴스가 단일하게 유지되도록 함으로써, 일관된 객체 생성을 보장할 수 있습니다.

이상으로 Abstract Factory 패턴에 대한 내용을 정리한 글입니다. 다른 패턴들과 함께 사용되거나 잘못 사용될 수 있는 경우 등에 대해서도 언급하였습니다. 이 글이 도움이 되기를 바랍니다.