[GoF의 디자인 패턴] 추상 팩토리 패턴 (Abstract Factory) (1/23)

Seyeong·2022년 12월 30일
0

GoF의 디자인패턴

목록 보기
2/5

이 글에서는 먼저 추상 팩토리 패턴을 적용하기 전과 후를 나누어 구현함으로써 패턴에 대해 좀 더 이해하고 직접 코드로 비교해보는 방식으로 진행하겠습니다.

우선 추상 팩토리 패턴이 무엇인지 알아야겠죠.

추상 팩토리 패턴이란?

구체적인 클래스를 지정하지 않고 관련성을 갖는 객체들의 집합을 생성하거나 서로 독립적인 객체들의 집합을 생성할 수 있는 인터페이스를 제공하는 패턴이다.

라고 한다.

글만 읽어서는 이해가 쉽게 되지 않는다.
일단 클래스를 구체적으로 지정하지 않는다는게 무슨말인지도 이해가 안된다.

쉽게 말해 인터페이스를 적용하지 않고 클래스를 직접적으로 선언할 때 구체적이라고 표현한다.

예시로

public class Apple {
	...
}

Apple 이라는 클래스가 있을 때

Apple apple = new Apple();

처럼 클래스명대로 직접 선언하는것을 '구체적' 으로 클래스를 지정한다고 한다.

그럼 당연히 클래스를 구체적으로 지정하지 어떻게 지정한다는 것인가?

아래는 인터페이스를 적용한 Apple 클래스이다.

public interface Fruit {
	...
}


public class Apple implements Fruit {
	...
}

인터페이스를 상속받은 Apple은 이제 더이상 위와 같이 선언할 필요가 없다.

// Apple apple = new Apple(); 
Fruit fruit = new Apple();

이렇게 과일이라고 이름을 추상적으로 지정해줄 수 있다. 물론 위의 코드에는 new Apple( ) 로 과일의 종류가 사과라고 눈에 보이지만 저런 사실을 모른채, fruit 변수만 사용한다면 과일의 구체적인 타입이 Apple인지 Banana인지 알 수가 없다.

추상 팩토리 패턴은 이렇듯 구체적인 클래스를 지정하지 않는 설계 방식이다.

가볍게 추상 팩토리 패턴을 맛보기 해보았고, 제대로 된 예시로 이 패턴을 이해해보자.

추상 팩토리 패턴 적용 전

삼성 공장

삼성의 공장이 하나 있고, 여기에선 스마트폰과 노트북을 생성할 수 있다고 가정해보자.

public class SamsungFactory {
    public void makeGalaxyPhone() {
        System.out.println("갤럭시 폰 생성");
    }

    public void makeSamsungLaptop() {
        System.out.println("삼성 노트북 생성");
    }
}

이제 아래와 같이 호출하면 스마트폰과 노트북을 생성할 수 있을 것이다.

public class Main {
    public static void main(String[] args) {
        SamsungFactory samsungFactory = new SamsungFactory();
        samsungFactory.makeGalaxyPhone();// 갤럭시폰 생성 요청
        samsungFactory.makeSamsungLaptop(); // 삼성 노트북 생성 요청
    }
}

그런데 갑자기 삼성 제품이 아니라, 애플 제품을 생성하고 싶어지면 어떻게 해야할까?

애플 공장

public class AppleFactory {
    public void makeIPhone() {
        System.out.println("아이폰 생성");
    }

    public void makeMacbook() {
        System.out.println("맥북 생성");
    }
}

애플 공장을 하나 만들고 스마트폰과 노트북을 생성할 수 있게 해주어야 한다. 이들을 실제로 생성해보자.

public class Main {
    public static void main(String[] args) {
        AppleFactory appleFactory = new AppleFactory();
        appleFactory.makeIPhone();
        appleFactory.makeMacbook();
    }
}

이제 성공적으로 삼성 제품이 아닌 애플 제품을 생성할 수 있게 되었다. 이제 끝난걸까?
이렇게 설계를 한다면 만약 다시 삼성 제품을 만들고 싶거나, 앞으로 새로운 제품을 만들고 싶어질 때마다 이런식으로 코드를 변경해주어야 한다. 그냥 변경하면 되지 않냐고? 너무 비효율적이지 않은지 생각해보자.

예로 삼성 애플 바나나 이런식의 버튼이 있을때 '단 한번의 선택' 으로 만들고자 하는 제품의 종류가 바뀐다면 어떨까? 훨씬 편하지 않을까?

이쯤에서 추상 팩토리 패턴을 다시 봐보자.

구체적인 클래스를 지정하지 않고 관련성을 갖는 객체들의 집합을 생성하거나 서로 독립적인 객체들의 집합을 생성할 수 있는 인터페이스를 제공하는 패턴이다.

구체적인 클래스를 지정하지 않고 ~~~
처음에 강조했듯 추상 팩토리 패턴은 구체적인 클래스를 지정하지 않는다고 한다.

우리가 위에서 작성한 코드는 '삼성' 공장, '애플' 공장 식으로 매우 구체적으로 공장을 명시하고 있다. 좋은 점은 어떤 제품을 만드는 공장인지 한눈에 알아보기 쉽지만, 안좋은 점은 제품 생성의 유연성이 떨어진다. 즉, 다른 제품을 생성하도록 바꾸기가 쉽지 않다는 것이다.

이제 추상 팩토리 패턴을 적용하여 구체적이지 않게 작성해보고 이들을 비교해보자

추상 팩토리 패턴 적용 후

추상 팩토리 패턴은 설명에도 적혀있듯 인터페이스를 제공하는 패턴이다.
인터페이스를 이용하여 클래스를 구체적이지 않게 만들 수 있다.

우선 '삼성' 공장, '애플' 공장 으로 구체적으로 명시했던 공장을 하나의 인터페이스로 추상화하자.

public interface Factory {
}

'공장' 이라는 인터페이스를 만들었다. 이 공장은 삼성을 의미하는지 애플을 의미하는지 알 수 없다. 그냥 '공장' 이라고 추상적으로 표현했다.

여기에 오퍼레이션을 추가해보자. 쉽게 말해 메서드를 의미한다.

public interface Factory {
    
    void makePhone();

    void makeLaptop();
    
}

공장은 스마트폰과 노트북을 만들 수 있다. 인터페이스는 메서드를 선언만 하고 구현을 하지 않으므로 인터페이스 설계가 끝났다.

이제 '삼성' 공장과 '애플' 공장이 각각 위의 인터페이스를 상속받게 해보자.

public class SamsungFactory implements Factory {
}


public class AppleFactory implements Factory {
}

이제 인터페이스에 존재하는 메서드를 구현해야 한다. 이 메서드또한 추상적이다. 기존에 삼성 공장에서 스마트 폰을 생성하는 메서드는 makeGalaxyPhone( ) 이고 애플 공장에서 스마트 폰을 생성하는 메서드는 makeIPhone( ) 이었다.

인터페이스에서 스마트 폰을 생성하는 메서드는 makePhone( ) 으로 무슨 폰을 생성하는지 구체적이지 않다.

이제 삼성과 애플 각각의 인터페이스를 구현해주자.

public class SamsungFactory implements Factory {
    @Override
    public void makePhone() {
        System.out.println("갤럭시 폰 생성");
    }

    @Override
    public void makeLaptop() {
        System.out.println("삼성 노트북 생성");
    }
}


public class AppleFactory implements Factory {
    @Override
    public void makePhone() {
        System.out.println("아이폰 생성");
    }

    @Override
    public void makeLaptop() {
        System.out.println("맥북 생성");
    }
}

이렇게 인터페이스를 구현하도록 공장을 변경해주었다. 이제 이들을 호출하는 부분도 변경되어야 한다.

public class NonAbstractFactoryMain {
    public static void main(String[] args) {
        Factory factory = new SamsungFactory(); <<< 구체적이지 않은 타입
        factory.makePhone();
        factory.makeLaptop();
    }
}

Factory factory = new SamsungFactory(); 부분을 보면 factory 변수를 SamsungFactory 타입으로 지정해주지 않고 Factory 타입으로 추상적으로 표현했다.

이렇게 함으로써 그 아래 폰을 만들고 노트북을 만드는 부분도 구체적으로 어떤 폰인지 어떤 노트북인지 명시할 필요가 없어졌다.

이렇게 했을때의 장점은 변경이 발생했을 때 비로소 진가가 드러난다.
실제로 삼성이 아닌 애플의 제품을 생성하도록 변경해보자.

public class NonAbstractFactoryMain {
    public static void main(String[] args) {
        Factory factory = new AppleFactory(); <<< 이 부분에만 변경 발생
        factory.makePhone();
        factory.makeLaptop();
    }
}

new 인스턴스로 생성해주는 부분 외에는 어느 곳에서도 변경이 발생하지 않았다.

기존과 비교를 해보면

public class Main {
    public static void main(String[] args) {
    	// 삼성 제품 생성
        SamsungFactory samsungFactory = new SamsungFactory();
        samsungFactory.makeGalaxyPhone();
        samsungFactory.makeSamsungLaptop();
        
        // 애플 제품 생성
        AppleFactory appleFactory = new AppleFactory();
        appleFactory.makeIPhone();
        appleFactory.makeMacbook();
    }
}

관련된 메서드까지 전부 변경해주던 것에서 훨씬 간편해졌다.

마지막으로 구현체를 분리하는 리팩토링을 하고 마무리하자.

public class NonAbstractFactoryMain {
    public static void main(String[] args) {
        Factory factory = getFactory();
        factory.makePhone();
        factory.makeLaptop();
    }

    private static Factory getFactory() { 	// 오직 이곳에서 
        return new SamsungFactory(); 		// 원하는 제품의 공장을 
        // return new AppleFactory(); 		// 선택하면 된다.
    }
}

결과

추상 팩토리 패턴을 쓰면서 얻는 이익과 부담은 아래와 같습니다.

장점

  • 구체적인 클래스를 분리합니다. 추상 팩토리 패턴을 쓰면 응용프로그램이 생성할 객체의 클래스를 제어할 수 있습니다. 프로그램은 추상 인터페이스(Factory) 를 통해서만 인스턴스를 조작합니다.

  • 쉽게 대체할 수 있도록 합니다. Factory는 필요한 모든 제품을 생성하기 때문에 전체 제품군(폰, 노트북) 은 한번에 변경이 가능합니다. 또한, 마지막 리팩토링된 코드를 보면 알 수 있듯이 구체적인 제품의 타입(삼성, 애플) 을 설정하는 부분이 전체 코드에서 딱 한 곳에서만 결정되기 때문에 변경에 쉽고 유연하게 대처할 수 있습니다.

단점

  • 새로운 종류의 제품을 제공하기 어렵습니다. 여기서 새로운 종류의 제품은 폰, 노트북같은 제품을 말합니다. 만약 공장에서 폰과 노트북 뿐만 아니라 펜슬을 추가로 생산하려고 하면 인터페이스 상속 특성상 이를 상속받는 모든 하위클래스에게 펜슬을 생산하라는 오퍼레이션을 추가해야 합니다.

0개의 댓글