해당 포스팅은 한빛 미디어 헤드퍼스트 디자인 패턴(에릭 프리먼, 엘리자베스 롭슨 저)를 통해 공부한 내용을 정리한 블로그입니다.

아직 많이 부족하고 배울게 너무나도 많습니다. 틀린내용이 있으면 언제나 가감없이 말씀해주시면 감사하겠습니다😁
좋은 프로그래머는 자기 두뇌를 사용한다. 그러나 좋은 가이드라인은 모든 케이스를 고려해야만 하는 노력을 줄여준다.
(Francis Glassborow, 개발자)

디자인 패턴의 설계

느슨한 결합으로 객체지향 디자인

  • 객체의 인스턴스는 항상 공개 되어야 하는 것이 아니며 모든 것을 공개 했다가는 문제가 생길 수도 있음
  • new 연산자가 구상을 생각!!
    • new를 사용하면 구상 클래스의 인스턴스가 만들어짐
    • 이는 구상 클래스를 바탕으로 코드를 수정해야 할 가능성이 커지고 유연성이 떨어짐.
      Duck duck = new MallardDuck()

new의 문제?

  • 구상 클래스 간의 변화되는 부분이 문제!
  • 인터페이스에 맞춰 코딩하면 변화에 대응이 가능함. ⇒ 다형성
  • 변경에는 닫혀있고 확장에는 열려있어야한다.

피자 코드 설계


public class Pizza {
    public Pizza() {
    }

    Pizza orderPizza(String type) {
        Pizza pizza;

        if (type.equals("cheese")) {
            pizza = new ChesePizza();
        } else if (type.equals("greek")) {
            pizza = new GreekPizza();
        } else if (type.equals("pepperoni")) {
            pizza = new PepperoniPizza();
        }

        pizza.prepare();
        pizza.bake();
        pizza.cut();
        pizza.box();

        return pizza;
    }
}
  • pizza는 type에 따라 구상 클래스의 인스턴스를 생성.
  • 사라지는 피자와 새로 생기는 피자가 생기면 코드 지우고 코드 새로 추가해야됨.

객체 생성 부분 캡슐화(변화가 생기는 부분)

  • 객체 생성을 처리하는 클래스를 팩토리라고 함.
  • SimplePizzaFactory가 OrderPizza()메소드가 새로 만든 객체의 클라이언트 생성해줌.
public class SimplePizzaFactory {
    Pizza create(String type) {
        Pizza pizza = null;
        if (type.equals("cheese")) {
            pizza = new CheesePizza();
        } else if (type.equals("greek")) {
            pizza = new GreekPizza();
        } else if (type.equals("pepperoni")) {
            pizza = new PepperoniPizza();
        }

        return pizza;
    }
}
  • 이러한 방법은 static factory라고 부름
    • 객체 생성 메소드를 실행하려고 객체의 인스턴스를 만들지 않아도 되기 때문.

Client Code 수정하기

객체의 팩토리 인스턴스 저장하기

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;
    }
}

public class SimplePizzaFactory {

    public SimplePizzaFactory() {
    }

    Pizza create(String type) {
        Pizza pizza = null;

        if (type.equals("cheese")) {
            pizza = new CheesePizza();
        } else if (type.equals("greek")) {
            pizza = new GreekPizza();
        } else if (type.equals("pepperoni")) {
            pizza = new PepperoniPizza();
        }

        return pizza;
    }
}
  • 각각의 지점들은 굽는 방식이 달라지거나 피자의 토핑이 달라질 수 있음.
  • PizzaStore를 만들어 각각 스타일대로 구현

다양한 팩토리 만들기


다양한 스타일의 피자 가게가 있음.

public abstract class PizzaStore { 
	abstract Pizza createPizza(String item);
 
	public Pizza orderPizza(String type) {
		Pizza pizza = createPizza(type);
		System.out.println("--- Making a " + pizza.getName() + " ---");
		pizza.prepare();
		pizza.bake();
		pizza.cut();
		pizza.box();
		return 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("clam")) {
			return new NYStyleClamPizza();
		} else if (item.equals("pepperoni")) {
			return new NYStylePepperoniPizza();
		} else return null;
	}
}

public class ChicagoPizzaStore extends PizzaStore {
	Pizza createPizza(String item) {
		if (item.equals("cheese")) {
			return new ChicagoStyleCheesePizza();
		} else if (item.equals("veggie")) {
			return new ChicagoStyleVeggiePizza();
		} else if (item.equals("clam")) {
			return new ChicagoStyleClamPizza();
		} else if (item.equals("pepperoni")) {
			return new ChicagoStylePepperoniPizza();
		} else return null;
	}
}

// 뉴욕식 치즈피자
PizzaStore nyStore = new NYPizzaStore();
nyStore.orderPizza("cheese");

// 시카고식 치즈피자
PizzaStore chicagoStore = new ChicagoPizzaStore();
chicagoStore.orderPizza("cheese");

팩토리 메소드 패턴

Factory Method Pattern

  • 팩토리 패턴객체 생성(Pizza)을 캡슐화
  • 팩토리 메서드 패턴은 서브클래스(NYPizzaStore, ChicagoPizzaStore)에서 어떤 클래스를 만들지 정하므로, 객체 생성을 캡슐화
  • 제품 클래스(Pizza) 와 생산자 클래스(PizzaStore)를 분리함으로써 구체적인 구현은 구상 클래스가 책임 지도록.
  • 특징
    • 캡슐화: 객체 생성 로직을 팩토리 클래스나 메서드로 분리하여 외부에서 직접 객체를 생성하지 않도록 함.
    • 유연성: 객체 생성 방식을 변경할 때 클라이언트 코드를 수정할 필요 없이 팩토리 클래스만 수정하면 됨.
    • 확장성: 새로운 종류의 객체를 추가할 때도 기존 클라이언트 코드에 영향을 주지 않고 팩토리 클래스만 확장하면 됨.
    • OCP: 하나의 클래스에 추가적인 기능을 넣어도 다른클래스의 변경을 하지 않아도된다.
💡 팩토리 메서드 패턴
객체를 생성할 때 필요한 인터페이스(PizzaStore)를 만들고 어떤 클래스의 인터페이스를 만들지는
서브 클래스(NYPizzaStore, ChicagoPizzaStore)에서 결정하도록 함.
💡 Simple Factory VS Factory Method 
팩토리 메소드 패턴간단한 팩토리와 상당히 비슷하지만 간단한 팩토리는 일회용 처방에 불과한 반면, 팩토리 메소드 패턴을 사용하면 여러번 재사용이 가능한 프레임워크를 만들 수 있습니다. 예를 들어, 팩토리 메소드 패턴의 orderPizza( ) 메소드는 피자를 만드는 일반적인 프레임워크를 제공합니다. 그 프레임워크는 팩토리 메소드 피자 생성 구상 클래스를 만들었죠. PizzaStore 클래스의 서브 클래스를 만들 때, 어떤 구상 제품 클래스에서 리턴할 피자를 만들지를 결정합니다. 이 프레임워크를 간단한 팩토리와 한번 비교해 보세요. 간단한 팩토리는 객체 생성을 캡슐화하는 방법을 사용하긴 하지만 팩토리 메소드만큼 유연하지는 않습니다. 생성하는 제품을 마음대로 변경할 수 없기 때문입니당. => (SimpleFactory라는 인스턴스를 가지고 있기 때문)

 

의존성 뒤집기 원칙(Dependency Inversion)

  • 구상 클래스의 의존성을 떨어뜨리기
  • 즉, 고수준 구성요소는 저수준 구성요소에 의해 정의되는 행동이 들어있는 구성요소
    • PizzaStore는 고수준 구성요소
    • Pizza 클래스는 저수준 구성요소.
💡 추상화된 것에 의존하게 만들고, 구상 클래스에 의존하지 않게 만든다.
  • DI를 지키는 방법
    • 변수에 구상 클래스의 레퍼런스를 저장하지 않는다.
    • 구상 클래스에서 유도된 클래스를 만들지 말자.
    • 베이스 클래스에 이미 구현되어 있는 메소드를 오버라이드하지 말자.

추상 팩토리 패턴

Abstract Factory Pattern

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

팩토리 메소드 패턴 VS 추상 팩토리 패턴

비교\패턴 팩토리 메소드 패턴 추상 팩토리 패턴
공통점 애플리케이션을 특정 구현으로부터 분리.
제품 생성 클래스를 사용 객체를 사용
제품 생성 방식 상속 구성
제품 생성 방법 객체를 생성하고 클래스를 확장할 때에는 팩토리 메소드를 오버라이드하여 서브 클래스에서 객체를 생성 제품군을 만드는 추상 형식을 제공. 인스턴스를 만든 다음 추상 형식을 써서 만든 코드에 전달.

 

'기타 > 디자인패턴' 카테고리의 다른 글

[Design Pattern] Command Pattern  (2) 2024.06.03
[Design Pattern] Singleton Pattern  (0) 2024.05.31
[Design Pattern] Decorator Pattern  (0) 2024.05.28
[Design Pattern] Observer Pattern  (0) 2024.05.27
[Design Pattern] Strategy Pattern  (0) 2024.05.27

+ Recent posts