신입 개발자를 위한 Repository를 만들었습니다. 공부한 내용을 정리 중이니 도움이 되신다면 와서 Star를 눌러주시면 감사하겠습니다.

안드로이드에서 Fragment를 생성할 때 클래스의 인스턴스 즉, 객체를 만드는 상황에서 new를 통해서 바로 생성하지 않고 newInstatnce()라는 메소드를 통해서 만든다. 이와 관련된 이유는 이전 포스팅을 참고하도록 하고 이런 방식을 팩토리 메소드?를 사용한다고 한다. 이것을 보고 팩토리 패턴에 대해 찾아보기로 하였다.

팩토리 패턴(Factory Pattern)

  • 모든 팩토리 패턴에서는 객체 생성을 캡슐화한다.
  • 팩토리 메소드 패턴과 추상 팩토리 패턴이 존재한다.
  • 팩토리 메소드 패턴 : 객체를 생성하기 위한 인터페이스를 정의하는데, 어떤 클래스의 인스턴스를 만들지는 서브 클래스에서 결정한다.
  • 추상 팩토리 패턴 : 인터페이스를 이용하여 서로 연관된, 또는 의존하는 객체를 구상 클래스를 지정하지 않고도 생성할 수 있다. 추상 팩토리 패턴에는 팩토리 메소드 패턴이 포함될 수 있다.
  • 디자인 원칙 중 '추상화된 것에 의존하도록 만들어라. 구상 클래스에 의존하지 않도록 만든다.'에 기인한 패턴이다.

팩토리 패턴의 핵심은 클래스의 인스턴스를 만드는 것을 서브클래스에서 결정하도록 한다는 것이다. 즉, new 키워드를 사용하는 부분을 서브클래스에 위임함으로서 객체 생성을 캡슐화하고 구상 클래스에 대한 의존성이 줄어든다는 이점을 얻을 수 있다.

특히 구상 클래스에 대한 의존성이 줄어드는 것은 의존성 뒤집기 원칙(Dependency Inversion Principle:DI)에 기인하는데, DI는 자바 진영에서 널리 쓰이고 있는 Spring 프레임워크의 핵심 개념 중 하나이다. 싱글톤 패턴과 더불어 가장 유명하고 널리 쓰이는 디자인 패턴 중 하나라고 할 수 있다.

팩토리 메소드 패턴

위에서 언급했듯이, 객체를 생성하기 위한 인터페이스를 정의하는데, 어떤 클래스의 인스턴스를 만들지는 서브 클래스에서 결정하도록 하는 것이다. 팩토리 메소드 패턴은 팩토리 메소드는 객체를 생성해서 반환하는 것을 말한다. 즉, 결과값이 객체인 것이다.

이점은 뭘까?
클래스의 변경사항이 생겼을 때 얼마나 다른 클래스에게도 영향을 줄 것인가가 결합도이다. 팩토리 메소드 패턴은 직저버 사용하는 객체를 생성하지 않고 팩토리 메소드 클래스를 통해 객체를 대신 새애성하고 그 객체를 반환 받아 사용하기 때문에 효율적인 코드 제어를 할 수 있을 뿐더러 결합도를 낮춰 유지보수가 용이하다.

예시
아이언맨 수트에 빗대어 예제를 설명하려고 한다. 아이언맨의 각 수트들은 각각의 고유한 기능들을 가지고 있기 때문에 특정 상황에 맞춰서 토니 스타크가 착용하는 수트도 달라지게 된다.(너무 간지…ㅎ) 우주 탐사를 할 때에는 스페이스 아머, 심해 탐사 시에는 하이드로 아머, 그리고 시네마틱 유니버스에서 나왔던 헐크버스터 등을 예로 들 수 있다.

위와 같이 특정 상황을 입력으로 보고 입력에 따라 다른 수트 클래스를 만들어야 하는 상황을 코드로 표현하면 아래와 같다.(사실 코드로 표현하지 않아도 된다. 하지만, 나는 컴공이니까…!)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Suit suit = null;

switch(type){
case("space"):
suit = new SpaceSuit();
break;
case("hydro"):
suit = new HydroSuit();
break;
case("stealth"):
suit = new StealthSuit();
break;
default:
suit = new CombatSuit();
}

이제 각 상황에 맞게 수트를 생성할 수 있게 되었고, 아이언맨도 활약을 할 수 있게 되었다. 하지만, 위와 같이 코드를 작성하게 되면 변경이나 확장할 요소가 생길 시 매번 코드를 추가, 제거해주어야 한다는 문제가 발생한다. 이로 인해 아이언맨도 활약할 시간이 지체된다.

위의 문제는 객체 인스턴스를 생성하는(new 키워드를 사용하는) 부분을 별도의 인터페이스로 분리하면 해결할 수 있다. 이렇게 생성자를 별도의 인터페이스로 분리하여 객체를 만들어내는 공장(factory)으로 이용하는 것팩토리 메소드 패턴이다.

  • 이제 위 코드에서 인스턴스를 생성하는 부분을 인터페이스로 분리해보자.
UML
  • 각가의 suit 클래스들은 Suit 추상 클래스를 상속받아 getName() 메소드를 구현하고 SuiitFactory 추상 클래스를 상속받은 TypeSuitFactory에서는 type별로 수트 인스턴스를 생성하도록 하는 createSuit 메소드를 구현하도록 한다.
  • Suit 추상 클래스
1
2
3
4
// Suit 추상 클래스
public abstract class Suit{
public abstract String getName();
}
  • Suit 추상 클래스를 구현한 각각의 수트 클래스들
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// SpaceSuit
public class SpaceSuit extends Suit{

@Override
public String getName(){
return "SpaceSuit";
}
}

// HydroSuit
public class HydroSuit extends Suit{

@Override
public String getName(){
return "HydroSuit";
}
}

// StealthSuit
public class StealthSuit extends Suit{

@Override
public String getName(){
return "StealthSuit";
}
}

// CombatSuit
public class CombatSuit extends Suit{

@Override
public String getName(){
return "CombatSuit";
}
}
  • Suit 팩토리 추상 클래스
1
2
3
public abstract class SuitFactory{
public abstract Suit createSuit(String type);
}
  • Suit 팩토리 추상 클래스를 구현한 Suit 팩토리 구현 클래스
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class TypeSuitFactory extends SuiFactory{

@Override
public createSuit(String type){
Suit suit = null;

switch(type){
case("space"):
suit = new SpaceSuit();
break;
case("hydro"):
suit = new HydroSuit();
break;
case("stealth"):
suit = new StealthSuit();
break;
default:
suit = new CombatSuit();
}
return suit;
}
}
  • Test를 해보자.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public static void main(String[] args){
TypeSuitFactory typeSuitFactory = new TypeSuitFactory();

Suit suit1 = typeSuitFactory.createSuit("stealth");
Suit suit2 = typeSuitFactory.createSuit("space");
Suit suit1 = typeSuitFactory.createSuit("");

System.out.println(suit1.getName());
System.out.println(suit2.getName());
System.out.println(suit3.getName());
}

/*
결과
StealthSuit
SpaceSuit
CombatSuit
*/

인스턴스 생성을 서브 클래스로 위임한 결과이다. 최종 메인 메소드에서는 new 키워드를 사용하여 인스턴스를 생성한 부분이 없는 것을 확인할 수 있다. 이를 통해 메인 프로그램에서는 어떤 객체가 생성되었는지 신경 쓸 필요 없이 반환된 객체만 사용하면 되고 슈트 클래스에서 변경이 발생해도 메인 프로그램이 변경되는 것은 최소화할 수 있다.

추상 팩토리 패턴

다음은 추상 팩토리 패턴에 관한 예시이다. 위의 정의에서 추상 팩토리 패턴은 인터페이스를 이용하여 서로 연관된, 또는 의존하는 객체를 구상 클래스를 지정하지 않고도 생성할 수 있다고했다. 즉, 연관된 서브 클래스를 그룹화할 수 있고 이것은 이 그룹을 자유롭게 교체할 수 있는 패턴이라고 할 수 있다.

UML

SuitAbstractFactory 인터페이스를 작성하고 이를 상속받아 각 슈트를 생성하는 팩토리 클래스를 구성한다. 그리고 SuitFactory에서는 이 팩토를 파라미터로 받아 최종적으로 생성된 슈트 객체를 반환하게 된다.

  • 추상 팩토리 인터페이스
1
2
3
4
public interface SuitAbstractFactory{
// Suit는 위에서 정의한 추상 클래스
public Suit CreateSuit();
}
  • Suit 팩토리 클래스
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class CombatFactory implements SuitAbstractFactory{

@Override
public Suit createSuit(){
return new CombatSuit();
}
}

// Space
public class SpaceFactory implements SuitAbstractFactory{

@Override
public Suit createSuit(){
return new SpaceSuit();
}
}

// Stealth
public class StealthFactory implements SuitAbstractFactory{

@Override
public Suit createSuit(){
return new StealthSuit();
}
}

// 생략
  • 팩토리 클래스를 파라미터로 받는 구현 클래스
1
2
3
4
5
public class SuitFactory{
public static Suit getSuit(SuitAbstractFactory suitAbstractFactory){
return suitAbstractFactory.createSuit();
}
}
  • Main에서 구현
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public static void main(String[] args){
Suit suit1 = SuitFactory.getSuit(new CombatFactory());
Suit suit2 = SuitFactory.getSuit(new SpaceFactory());
Suit suit3 = SuitFactory.getSuit(new StealthFactory());

System.out.println(suit1.getName());
System.out.println(suit2.getName());
System.out.println(suit3.getName());
}

/*
결과
CombatSuit
SpaceSuit
StealthSuit
*/

위 결과 코드와 같이 슈트별 팩토리 클래스를 파라미터로 넘겨 각 슈트 객체를 반환 받아 사용할 수 있다. 이렇게 되면 팩토리 클래스 교체만으로 조금 더 유연하게 기능의 수정, 확장에 대처할 수 있게 된다. 코드 상으로는 if-else 구문을 제거하여 조금 더 깔끔하게 코드를 구성할 수 있다.

나의 이해

CombatFactory는 SuitAbstractFactory 인터페이스를 구현하고 있다. 각 수트 Factory 클래스는 전부 SuitAbstractFactory 인터페이스를 구현하고 있는 셈이다. 결국 부모는 SuitAbstractFactory가 되고, 자식은 각 Suit Factory들이 된다.

SuitFactory.getSuit() 문장을 보면 getSuit() 함수의 파라미터로 SuitAbstractFactory 타입의 클래스를 넘길 수 있다. 하지만, 잘 생각해보면 SuitAbstractFactory를 구현한 자식 클래스도 넘길 수 있다. 코드처럼 new CombatFactory()를 넘기면 부모 클래스를 구현한 자식인 CombatFactory는 부모 타입으로 전달될 수 있다. 그리고 createSuit() 메소드를 호출하게 되는데 이는 파라미터로 전달받은 객체의 createSuit() 메소드를 호출하게 된다. 우리가 파라미터로 전달받은 객체는 CombatFactory이다. 그러므로 CombarFactory의 createSuit() 메소드를 호출하고 new CombatSuit()를 통해 객체를 생성하게 된다. 결국 CombatSuit 클래스의 객체를 얻을 수 있으며 getName() 함수를 통해 확인하면 CombatSuit가 출력되는 걸 확인할 수 있다.

참고 사이트