본문 바로가기

책리뷰/헤드퍼스트 디자인패턴

헤드퍼스트 디자인패턴 - 팩토리 패턴

728x90
반응형

목차


    NEW 의 문제


    인터페이스에 맞춰서 코딩하면 어떤 클래스든 인터페이스만 구현하면 사용할 수 있다. 

    다형성 덕분에 가능하다.

    반대로 구상 클래스를 많이 사용하면 새로운 구상 클래스가 추가될 때마다 코드를 고쳐야 한다.

    OCP - 변경에 닫혀 있는 코드.

     

     

    최첨단 피자 코드 만들기


    Pizza orderPizza(String type) {
    
        Pizza pizza = new Pizza();
    
        pizza.prepare();
        pizza.bake();
        pizza.cut();
        pizza.box();
    
        return pizza;
    
    }

    피자 종류는 여러가지가 될 수 있다.

    Pizza orderPizza(String type) {
        Pizza pizza;
    
        if(type.equals("cheese")) {
            pizza = new CheesePizza();
        } else if(type.equals("pepperoni")) {
            pizza = new PepperoniPizza();
        } else if(type.equals("calm")) {
            pizza = new CalmPizza();
        } else if(type.equals("veggie")) {
            pizza = new VeggiePizza();
        }
        
        pizza.prepare();
        pizza.bake();
        pizza.cut();
        pizza.box();
    
        return pizza;
    }

    피자를 준비하고 소스를 넣고 추가하고 굽고 자르는 건 모든 피자가 동일하다.

    여기서 피자의 종류가 추가되고, 삭제될 경우 피자를 만드는 구상클래스를 선택하는 부분이 문제가 된다.

     

     

     

    객체 생성 캡슐화하기 - 객체 생성 팩토리 만들기 


    팩토리 : 객체 생성을 처리하는 클래스를 팩토리라고 부른다.

     

    public class SimplePizzaFactory {
    
        public Pizza createPizza(String type) {
            Pizza pizza = null;
    
            if(type.equals("cheese")) {
                pizza = new CheesePizza();
            } else if(type.equals("pepperoni")) {
                pizza = new PepperoniPizza();
            } else if(type.equals("calm")) {
                pizza = new CalmPizza();
            } else if(type.equals("veggie")) {
                pizza = new VeggiePizza();
            }
    
            return pizza;
        }
    }

    객체 생성만 전담하는 클래스.

     

     👆 캡슐화의 장점 

    객체 생성을 팩토리 클래스로 캡슐화하면 구현 변경시 여기저기 고칠 필요가 없고 팩토리 클래스 하나만 고치면 된다.

     

     

    클라이언트 코드 수정하기


    public class PizzaStore {
        
        SimplePizzaFactory factory;
    
        public PizzaStore(SimplePizzaFactory factory) {
            this.factory = factory;
        }
    
        public Pizza orderPizza(String type) {
    
            Pizza pizza;
    
            pizza = factory.createPizza(type);
    
            pizza.prepare();
            pizza.bake();
            pizza.cut();
            pizza.box();
    
            return pizza;
            
        }
    }

    팩토리 객체를 전달받아서 orderPizza()메소드로 피자 객체를 만든다. 

     

    간단한팩토리 

    디자인 패턴이라기보다는 프로그래밍에서 자주 쓰이는 관용구에 가깝다.

     

     

    피자가게 프레임 워크 만들기


    피자가게가 지점이 생기면서 다양한 스타일의 피자를 만들게 될 경우(뉴욕 스타일, 시칵 스타일, 캘리포니아 스타일 ..)

    피자만드는 일 자체는 전부 PizzaStore클래스에서 진행하면서 지점 스타일을 살리는 방법

     

    public abstract class PizzaStore {
    
        public Pizza orderPizza(String type) {
    
            Pizza pizza;
    
            pizza = createPizza(type);
    
            pizza.prepare();
            pizza.bake();
            pizza.cut();
            pizza.box();
    
            return pizza;
            
        }
    
        abstract Pizza createPizza(String type);
    }

    팩토리 메소드를 추상 메소드로 바꾼다.

    PizzaStore는 추상클래스가 된다.

     

    orderPizza()메소드에서 피자 객체를 가지고 여러 가지 작업(굽고, 자르고, 포장 등)을 하지만 orderPizza()는 실제로 어떤 구상클래스에서 작업이 처리되는지 모른다. PizzaStore와 Pizza는 서로 완전히 분리되어 있다.

     

    피자의 종류는 어떤 서브클래스를 선택했느냐에 따라 결정된다.

    public class NYPizzaStore extends PizzaStore{
    
        Pizza createPizza(String item) {
            if(item.equals("cheese")) {
                return new NYStyleCheesePizza();
            } else if(item.equals("veggie")) {
                return new NYStyleVeggiePizza();
            } else if(item.equals("calm")) {
                return new NYStyleCalmPizza();
            } else if(item.equals("pepperoni")) {
                return new NYStylePepperoniPizza();
            } else return null;
        }
        
    }

    구상클래스에서 피자 종류에 해당하는 피자를 생성해서 리턴한다.

     

     

    Pizza class 만들기


    public abstract class Pizza {
        
        String name;
        String dough;
        String source;
    
        List<String> toppings = new ArrayList<String>();
    
        public void prepare(){
            System.out.println("준비중 : " + name);
            System.out.println("도우를 돌리는중");
            System.out.println("소스를 뿌리는 중 ...");
            System.out.println("토핑을 올리는중");
            toppings.forEach(System.out::println);
        }
    
        public void bake(){
            System.out.println("175도에서 25분간 굽기");
        }
    
        public void cut() {
            System.out.println("피자를 사선으로 자르기");
        }
    
        public void box(){
            System.out.println("피자를 상자에 담기");
        }
    
        public String getName() {
            return name;
        }
    }
    public class NYStyleCheesePizza extends Pizza {
    
        public NYStyleCheesePizza() {
            name = "뉴욕스타일 소스와 치즈피자";
            dough = "씬 크러스트 도우";
            source = "마리나라 소스";
        
            toppings.add("잘게 썬 레지아노 치즈");
        }
    
    }
    ---------------------------------------------------------
    public class ChicagoStyleCheesePizza extends Pizza {
    
        public ChicagoStyleCheesePizza() {
            name = "시카고 스타일 딥 디쉬 치즈 피자";
            dough = "아주 두꺼운 크러스트 도우";
            source = "플럼토마토 소스";
    
            toppings.add("잘게 조각낸 모짜렐라 치즈");
        }
        
        void cut() {
            System.out.println("네모난 모양으로 피자 자르기");
        }
    }

     

    팩토리 메소드 패턴 정의


    객체를 생성할 때 필요한 인터페이스를 만든다. 

    어떤 클래스의 인스턴스를 만들지는 서브클래스에서 결정한다.

    팩토리 메소드 패턴 사용시 클래스 인스턴스 만드는 일을 서브클래스에게 맡기게 된다.

     

    간단한 팩토리 vs 팩토리 메소드 패턴 

    간단한 팩토리는 일회용 처방

    팩토리 메소드패턴은 여러번 재사용이 가능한 프레임워크를 만들 수 있다.

     

     

    의존성 뒤집기 원칙


    PizzaStore는 고수준 구성요소, Pizza는 저수준 구성요소 -> PizzaStore는 Pizza구상클래스에 의존하고 있다.

    Pizza 클래스를 추상화해서 추상클래스에 의존하는 것. 구체클래스가 추상클래스에 의존하는 것을 말한다.

     

    - 구체화된 것 대신 추상화된 것에 의존하라

    - 고수준 모듈이 저수준 모듈에 의존하지 않도록 하라.

    - 변수에 구상 클래스의 레퍼런스를 저장하지마라.

    - 구상클래스에서 유도된 클래스를 만들지 마라.

    - 베이스클래스에서 이미 구현되어 있는 메소드를 오버라이드 하지마라.

     

    ☝🏻 디자인 원칙 

    추상화된 것에 의존하게 만들고 구상클래스에 의존하지 않게 만든다. 

     

     

     

    원재료 팩토리 만들기


    public interface PizzaIngredientFactory {
        
        public Dough createDough();
        public Sauce createSauce();
        public Cheese createCheese();
        public Veggies[] createVeggies();
        public Pepperoni createPepperoni();
        public Clams createClam();
    }

    지역별로 팩토리를 만들 경우 생성메소드를 구현하는 PizzaIngredientFactory class를 만들어야함.

     

    public class NYPizzaIngredientFacoty implements PizzaIngredientFactory {
    
        public Dough createDough() {
            return new ThinCrustDough();
        }
    
        public Sauce createSauce() {
            return new MarinaraSauce();
        }
    
        public Cheese createCheese() {
            return new ReggianoCheese();
        }
    
        public Veggies[] createVeggies() {
            Veggies veggies[] = {new Garlic(), new Onion(), new Mushroom(), new RedPepper()};
            return veggies;
        }
    
        public Pepperoni createPepperoni() {
            return new SlicedPepperoni();
        }
    
        public Clams createClam() {
            return new FreshClams();
        }
        
    }
    public abstract class Pizza {
        
        String name;
        String dough;
        String source;
        Veggies veggies[];
        Cheese cheese;
        Pepperoni pepperoni;
        Clams clam;
    
        abstract void prepare();
    
    
        public void bake(){
            System.out.println("175도에서 25분간 굽기");
        }
    
        public void cut() {
            System.out.println("피자를 사선으로 자르기");
        }
    
        public void box(){
            System.out.println("피자를 상자에 담기");
        }
    
        void setName(String name) {
            this.name = name;
        }
    
        public String getName() {
            return name;
        }
    
        public String toString() {
            // print..
        }
    }

    Pizza 클래스에서 prepare()메소드를 추상메소드로 만들었다.

    모든 원재료는 원재료 팩토리에서 가져온다.

    public class NYPizzaStore extends PizzaStore{
    
        protected Pizza createPizza(String item) {
            Pizza pizza = null;
            PizzaIngredientFactory ingredientFactory = new NYPizzaIngredientFacoty();
    
    
            if(item.equals("cheese")) {
                pizza = new CheesePizza(ingredientFactory);
                pizza.setName("NewYork Style cheese pizza");
            } else if(item.equals("veggie")) {
                pizza = new VeggiePizza(ingredientFactory);
                pizza.setName("NewYork Style veggie pizza");
            } else if(item.equals("calm")) {
                pizza = new CalmPizza(ingredientFactory);
                pizza.setName("NewYork Style clam pizza");
            } else if(item.equals("pepperoni")) {
                pizza = new PepperoniPizza(ingredientFactory);
                pizza.setName("NewYork Style pepperoni pizza");
            } 
            return pizza;
        }
        
    }

    뉴욕 지점에는 뉴욕 피자 원재료 팩토리를 전달해주면 원재료를 이 팩토리에서 공급한다.

     

    추상팩토리 패턴 정의


    구상클래스에 의존하지 않고도 서로 연관되거나 의존적인 객체로 이루어진 제품군을 생산하는 인터페이스를 제공.(객체구성으로 만듬)

    구상클래스는 서브클래스에서 만듬 

     

    추상팩토리 패턴을 사용하면 클라이언트에서 추상 인터페이스로 일련의 제품을 공급받을 수 있고 실제로 어떤 제품이 생산되는지 전혀 알 필요가 없다.

     

     

    정리


    • 팩토리 메소드 패턴은 상속을 활용, 객체 생성을 서브클래스에게 맡긴다.
    • 추상팩토리 패턴은 객체 구성을 활용, 팩토리 인터페이스에서 선언한 메소드에서 객체 생성이 구현된다. 
    • 모든 팩토리 메소드 패턴은 특정 클래스에서 인스턴스 만드는 일을 서브클래스에게 넘긴다. 
    • 추상팩토리 패턴은 구상 클래스에 직접 의존하지 않고도 서로 관련된 객체로 이루어진 제품군을 만드는 용도로 쓰인다.
    • 의존성 뒤집기 원칙을 따르면 구상 형식 의존을 피하고 추상화를 지향할 수 있다.
    • 팩토리는 구상클래스가 아닌 추상클래스와 인터페이스에 맞춰서 코딩할 수 있게 해주는 강력한 기법이다.
    •  
    728x90
    반응형