본문 바로가기
Develop/Fundmental

팩토리 패턴(Factory Pattern)

by 3-stack 2022. 4. 26.

아래는 우리 이수만 형이 BTS와 BlackPink를 만드는 코드입니다.

 

수만이 형이 Idol 클래스를 이용하고 있군요.

public class LeeSooMan {
    public static void main(String[] args) throws Exception {
        Idol bts = new Idol("RM", "M");
        System.out.println(bts);
        Idol blackpink = new Idol("제니", "W");
        System.out.println(blackpink);
    }
}

구현되어 있는 Idol 클래스 입니다.

public class Idol {
  private String name;
  private String groupName;

  public Idol(String name, String sex) {
    createIdol(name, sex);
  }

  public String getName() {
    return name;
  }

  public void setName(String name) {
    this.name = name;
  }

  public String getGroupName() {
    return groupName;
  }

  public void setGroupName(String groupName) {
    this.groupName = groupName;
  }

  @Override
  public String toString() {
    return "안녕하십니까!!! " + groupName + " " + name + "입니다.";
  }

  public void createIdol(String name, String sex) {
    audition(name, sex);
    setName(name);
    decideGroupName(this, sex);
    passAudition(name);
  }

  private void decideGroupName(Idol idol, String sex) {
    if (sex == "W") {
      idol.setGroupName("블랙핑크");
    } else if (sex == "M") {
      idol.setGroupName("방탄소년단");
    } else {
      idol.setGroupName("연습생");
    }
  }

  private void audition(String name, String sex) {
    if (name == null || name.isBlank()) {
      throw new IllegalArgumentException("이름표 어디갔니? 집에 가라.");
    }
    if (sex == null || sex.isBlank()) {
      throw new IllegalArgumentException("남쟈야? 여자야? 안 되겠다..집에 가라.");
    }
  }

  private void passAudition(String name) {
    System.out.println(name + "은 오디션에 합격했다.");
  }
}

 

우리 수만 형이 새롭게 에스파라는 아이돌을 만들려고 하면 어떻게 해야 할까요? 기존 Idol 클래스를 수정해야 합니다.

그리고 새로운 아이돌 에스파는 "다른 세계관"이라는 기존에 없었던 기능도 추가될 예정인데요. 추가되는 기능이 있으니 코드의 복잡성이 더욱 증가할 것 같습니다.

시간이 갈수록 복잡성이 증가할 테고, 유지보수에 취약한 코드로 진화할게 불 보듯 뻔하네요.

 

이런 상황에서 팩토리 패턴을 적용하면 확장에는 열려있고 변경에는 닫혀있는 좋은 냄새가 나는 코드로 바꿀 수 있습니다.

 

 

1. 우선 팩토리 패턴을 적용하기 위해서 Production와 Creator 부분을 나눠볼게요.

여기서 Creator는 OOFactory와 같은 생성 클래스, Production은 Idol과 같은 Creator로 만들어내는 클래스를 말해요.

 

Idol 특징만 전담하는 Idol.

public class Idol {
  private String name;
  private String groupName;

  public Idol(String name) {
    setName(name);
  }

  public String getName() {
    return name;
  }

  public void setName(String name) {
    this.name = name;
  }

  public String getGroupName() {
    return groupName;
  }

  public void setGroupName(String groupName) {
    this.groupName = groupName;
  }

  @Override
  public String toString() {
    return "안녕하십니까!!! " + groupName + " " + name + "입니다.";
  }
}

 

아이돌 생성에 집중하는 IdolFactory.

public class IdolFactory {
  public Idol trainIdol(String name, String sex) {
    audition(name, sex);
    Idol idol = createIdol(name, sex);
    decideGroupName(idol, sex);
    passAudition(name);
    return idol;
  }

  private Idol createIdol(String name, String sex) {
    Idol idol = new Idol(name);
    return idol;
  }

  private void decideGroupName(Idol idol, String sex) {
    if (sex == "W") {
      idol.setGroupName("블랙핑크");
    } else if (sex == "M") {
      idol.setGroupName("방탄소년단");
    } else {
      idol.setGroupName("연습생");
    }
  }

  private void audition(String name, String sex) {
    if (name == null || name.isBlank()) {
      throw new IllegalArgumentException("이름표 어디갔니? 집에 가라.");
    }
    if (sex == null || sex.isBlank()) {
      throw new IllegalArgumentException("남쟈야? 여자야? 안 되겠다..집에 가라.");
    }
  }

  private void passAudition(String name) {
    System.out.println(name + "은 오디션에 합격했다.");
  }
}

 

우리 수만이 형은 이제 IdolFactory를 사용해서 Idol을 생산합니다.

아이돌의 컨셉이나 멤버수를 변경할 때는 Idol를 수정하고

아이돌 육성 프로세스, 트레이닝 방침을 변경할 때는 IdolFactory만 수정하면 되겠네요.

public class LeeSooMan {
    public static void main(String[] args) throws Exception {
        Idol bts = new IdolFactory().trainIdol("RM", "M");
        System.out.println(bts);
        Idol blackPink = new IdolFactory().trainIdol("제니", "W");
        System.out.println(blackPink);
    }
}

 

2. 본격적으로 Factory Pattern 적용

수만이 형이 원하는 다양한 타입의 Idol을 육성할 수 있게 Production, Creator 클래스를 SubClass로 나눌게요.

Factory Pattern의 핵심은 Creator, Production 모두 계층구조로 만들어서 구체적인 클래스에서 구체적인 상품을 만들어 내는 것이에요.

 

1) 모든 Idol이 갖는 공통 특징을 관리하는 Idol 추상클래스.

Idol 클래스는 추상클래스로 변경하고 직접 인스턴스를 만들지 못하도록 생성자도 삭제했어요.

public abstract class Idol {
  private String name;
  private String groupName;

  public String getName() {
    return name;
  }

  public void setName(String name) {
    this.name = name;
  }

  public String getGroupName() {
    return groupName;
  }

  public void setGroupName(String groupName) {
    this.groupName = groupName;
  }

  @Override
  public String toString() {
    return "안녕하십니까!!! " + groupName + " " + name + "입니다.";
  }
}

 

2) Idol 클래스를 상속받는 BTS 클래스.

아이돌에 필요한 공통 DNA는 상속으로 수혈받고, BTS 그들의 Identity를 보여주는 특별한 속성은 여기서 부여합니다.

public class BTS extends Idol {
  public BTS(String name) {
    setName(name);
    setGroupName("BTS");
  }
}

 

3) 모든 아이돌 생성에 공통적으로 필요한 기능을 Interface IdolFactory로 만들어요.

아이돌을 생성하는 creatIdol은 각각의 아이돌(서브클래스)에서 맞춤형으로 구현하도록 처리할 거에요.

그룹 이름을 정해주던 decideGroupName도 개별 서브클래스에서 처리하면 되므로 삭제됐어요.

public interface IdolFactory {
  default Idol trainIdol(String name, String sex) {
    audition(name, sex);
    Idol idol = createIdol(name, sex);
    passAudition(name);
    return idol;
  }

  Idol createIdol(String name, String sex);

  private void audition(String name, String sex) {
    if (name == null || name.isBlank()) {
      throw new IllegalArgumentException("이름표 어디갔니? 집에 가라.");
    }
    if (sex == null || sex.isBlank()) {
      throw new IllegalArgumentException("남쟈야? 여자야? 안 되겠다..집에 가라.");
    }
  }

  private void passAudition(String name) {
    System.out.println(name + "은 오디션에 합격했다.");
  }
}
* default 메소드?
Interface에서 기능을 구현할 수 있게 된다. interface에서만 사용할 수 있으며 상속받는 서브클래스/인터페이스의 중복 코드를 제거할 수 있다.
* private 메소드?
interface에서 기능을 구현할 수 있게 된다. 서브클래스에서 구현하지 않는 공통 코드를 작성함으로써 중간 계층 구조를 줄이고, 코드 중복을 줄인다. private 키워드를 사용하지 못하는 Java8 미만에서는 Parent와 Child사이에 클래스를 하나 더 만들고 Child가 그 클래스를 상속받게 처리해야 했다.
예제에서 예를 들면 IdolFactory를 implement 하는 BaseIdolFactory를 만들어서 공통 기능을 구현하고, BTSFactory에서 BaseIdolFactory를 상속받는 구조가 된다.

 

4) IdolFactory를 구현하는 BTSFactory

BTS만의 특별한 육성 방법은 여기서 관리합니다.

public class BTSFactory implements IdolFactory {
  @Override
  public BTS createIdol(String name, String sex) {
    BTS bts = new BTS(name);
    return bts;
  }
}

 

5) 수만이 형은 이제 BTS를 만들었습니다... 블핑도 만들고 싶은 우리 수만이 형..

public class LeeSooMan {
    public static void main(String[] args) throws Exception {
        Idol bts = new BTSFactory().trainIdol("RM", "M");
        System.out.println(bts);
    }
}

 

 

3. 이제 블랙핑크를 만들게요. Factory  Pattern이 잘 적용되었나 볼 게요.

기존 코드는 변경하지 않고 확장은 자유롭게 되면 Factory Pattern이 잘 적용됐다고 볼 수 있어요.

 

1) 블랙핑크 Product 추가

public class BlackPink extends Idol {
  public BlackPink(String name) {
    setName(name);
    setGroupName("블랙핑크");
  }
}

2) 블랙핑크 Creator 추가

public class BlackPinkFactory implements IdolFactory {
  @Override
  public BlackPink createIdol(String name, String sex) {
    BlackPink blackPink = new BlackPink(name);
    return blackPink;
  }
}

3) 수만 형이 아주 eazy~하게 블랙핑크도 만들었네요.

public class LeeSooMan {
    public static void main(String[] args) throws Exception {
        Idol bts = new BTSFactory().trainIdol("RM", "M");
        System.out.println(bts);
        Idol blackPink = new BlackPinkFactory().trainIdol("제니", "W");
        System.out.println(blackPink);
    }
}

 

간단한 예제를 통해 팩토리 패턴을 적용해봤습니다.

 

# 팩토리 메소드 패턴을 적용했을 때의 장점은?

* 클라이언트 코드로부터 서브 클래스의 인스턴스화를 제거하여 서로 간의 종속성을 낮추고, 결합도를 느슨하게 하며 확장을 쉽게 한다.

객체 생성 코드를 서브클래스로 분리해뒀기 때문에 새로운 기능을 추가할 때 추가되는 부부만 건들면 되기 때문에 확장에 열리게 되고, 기존 코드는 만질 필요가 없으므로 수정에는 닫히게 된다.

* 클라이언트와 구현 객체들 사이에 추상화를 제공한다.

# 팩토리 메소드 패턴의 단점은?

* 새로 생성할 객체가 늘어날 때마다, Factory 클래스에 추가해야 되기 때문에 클래스가 많아진다. 제품 클래스가 증가할 때마다 새로운  서브클래스를 생성해야 한다.

* 클라이언트가 creator 클래스를 반드시 상속해서 Prodouct를 생성해야 한다.

 

핵심은 Creator, Product 양 쪽 모두 계층구조로 만들어서(다형성이 핵심) 구체적인 클래스 안에서 구체적인 제품을 만들어 내는 것이다.

구체적으로 어떤 것을 만들지는 서브 클래스가 정한다.

 

 

 

댓글