본문 바로가기
3학년 2학기 학사/소프트웨어 분석 및 설계

[소프트웨어 분석 및 설계] L15. Factory Method (팩토리 메소드)

by whiteTommy 2024. 12. 12.
반응형
더보기

목차

  • Factory Method Pattern
  • Enum Factory Method Pattern

 

예상 문제

  • XML document Parser 코드 주고, 어떤 디자인 패턴이 필요한지
    • 이 패턴의 장/단점

 

Factory Method Pattern

  • 팩토리 메소드 패턴이란?
    • 객체 생성을 서브 클래스에 위임할 수 있는 생성 패턴
    • 객체를 생성하기 위한 인터페이스를 정의하지만, 어떤 클래스를 인스턴스화할지는 서브 클래스가 결정함
  • 팩토리 메소드 패턴이 사용되는 상황
    • 객체의 생성을 코드의 나머지 부분으로 분리 하려고 할 때
    • 사용자가 자체 객체를 만들어 시스템을 확장하는 방법을 제공하고자 할 때
    • 객체 생성에 있어서 기존의 코드를 건들지 않고, 쉽게 확장할 수 있는 방법이 있을까? (OCP 원칙)
  • Example situation
    • 배를 만드는 공장 (ShipFactory) 에서 Client 의 요청에 맞추어 배를 주문해서 (orderShip) 배 (Ship) 의 객체를 받는 상황으로 비유
      public class Client{
          public static void main(String[] args){
          	Client client = new Client();
              
              Ship whiteship = ShipFactory.orderShip("whiteship");
              System.out.println(whiteship);
              
              Ship blackship = ShipFactory.orderShip("blackship");
              System.out.println(whiteship);
          }
      }
      
      public class ShipFactory{
          public static Ship orderShip(String name){
          	validate(name);
              prepareFor(name);
              
              Ship ship = new Ship();
              ship.setName(name);
              
              if(name.equalsIgnoreCase("whiteship"))
              	ship.setColor("white");
              else if(name.equlsIgnoreCase("blackship"))
              	ship.setColor("black");
                  
              return ship;
          }
          private static void validate(String name){
          	if(name==null || name.isBlank())
              	//throw an exception
          }
      }
      
      public class ship{
          private String name;
          private String color;
          
          // getters and setters
      }


      • 요구사항 변경으로 새로운 배(blueship) 이 추가되어야 한다면?
      • Ship 클래스에 새로운 멤버 변수가 추가된다면?
        => ShipFactory 와 Ship 사이의 강한 결합
        => 확장을 시도하게 되면 기존의 코드에 수정이 발생할 수 밖에 없는 구조 (OCP 위배)
  • 팩토리 메소드 패턴의 구조

    • 인터페이스를 통해 팩토리 객체와 제품 객체 간 결합을 느슨하게 함
      • 팩토리 클래스는 제품 객체를 도맡아 생성하고 이를 상속하는 서브 클래스의 메소드에서 여러 가지 제품 객체 생성을 각각 책임 지는 것
  • 팩토리 메소드 패턴으로 객체 생성시 시퀀스
    • ConcreteProductA 에 대한 구체적인 생성은 ConcreteCreatorA (팩토리) 객체가 전담
    • Client 는 ConcreateCreatorA 를 통해서 Product 객체를 받는다.
      • ConcreteProductA product = new ConcreateCreatorA().createProduct()
  • Example situation
    • ShipFactory 에 팩토리 메소드 패턴을 적용해서 코드를 수정
      public interface Ship{
          void setName(String name);
          void setColor(String color);
      }
      
      public class ConcreteShip implements Ship{
          private String name;
          private String color;
          
          @Override
          void setName(String name){
          	this.name = name;
          }
          
          @Override
          void setColor(String color){
          	this.color = color;
          }
      }
      
      public class WhiteShip extends ConcreteShip{
          public WhiteShip(){
          	setName("whiteship");
              setColor("white");
          }
      }
      //--------------------------------------------
      public interface ShipFactory{
          default Ship orderShip(String name){
          	validate(name);
              prepareFor(name);
              Ship ship = createShip();
              return ship;
          }
          
          Ship createShip(); // to be overrided
          
          private void validate(String name){
          	if(name == null || name.isblank())
              	//throw an exception
          }
          public static void prepareFor(String name){
          	//print
          }
      }
      
      public class WhiteShipFactory implements ShipFactory{
          @Override
          public Ship createShip(){
          	return new WhiteShip();
          }
      }
      //---------------------------------------------
      public class Client{
          public static void main(String[] args){
          	Ship whiteship = new WhiteShipFactory().orderShip("whiteship");
              System.out.println(whiteship);
          }
      }


    • 새로운 BlackShip을 추가하려고 할 때, 팩토리 메소드 패턴이 적용된 구조에서는 기존 코드의 수정이 없음!
      public class BlackShip extends ConcreteShip{
          public BlackShip(){
          	setName("blackship");
              setColor("black");
          }
      }
      //---------------------------------------------
      public class Client{
          public static void main(String[] args){     
              Ship blackship = new BlackShipFactory().orderShip("blackship");
              System.out.println(blackship);
          }
      }


    • Client 코드도 추가 수정이 최소화되도록 수정해 볼 수 있음
      • 인터페이스로 코드가 구성되었기 때문에 가능 (코드의 중복을 줄임)
        public class Client{
            private void print(ShipFactory shipFactory, String name){
            	System.out.println(shipFactory.orderShip(name));
            }
            
            public static void main(String[] args){
            	Client client = new Client();
                client.print(new WhiteShipFactory(), "whiteship");
                client.print(new BlackShipFactory(), "blackship");
            }
        }
  • XML document parser
    • 여러 가지 파싱 방법을 제공하기 위해 팩토리 메소드 패턴으로 구현됨
  • 다른 사용 사례
    • Java.util.Calendar, java.text.Numberformant, Spring BeanFactory, etc.
  • 장점
    • 객체의 생성을 캡슐화 하여 나머지 코드와 분리 시키기 때문에 코드의 유지보수를 용이하게 함
    • 객체의 생성이 인터페이스를 통해 이루어지므로 인터페이스 기반의 재사용이 용이함 (Client 코드의 변경 예시)
    • SRP 준수
      • 팩토리 메소드 패턴은 객체의 생성 및 초기화에 관한 책임만 가지고 있음
    • OCP 준수
      • 기존 코드의 변경 없이 새로운 코드를 유연하게 추가할 수 있음
      • 객체를 생성하기 위한 인터페이스를 정의하지만 어떤 클래스를 인스턴스화 할지는 서브클래스가 결정함
  • 단점
    • 코드 구조 복잡도 증가
      • 각 제품 구현체 마다 팩토리 객체를 모두 구현 해주어야 하기 때문에 클래스 수가 많아지며 한 눈에 이해하기 어려움
    • 추가적인 클래스 객체가 생성되기 때문에 객체 생성에 따른 오버헤드가 발생할 수 있고 성능 영향을 미칠 수 있음
  • 기타 사항
    • 팩토리 메소드 패턴의 여러 개선된 변형이 존재
      • Enum factory pattern
        • 서브 팩토리 클래스를 하나하나 구현하지 않고 enum Factory 하나로 구성하려고 하는 것
      • Dynamic factory pattern
        • 서브 클래스 수가 늘어 나는 것을, Reflection API 를 이용해 동적으로 처리하여 문제를 해결

 

 

Enum Factory Method Pattern

  • Factory Method Pattern 의 문제점
    • 제품의 객체의 수 마다 팩토리 서브 클래스를 모두 구현해주어야 함
    • 팩토리 클래스는 한 번만 인스턴스화 하고 재사용하면서 제품 객체를 생성하는 역할만 하면 되는데, 이전의 코드는 여러 팩토리 객체가 생기면서 낭비될 여지가 없음
    • Enum 으로 팩토리 메소드 패턴을 구성해 준다면, 일일이 서브 팩토리 클래스 구현 없이 하나의 enum Factory 에서 구성할 수 있음
  • 예시 - 도형에 대한 팩토리 메소드 패턴
    class Client{
        public static void main(String[] args){
        	Shape rectangle = new RectangleFactory().create("red");
            rectangle.draw();
            
            Shape circle = new CircleFactory().create("yellow");
            circle.draw();
        }
    }


    • Factory 객체가 여러번 생성됨
      (꼭 여러번 생성 되어야 할까? 한 번만 생성되어도 되지 않을까?)

 

  • Enum 을 활용한 해결 아이디어
    • 추가적인 팩토리 클래스를 만들지 않아야 하면서 동시에 단일 팩토리로 제품 객체를 생성할 수 있어야 함
      • Enum 을 이용하면 싱글톤 객체를 만들 수 있음
        enum Season{
            SUMMER("여름");
            
            private String season; //문자열을 저장할 필드
            
            private Season(String season){  //생성자 (싱글톤)
            	this.season = season;
            }
            
            public String getSeason(){  //Getter
            	return season;
        	}
        }
        
        public static void main(String[] args){
            Season s = Season.SUMMER;
            
            System.out.println(s.name()); // 열거 객체명 출력 : SUMMER
            System.out.println(s.getSeason()); //매핑된 열거 데이터 출력 : 여름
        }
      • Enum 을 이용하면 멀티톤 (multiton) 으로도 일반화 (2가지 방식)
        • Multiton 이란 여러 개의 관리되는 인스턴스를 가지는 클래스를 말함(1)
          enum Season{
              Spring("봄");
              SUMMER("여름");
              FALL("가을");
              WINTER("겨울");
              
              private String season; //문자열을 저장할 필드
              
              private Season(String season){  //생성자 (싱글톤)
              	this.season = season;
              }
              
              public String getSeason(){  //Getter
              	return season;
              }
          }

          abstract class Season{
              public static final Seson SPRING = new Season("봄");
              public static final Seson SUMMER = new Season("여름");
              public static final Seson FALL = new Season("가을");
              public static final Seson Winter = new Season("겨울");
              
              private String season;
              
              private Season(String season){
              	this.season = season;
              }
              
              public String getSeason(){
              	return season;
              }
          }
        • Multiton 이란 여러 개의 관리되는 인스턴스를 가지는 클래스를 말함(2)
          enum Operation{
              PLUS("+"){
              	@Override
                  public double apply(double x, double y){
                  	return x+y;
                  }
              };
              
              private final String symbol;
              Operation(String symbol){
              	this.symbol = symbol;
              }
              public abstract double apply(double x, double y);
          }

          abstract class Operation{
              public static final Operation PLUS = new Operation("+"){
              	@Override 
                  public double apply(double x, double y){
                  	return x+y;
                  }
              };
              
              private final String symbol;
              
              Operation(String symbol){
              	this.symbol = symbol;
              }
              
              public abstract double apply(double x, double y);
          }
    • 예시 - 도형에 대한 팩토리 메소드 패턴
      • Enum 을 활용한 해결 아이디어
        • 팩토리 서브 클래스 추가 대신에, Enum 변수 추가로 대체 가능
        • 팩토리 객체를 외부에서 만들 필요가 없음

          enum EnumShapeFactory{
              RECTANGLE{
              	@Override
                  public Shape createShape(){
                  	return new Rectangle();
                  }
              },
              CIRCLE{
              	@Override
                  public Shape createShape(){
                  	return new Circle();
                  }
              };
              
              public Shape create(String color){
              	Shape shape = createShape();
                  shape.setColor(color);
                  return shape;
              }
              
              abstract protected Shape createShape();
          }
          
          class Client{
              public static void main(String[] args){
              	Shape rectangle = EnumShapeFactory.RECTANGLE.create("red");
                  rectangle.draw();
                  
                  Shape circle = EnumShapeFactory.CIRCLE.create("yellow");
                  circle.draw();
              }
          }
    • Discussion on Enum Factory Method Pattern
      • JAVA Enum 의 기능을 활용하여 기존의 Factory Method Pattern 이 가지는 문제 해결
        • 새로운 제품(product) 를 추가할 때는 Product class 를 추가해주고 Enum Factory 에 Factory 객체를 추가 해주면 됨
        • 하나의 Factory 객체는 싱글톤으로 공유되고, 외부에서 객체를 만들어 줄 필요가 없음 (객체 생성에 따른 부담 완화)
        • 새로운 Factory 추가 시 기존의 Enum 코드가 수정되는 단점이 존재
          • 결국 코드 구조의 복잡성과 OCP 를 얼마나 엄밀하게 지킬 것인가에 대한 trade-off 가 존재
        • 만일 Factory 가 복잡한 상속 계층으로 구성된다면, Enum 외 클래스 상속이 안되기 때문에 상속 구조 표현에 한계가 있음

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

반응형