디자인패턴을 공부하기 전 디자인패턴 뿐만 아니라 자주 나오는 용어지만 제대로 알지 못한 용어를 정리하고자합니다.
Concrete Class(구상클래스) vs Abstract Class (추상클래스) 추상 클래스는 객체 지향을 공부하며 많이 공부했을 것이라 생각합니다. 디자인 패턴을 공부하며 많이 나왔던 Concrete Class는 구체화된 클래스? 라고 생각하면 될 것같습니다. 즉, 모든 오퍼레이션의 구현을 제공하는 클래스 정도로 알고 있으면 될 것 같습니다. :)
[팩토리패턴] 1. 정의 - 모든 팩토리 패턴에는 객체 생성을 캡슐화 한다. - 팩토리 메서드 패턴과 추상 팩토리 패턴이 있다. - 팩토리 메서드 패턴 : 객체를 생성하기 위한 인터페이스를 정의하는데, 어떤 클래스의 인스턴스를 만들지는 서브클래스에서 결정한다. - 추상 팩토리 패턴 : 인터페이스를 이용하여 서로 연관된, 또는 의존하는 객체를 구상 클래스를 지정하지 않고도 생성할 수 있다. 추상 팩토리 패턴에는 팩토리 메서드 패턴이 포함될 수 있다. - 디자인 원칙 중 '추상화된 것에 의존하도록 만들어라. 구상클래스에 의존하지 않도록 만든다.'에 기인한 패턴.
팩토리 패턴의 핵식은 클래스의 인스턴스를 만드는 것을 서브클래스에서 결정하도록 한다 입니다. 즉, new 키워드를 사용하는 부분을 서브클래스에 위임함으로서 객체 생성을 캡슐화 하고 구상클래스에 대한 의존성이 줄어든다는 이점을 얻을 수 있습니다. 특히 구상클래스에 대한 의존성이 줄어드는 것은 의존성 뒤집기 원칙(Dependency Inversion Principle : DI)에 기인하는데, DI는 자바진영에서 널리 쓰이고 있는 Spring Framework의 핵심 개념 중 하나 입니다. 싱글톤패턴과 더불어 가장 유명하고 널리 쓰이는 디자인 패턴 중 하나라고 할 수 있겠습니다.
<<팩토리 메서드 패턴 개념도>>
<<추상 팩토리 패턴 개념도>>
2. 예시
팩토리패턴 예시를 어느 카페 커피 종류들을 이용해 예시를 들어보겠습니다. 원하는 커피를 주문 받으면 커피를 제공해야합니다.
음류 종류에 따라 음류를 생성할 수 있습니다. 하지만 위와 같이 코드를 작성하게 되면 변경이나 확장할 요소가 생길 시 매번 코드를 추가, 제거 해주어야 한다는 문제가 발생합니다. 위 문제는 객체 인스턴스를 생성하는(new 키워드를 사용하는) 부분을 별도의 인터페이스로 분리하면 해결할 수 있습니다. 이렇게 생성자를 별도의 인터페이스로 분리하여 객체를 만들어내는 공장(factory)로 이용하는 것이 팩토리 메서드 패턴입니다.
2.1 예 : 팩토리 메서드 패턴
위 코드에서 인스턴스를 생성하는 부분을 인터페이스로 분리해보겠습니다.
<<팩토리 메서드 패턴>>
UML로 그려보면 이런 모습이 됩니다. 각각의 Beverage 클래스들은 Beverage 추상 클래스를 상성받아 getName 메서드를 구현하고, BeverageFactory 추상클래스를 상속받은 TypeBeverageFactory에서는 type별로 Beverage 인스턴슬르 생성하도록 하는 createBeverage 메서드를 구현하도록 합니다. 먼저 각 Beverage 클래스 예시 코드입니다.
//Beverage Abstract class (Component) public abstract classBeverage{public abstract String getName();}//Beverage Concrete ClasspublicclassEspressoextendsBeverage{publicEspresso(){};public String getName(){return"Espresso";}}
인스턴스 생성을 서브클래스로 위임한 결과입니다. 최종 메인 메서드에서는 new 키워드를 사용하여 인스턴스를 생성한 부분이 없는 것을 확인할 수 있습니다. 이를 통해 메인 프로그램에서는 어떤 객체가 생성되었는지 신경 쓸 필요없이 반환된 객체만 사용하면 되고 Beverage클래스에서 변경이 발생해도 메인 프로그램이 변경되는 것은 최소화할 수 있습니다.
2.2 예시 : 추상 팩토리 패턴 추상 팩토리 패턴에 대한 예시입니다. 위의 정의에서 추상 팩토리 패턴은 인터페이스를 이용하여 서로 연관된, 또는 의존하는 객체를 구상 클래스를 지정하지 않고도 생성할 수 있다고 했습니다. 즉, 연관된 서브 클래스를 그룹화할 수 있고 이것은 이 그룹을 자유롭게 교체할 수 있는 패턴 이라고 할 수 있습니다.
BeverageAbstractFactory 인터페이스를 작성하고 이를 상속받아 각 커피를 생성하는 팩토리 클래스를 구성합니다. BeverageFactory에서는 이 팩토리를 파라미터로 받아 최종적으로 생성된 Beverage 객체를 반환하게 됩니다.
//Abstract Factory InterfacepublicinterfaceBeverageAbstractFactory{public Beverage createBeverage();}//DarkBlend factory classpublicclassDarkBlendFactoryimplementsBeverageAbstractFactory{
@Override
public Beverage createBeverage(){returnnewDarkBlend();}}//Concrete Class publicclassBeverageFactory{staticpublic Beverage getBeverage(BeverageAbstractFactory beverageAbstractFactory){return beverageAbstractFactory.createBeverage();}}
위 결과 코드와 같이 커피별 팩토리 클래스를 파라미터로 넘겨 각 커피 객체를 반환 받아 사용할 수 있습니다. 이렇게 되면 팩토리 클래스 교체 만으로 조금 더 유연하게 기능을 수정, 확장에 대처할 수 있게 됩니다. 코드상으로 if-else구문을 제거하여 조금 더 깔끔하게 코드를 구성할 수 있습니다.