[디자인패턴] 팩토리 메소드

Foo·2022년 3월 24일
1

Design Pattern

목록 보기
1/3
post-thumbnail

Factory Method

배경

팩토리 메소드는 생성적 디자인 패턴 중 하나로서, 부모 클래스에서 객체를 생성하는 인터페이스를 제공한다. 자식 클래스들은 객체를 생성할 때 형을 변환할 수 있다.

💀 문제 정의

물류 관리 애플리케이션을 만든다고 생각해보자. 앱의 첫 버전은 오직 트럭 운송을 다루기 때문에, 앱의 코드는 Truck 클래스 속에 있다.

얼마 후, 이 앱이 유명해져서 해상 운송 회사로부터 해상 운송도 지원해달라는 요청이 쏟아지고 있다.

이미 Truck 클래스와 많이 엮여있는 코드에 Ships를 추가하여 변경하는 것은 쉽지 않을 것이다.

확장성이 떨어지는 코드에 문제가 있는 것이다.

😊 해결방안

이 때, 팩토리 메소드 디자인 패턴은 new를 통해 객체를 생성하는 부분을 특별한 팩토리 메소드를 호출하는 것으로 대체할 것을 제안한다. 즉, 팩토리 메소드 안에서 new를 통해 객체가 생성되는 것이다. 팩토리 메소드를 통해 생성되는 객체들은 product로 불린다.


효과 : 팩토리 메소드(createTransport)를 통해 자식 클래스에서 만들어지는 객체의 타입을 변경할 수 있다!

Logistics l=new RoadLogistics(); or new SeaLogistics(); 
Transtport t=l.createTransport();
t.deliver()
  • 조건에 따라 해상 운송, 트럭 운송을 결정하고, 하나의 팩토리 메소드를 통해 상황에 맞는 객체를 생성한다.
  • 생성 부분만 분리하여 코드 중복 없이 기존 deliver 메소드 호출 부분을 그대로 사용할 수 있기 때문에 메인 로직은 흔들리지 않음.

  • 운송수단마다 달라지는 deliver 부분은 Transport 인터페이스에서 deliver 메소드를 정의하여 자식클래스들이 구현하게 함.
  • 팩토리 메소드를 통해 만들어진 product들은 모두 추상체인 Transport로 취급됨. 따라서, 구상체가 많이 늘어나도 코드 상으로는 달라지는 것이 없음.

🧱 구조

  1. 팩토리 메소드를 통해 만들어지는 Product 부분. 팩토리메소드를 통해 생성된 모든 객체들(Concrete Product들)에게 적용되는 특징을 고려하여 Product 인터페이스를 정의함.

  2. Concrete Product들은 Product의 인터페이스를 각자에 맞게 구현함.

  3. Creator 클래스에서는 Product object를 생성하는 팩토리 메소드를 선언한다. 메소드의 반환 타입이 product와 매치되는 것이 중요하다.

    • 팩토리 메소드를 추상화하여 Creator의 자식클래스들이 각자의 버전으로 메소드를 구현하도록 강제하는 것도 가능하다. 아니면 base 팩토리 메소드가 디폴트 버전을 리턴하는 것도 하나의 방법이다.

    • Creator 이름을 보고 오해하지 말아야할 것은, Product를 생성하는 것이 Creator의 주기능이 아니라는 것이다. 통상적으로, Creator 클래스는 이미 기존 Product와 관련된 핵심 비즈니스 로직을 가지고 있다. 팩토리 메소드는 이 로직을 Concrete Product로부터 떼어내는 것을 도와준다.

  4. Concrete Creators는 Creator 클래스의 base 팩토리 메소드를 상속받아 다른 타입의 product를 반환한다.

    • 꼭, 팩토리 메소드들이 새로운 버전의 인스턴스를 생성해낼 필요는 없다. 캐시나, object pool 등 기존에 존재하는 객체를 반환할 수도 있다.

🙋 언제 사용해요?

  1. 매번 비즈니스 로직을 다시 짜는 것 대신 기존에 존재하는 object들을 재사용함으로써 시스템 리소스들을 아끼고 싶을 때

    • 기존 객체를 재사용하고 싶을 때 거쳐야 하는 과정
      1. 생성된 모든 객체들을 추적하고 있는 저장공간 생성
      2. 누가 객체를 요청했을 때, 프로그램은 pool에서 free object를 찾음.
      3. return
      4. 없으면 new를 통해 생성 or pool에 등록

    • 해당 로직은 클래스의 생성자 부분에 넣기 적합하나, 생성자들은 무조건 new objects들을 return 해야함. 기존 존재하는 객체를 반환할 수 없음.

    • 따라서, 팩토리 메소드를 통해 생성/재사용을 둘다 할 수 있음.

  2. 코드와 연관이 깊은 객체의 정확한 타입과 연관관계, 의존관계를 사전에 모르는 경우

    • 팩토리 메소드는 product를 사용하는 로직과 product를 생성하는 로직을 분리한다. 따라서 기존의 코드와 상관없이 product를 확장하는데 유리하다.
  3. 라이브러리 또는 프레임워크의 사용자에게 내부 구성요소를 확장하는 방법을 제공하고 싶을 때

🔪 장단점

1. 장점

  • 생성부분과 concrete product간의 강한 결합을 피할 수 있다.
  • Single Responsibility Principle(SRP)를 잘 지킨다. 생성부분을 하나의 곳으로 옮김으로써 한 부분의 수정이 다른 부분의 수정에 영향을 미치는 일을 없도록 함.
  • Open/Closed Principle(OCP). 기존에 존재하는 코드를 갈아엎어야 하는 일 없이 새로운 타입의 product를 적용할 수 있다. 확장에 강하다.

2. 단점

  • 이 패턴을 구현하기 위해 많은 자식 클래스들이 추가되어서 코드가 더 복잡해질 수 있다.

📝 예시

Shape.java

public interface Shape{
	void draw();
}

Circle.java

public class Circle implements Shape{
	@Override
    public void draw(){
    	System.out.println("Inside Circle::draw() method.");
    }
}

Rectangle.java

public class Circle implements Shape{
	@Override
    public void draw(){
    	System.out.println("Inside Rectangle::draw() method.");
    }
}

Square.java

public class Circle implements Shape{
	@Override
    public void draw(){
    	System.out.println("Inside Square::draw() method.");
    }
}

ShapeFactory.java

public class ShapeFactory{
	public Shape getShape(String shapeType){
    	if(shapeType==null){
        	return null;
        }
        if(shapeType.equalsIgnoreCase("CIRCLE")){
        	return new Circle();
        } else if(shapeType.equalsIgnoreCase("RECTANGLE")){
        	return new Rectangle();
        } else if(shapeType.equalsIgnoreCase("SQUARE")){
        	return new Square();
        }
        return null;
    }
}

FactoryPatternDemo.java

public class FactoryPatternDemo{
	public static void main(String[] args){
    	ShapeFactory shapeFactory=new ShapeFactory();
        
        Shape shape1=shapeFactory.getShape("CIRCLE");
        shape1.draw();
        
        Shape shape2=shapeFactory.getShape("RECTANGLE");
        shape1.draw();
        
        Shape shape3=shapeFactory.getShape("SQUAREa");
        shape1.draw();
        
    }
}

참고

Refactoring Guru
예제 코드

profile
천천히, 꾸준히, 겸손히

0개의 댓글

관련 채용 정보