ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 디자인 패턴 (4) - 팩토리 패턴
    언어/디자인패턴 2023. 9. 11. 12:28

     

    1. 팩토리 메소드 패턴이란?

     

    - 객체 생성 고장 클래스로 캡슐화 처리하여 대신 생성하는 생성 디자인 패턴이다.

    - 즉 클라이언트가 직접 new 연산자를 통해 객체를 생성하는 것이 아닌, 제품 객체 생성을 도맡은 공장 클래스 생성

      -> 이를 상속하는 서브 클래스에서 제품 생성 책임 

    - 객체 생성에 필요한 템플릿 미리 구성하고, 객체 생성에 관한 전처리나 후처리를 생성 과정을 다양하게 처리 

     

    1.2 팩토리 메서드 패턴 구조 

    출처: https://inpa.tistory.com/entry/GOF-%F0%9F%92%A0-%ED%8C%A9%ED%86%A0%EB%A6%AC-%EB%A9%94%EC%84%9C%EB%93%9CFactory-Method-%ED%8C%A8%ED%84%B4-%EC%A0%9C%EB%8C%80%EB%A1%9C-%EB%B0%B0%EC%9B%8C%EB%B3%B4%EC%9E%90

     

    출처: https://inpa.tistory.com/entry/GOF-%F0%9F%92%A0-%ED%8C%A9%ED%86%A0%EB%A6%AC-%EB%A9%94%EC%84%9C%EB%93%9CFactory-Method-%ED%8C%A8%ED%84%B4-%EC%A0%9C%EB%8C%80%EB%A1%9C-%EB%B0%B0%EC%9B%8C%EB%B3%B4%EC%9E%90

     

     

     

    1. Creator : 최상위 공장 클래스, 팩토리 메서드 추상화 -> 서브클래스가 구현하도록 한다.

                 -> 객체 생성 처리 메서드: 객체 생성 관한 전처리, 후처리 템플릿 메소드

                 -> 팩토리 메서드 : 서브 공장 클래스에서 재정의할 객체 생성 추상 메서드 

                (각 서브 팩토리의 공통코드)

    2. ConcreteCreator: 각 서브 공장 클래스는 알맏은 제품을 반환하도록 재정의 (제품 당 서브 팩토리 위치)

                (피자 지점처럼 하나의 팩토리는 여러 제품을 생성 가능)

    3. Product : 제품 구현 추상화

    4. ConcreateProduct:제품의 구현체 

     

    * 객체 만드는 공장을 만드는 패턴이다. 

    * 병렬 클래스 계층구조 만들 수 있음 생산자- 서브생산자(제품1,제품2,제품3), 서브생산자2(제품1,제품2,제품3)

       > 최상위 공장에 대입된 서브클래스에 따라 생성될 객체 인스턴스 결정됨 

     

    - 제품 클래스 

    interface Product{
    	void setting();
    }
    
    //제품 구현체 
    class ConcreteProductA implements Product{
    	public void setting(){
        }
    }
    
    //제품 구현체 
    class ConcreteProductB implements Product{
    	public void setting(){
        }
    }

    - 공장 클래스 

     

    abstract class AbFactory{
    
    
    	//전처리 후처리 메서드
      final Product createOperation{
      	Product pro = createProduct();// 서브 팩토리 클래스에서 구체화 된 제품리턴
        pro.setting(); //객체 생성시 각 제품 별 추가할 셋팅
        return pro; // 객체 생성, 추가 설정 완료 후 리턴
      }
    	// 팩토리 메서드: 구체적 객체 생성은 서브 클래스에 위임 
        abstract protected Product createProduct();
    }
    
    class ConcreteFactoryA extends AbFactory{
    
    	public Product createProduct(){
        	return new ConcreateProductA();
        }
    }
    
    class ConcreteFactoryB extends AbFactory{
    
    	public Product createProduct(){
        	return new ConcreateProductB();
        }
    }

    - 클라이언트 

     

    class Cli{
    
    	public static void main(String[] args)
        {
        
        	AbFactory[] fac = {new ConcreteFactoryA(), new ConcreteFactoryB() };
            
            Product proA = fac[0].createOperation();
            Product proB = fac[1].createOperation();
        
        }
        
        }

    1.3 패턴 정보

     

    - 사용시기 : > 클래스 생성 결합도 낮추기 

                        > 객체 유형과 종속성을 캡슐화를 통해 정보 은닉 

                        > 라이브러리와 프레임워크에서 구성 요소 확장하는 방법

                        > 기존 객체 재구성 대신, 재사용하여 리소스 절약

                              -> 객체 생성의 생성 코드를 별도의 클래스/메서드로 분리하여 객체 생성 변화에 대해 대비! 

     

    - 장점: 생성자: > 구현체 강한 결합 회피

                            > 객체 생성시 공통처리 가능 

                            > SRP,OCP준수 

     

    - 단점 : > 서브클래수 수 증가와 복잡성 증가 

     

    *객체 생성이라는 변화를 인터페이스화

    * 팩토리 객체와 제품 객체 간 느슨한 결합 구조이다.

     

     

    2. 최첨단 피자 코드 만들기 

     

    2.1 패턴 없이 만들기 

     

    Pizza orderPizze(){
    	Pizza pi = new Pizza(); // 특정 구현클래스에 의존
        
        //피자 종류 바탕으로 구현클래스 대입해서 대입
        if(type.equals("cheese"){
        	pi = new CheesePizza(); 
        }else if(type.equals("greek")){
        	pi = new GreekPizza();
        }else if(type.equals("peppernoi"))
        	pi = new PepperoniPizza();
        
        pi.prepare();
        pi.bake();
        pi.cut();
        pi.box();
        return pi;
    
    }

    - > 문제되는 부분은 인스턴스 생성하는 구상 클래스 선택 부분 -> 신메뉴가 추가될 수록 if문이 증가함 

    -> if 중첩문을 담당하는 팩토리를 만들면, 오더시에 어떤 피자일지 선택하는 if문을 뺄 수 있다.

     

    //피자 생성 전담 클래스 
    public class SimplePizzaFactory{
    
    	public Pizza createPizza(String type){
        	Pizza pi = null;
            
            //type에 따라 if문을 돌면서 알맞은 pizza 구현체 생성 
            
            return pi
        }
    
    
    }

     -> 피자 생성 캡슐화의 장점

     캡슐화 x시 : 클라이언트는 주문하고, 피자 정보를 얻고 하는 등의 모든 메서드에서 if문을 통해 피자를 확인해야함

     캡슐화 o시 : 팩토리를 통해서 피자를 한번만 생성하면, 각 order와 피자 정보얻기에서 타입확인 없이 객체사용가능

    -> 특이사항 (변화) 발생시 팩토리 부분만 고치면 됨 

     

    * 팩토리 메서드를 정적으로 만들 수도 있지만, 그러면 서브클래스를 통해 객체 생성 메소드 행동 변경이 어려워짐

      -> 정적 메소드는 오버라이딩이 안됨

     

    - 간단한 팩토리 적용해서 클라이언트 고치기 

    public class PizzaStore{
    
    	SimplePizzaFactory fac;
        public PizzaStore(SImplePizzaFactory fac){
        	this.fac = fac;
        }
        
        public Pizza orderPizza(String type){
        	Pizza pi;
            pi = factory.createPizza(type);
            
            // 피자 생성시 해야할 부분 위치 pi.box()등등
        }
    }

     * 팩토리를 통해 생성되는 각 피자클래스는 -> 피자인터페이스구현+구현클래스여야함 

     

    2.2 요구사항 변경 -> 여러 팩토리 추가됨

     

    NYPizzaFactory nyFac = new NYPizzaFactory();
    PizzaStore nyStore = new PizzaStore(vyFac);
    nyStore.orderPizza("Veggie");
    
    ChicagoPizzaFactory cif = new ChicagoPizzaFactory();
    PizzaStore cStore = new PizzaStore(cif);
    nyStore.orderPizza("Veggie); //뉴욕과 같은 이름이지만 스타일이 다름

     

    2.3 피자가게 프레임워크 만들기 (팩토리 메서드 패턴 적용)

     

    public abstrat class PizzaStore{
    
    	public final Pizza orderPizza(String type){ //서브클래스가 고치지 못하도록 이대로 해야함
        	Pizza pi;
            pi = createPizza(type);
            
            pi.prepare(); 
        }
    	
        abstract Pizza createPizza(String type); //지점별 다른 피자를 생성하도록 함 서브팩토리 생성시 
                                                 //오버라이딩 해야함
    }
    
    public class NYPizza extends PizzaStore{
    	Pizza createPizza(String item){
        
        	//if문을 통해 알맞은 피자 생성
        }
    
    }

    - 상위 피자스토어를 구현한 클래스를 PizzaStore 변수에 넣어서 사용

    - 상위 클래스는 서브 클래스에서 어떻게 createPizza()하는 지 모름 그냥 사용만함 

    - PizzaStore와 Pizza를 완전 분리 -> Pizza를 결정하는 것은 서브 팩토리 

     

    2.4 Pizza클래스 만들기 

    public abstract class Pizza{
    
    	String name;
        String dough;
        String sauce;
        List<String> toppings = new ArrayList<String>();
        
        void prepare(){
        	
        }
        void bake(){
        
        }
        //기본 준비과정 
    
    }
    
    public class NYStyleCheesePizza extends Pizza{
    
    	public NYStyleCheesePizza(){
        	name ="";
            dough ="";
            sauce="";
            toppings.add("오레가노치즈");
        }
    
    }
    
    public class ChicagoStyleCheesePizza extends Pizza{
    public NYStyleCheesePizza(){
        	name ="";
            dough ="";
            sauce="";
            toppings.add("모짜렐라");
        }
        void cut(){
        	// 특성에 맞게 Pizza에 메서드 오버라이드 
        }
    }

    - Pizza를 추상클래스로 만들고, 각 피자는 이를 상속, Pizza추상클래스는 몇가지 공통작업 제공 

     

    2.5 클라이언트

     

    public class PizzaDrive{
    
    
    	public static void main(String[] args){
        
        	PizzaStore  nyStore= new NYPizzaStore();
            PizzaStore chicagoStore = new ChicagPizzaStore();
            
            Pizza pi = nyStore.orderPizaa("cheese");
            Pizza pi2 = chicagoStore.orderPizza("cheese");
        }
    }

     

     

     

    *생산 서브클래스가 하나일 때여도 의미 있음

      -> 새로운 제품을 생성할 때 클라이언트가 사용하는 CreatorConcreteProduct가 느슨하게 결함됨 ->

          Creator를 건드릴 필요 없이 추가 가능 

    * 팩토리 메서드 매개변수에 타입 안정성 문제 -> String type같은거 말고 enum사용하자 

    * 간단한 팩토리 메서드를 위와 같이 만들면 언제든 재사용이 가능하다. 

     

    3. 객체 의존성

     

    - 객체 인스턴스를 직접 만들면 구현 클래스에 의존해야한다. 

     

    3.1 의존성 뒤집기 원칙 (DIP)

     

    *디자인원칙: 추상화된 것에 의존하게 만들고, 구현 클래스에 의존하지 않는다. 

    -> 인터페이스에 대고 프로그래밍한다보다 추상화를 더 강조한다. 

    -> PizzaStore는 고수준 구성요소, 피자는 저수준 구성요소 

     

    * 고수준 구성요소는 저수준 구성요소에 의해 정의되는 행동 모음 

       -> PizzaStore의 행위는 Pizza에 의해 정의됨

       -> 고수준(잘 추상화된)것에 의존하자  

       -> 일반적으로 추상클래스 정의하고 이를 구현한 클래스를 만들고 구현 클래스를 사용하므로

           저수준이 고수준에 의존하는데, 이를 뒤집은 것 

     

    * 생각 뒤집기:

    Pizza 스토어가 아닌 Pizza부터 생각 -> Pizza인터페이스로 여러 피자 구현 (추상화)

    PizzaStore에 팩토리를 적용하여 생성하면 -> 다양한 Pizza구현 클래스가 -> Pizza에 의존하게 됨

     

    3.2 의존성 뒤집기 원칙 지키는 방법

     

     1. 변수에 구현 클래스 래퍼런스 저장하지말자 (new 가아닌 팩토리 사용해서 구현 클래스 래퍼런스 저장)

     2. 구현 클래스에서 유도된 클래스 만들지 말자 (인터페이스나 추상화된 클래스로 구현 클래스 만들자)

     3. 베이스 클래스에 이미 구현된 메소드는 오버라이드 하지 말자

       (베이스 클래스는 모든 서브클래스가 공유할 수 있는 메서드만 작성)

     

     

Designed by Tistory.