1. 팩토리 메서드 패턴
팩토리 메서드 패턴(Factory Method Pattern)이란 객체 생성을 하는 클래스를 따로 두는 것입니다.
그래서 공장(Factory)이라는 표현을 쓰나봅니다. 🙂
실질적인 클래스의 구현은 하위 클래스에서 이루어지는데요.
이렇게 되면 상위 클래스는 하위 클래스의 구현내용을 모르더라도 사용이 가능합니다.
어려운 말로 객체간 결합도가 낮아진다고 하는데요.
결합도란 모듈이 어떤 작업을 수행할 때, 다른 모듈에게 얼마나 의존하느냐입니다.
결합도가 낮아지면 유지보수 하기가 쉬워집니다.
예시를 먼저 보는게 빠를 것 같습니다.
현대자동차에서 차를 만든다고 가정해보겠습니다.
그럼 자동차 공장에 팩토리 메서드를 두고,
공장에서 승용차, 버스, 스쿠터 등을 생성하도록 두는 것이죠. 😊
UML 다이어 그램으로 표시해 보겠습니다.
현대자동차에서는 Factory 클래스의 MakeCar()로 모든 차를 만들 수 있습니다.
String으로 승용차, 버스, 스쿠터 중 무엇을 줄 지만 결정하면 되죠.
뿐만아니라 Car 인터페이스의 추상메서드 Drive()를 각각 SmallCar, Bus, Scooter가 구현하므로
차종에 맞게 Drive()를 오버라이딩 할 수 있습니다.
코드로 보는 것이 더 이해가 빠를 듯 합니다.
package exFactoryMethodPattern;
// 객체를 생성할 Factory 클래스
public class Factory {
// 객체를 생성할 팩토리 메서드
public Car MakeCar(String s) {
if (s == "승용차") {return new SmallCar();}
if (s == "버스") {return new Bus();}
if (s == "스쿠터") {return new Scooter();}
return null;
}
}
package exFactoryMethodPattern;
// Bus 클래스
public class Bus implements Car {
// 버스에 맞는 추상메서드 Drive() 구현
@Override
public void Drive() {
System.out.println("버스 - 1종운전 주행을 시작합니다.");
}
}
package exFactoryMethodPattern;
public class SmallCar implements Car{
// SmallCar에 맞는 추상메서드 Drive() 구현
@Override
public void Drive() {
System.out.println("승용차 - 2종운전 주행을 시작합니다.");
}
}
package exFactoryMethodPattern;
public class Scooter implements Car{
// Scooter에 맞는 추상메서드 Drive() 구현
@Override
public void Drive() {
System.out.println("스쿠터 - 스쿠터 주행을 시작합니다.");
}
}
package exFactoryMethodPattern;
public class Hyundai {
public static void main(String[] args) {
Factory factory = new Factory();
// 팩토리 메서드 MakeCar()로 버스, 승용차, 스쿠터 객체 생성
Car bus = factory.MakeCar("버스");
Car smallcar = factory.MakeCar("승용차");
Car scooter = factory.MakeCar("스쿠터");
// 차종 별로 운전해보기
bus.Drive();
smallcar.Drive();
scooter.Drive();
}
}
이렇게 팩토리 메서드를 사용하면 어떤 장점이 있을까요?
팩토리 메서드 MakeCar()에서만 객체를 생성하니 객체 관리가 편리합니다.
반환된 객체가 Bus던, SmallCar던, Scooter던 신경 쓸 필요가 없습니다.
또한 새로운 클래스 Truck을 추가한다면, 하위클래스를 생성한 뒤 Car 인터페이스만 구현하면 됩니다.
기존의 클래스를 변경할 필요 없이 확장이 가능이 가능한 장점도 있네요. 🙂
하지만 너무 많은 클래스를 생성할 경우 팩토리 메서드가 복잡해지니 주의해야 합니다.
2. 템플릿 메서드 패턴
템플릿 메서드 패턴은 상속 시 상위 클래스의 메서드를 3종류로 나누는 방식입니다.
템플릿 메서드, 추상메서드, 훅 메서드로 나뉘고
템플릿 메서드를 실행하는 과정에서 추상메서드와 훅 메서드를 호출하는 구조입니다.
구체적인 역할은 다음과 같습니다.
1. 공통된 역할을 수행하는 메서드인 템플릿 메서드(Template Method)
2. 반드시 구현해야 하는 추상 메서드(Primitive Method)
3. 그대로 사용해도 되고 오버라이딩해서 사용해도 되는 훅 메서드(Hook Method)
이 때 장점은 하위클래스가 전체 로직을 변경하지 않으면서 부분적인 수정이 가능합니다.
템플릿 메서드가 전체적인 알고리즘 구조를 보호한다고 표현하기도 합니다.
뿐만아니라 코드의 중복을 최소화 할 수 있는 장점도 있습니다.
디테일한 코드는 하위클래스에서만 오버라이딩해서 사용회면 되는 것이죠. 😎
아메리카노와 카페라떼의 차이를 아시나요?
아메리카노는 에스프레소에 물을, 카페라떼는 에스프레소에 우유를 넣은 것입니다.
이를 바탕으로 커피를 판매하는 카페 프로그램을 작성한다고 가정해보겠습니다.
아메리카노 주문이 들어왔습니다.
그럼 점원은 에스프레소를 내리고, 물을 넣고, 머그컵에 담아 손님에게 줄 것입니다.
그리고 만약 테이크 아웃이면 일회용컵에 담아 건네주겠죠.
UML 다이어그램으로 나타내면 이렇습니다.
템플릿 메소드 makeCoffe()는 Espresso() -> putSomething() -> wrap() 순서로 진행됩니다.
추상메소드인 putSomething()은 하위 클래스인 Americano, Cafelatte에서 각각 구현되고요.
훅 메소드인 wrap()은 일반적으로 머그컵 포장이나, 변경해 줄 수 도 있습니다.
다음은 구현한 코드입니다.
package exTemplateMethodPattern;
public abstract class Coffee {
// 템플릿 메서드 makeCoffee()
public void makeCoffee() {
System.out.println("커피 머신을 시작합니다.");
Espresso();
putSomething();
wrap();
}
// 공통 메서드는 상위 클래스에서 구현
public void Espresso() {
System.out.println("에스프레소를 넣습니다.");
}
// 상속 시 반드시 구현해야하는 추상메서드 putSomething();
public abstract void putSomething();
// 선택적 오버라이딩이 가능한 훅 메서드 wrap()
public void wrap() {
System.out.println("머그잔에 커피를 담습니다.");
}
}
package exTemplateMethodPattern;
// 아메리카노
public class Americano extends Coffee{
// 추상메서드 구현
public void putSomething() {
System.out.println("물을 넣습니다.");
}
}
package exTemplateMethodPattern;
// 카페라떼
public class Cafelatte extends Coffee {
// 추상메서드 구현
public void putSomething() {
System.out.println("우유를 넣습니다.");
}
}
package exTemplateMethodPattern;
// 아메리카노 테이크아웃
public class Takeout_Americano extends Coffee {
// 추상메서드 구현
public void putSomething() {
System.out.println("물을 넣습니다.");
}
// wrap 메서드 오버라이딩
public void wrap() {
System.out.println("테이크 아웃으로 포장합니다.");
}
}
package exTemplateMethodPattern;
// 카페라떼 테이크아웃
public class Takeout_Cafelatte extends Coffee {
// 추상메서드 구현
public void putSomething() {
System.out.println("우유를 넣습니다.");
}
// wrap 메서드 오버라이딩
public void wrap() {
System.out.println("테이크 아웃으로 포장합니다.");
}
}
package exTemplateMethodPattern;
public class Clerk {
public static void main(String[] args) {
Americano americano = new Americano();
Cafelatte cafelatte = new Cafelatte();
Takeout_Americano to_americano = new Takeout_Americano();
Takeout_Cafelatte to_cafelatte = new Takeout_Cafelatte();
// 아메리카노, 라떼를 만들어 매장 손님께 전달
System.out.println("--- Americano --- ");
americano.makeCoffee();
System.out.println("--- CafeLatte ---");
cafelatte.makeCoffee();
System.out.println("===== Take out 입니다 =====");
// 아메리카노, 라떼를 만들어 테이크 아웃 손님께 전달
System.out.println("--- Americano --- ");
to_americano.makeCoffee();
System.out.println("--- CafeLatte ---");
to_cafelatte.makeCoffee();
}
}
3. 정리 및 느낀점
팩토리 메서드 패턴의 경우 상위 클래스가 하위 클래스의 구현 내용을 몰라도 사용할 수 있었습니다.
객체를 생성 공장을 따로 두었기 때문에 객체 관리가 편해졌습니다.
새로운 클래스가 추가되더라도 기존 코드를 변경할 필요가 없었죠.
결과적으로 결합도가 낮아지면서 유지보수에 용이해졌습니다. 🙂🙂
템플릿 메서드 패턴의 경우 재사용성이 크게 증가했습니다.
공통된 코드들은 템플릿 메서드에 넣어두었습니다.
반드시 구현해야 하는 추상메서드와 선택적 오버라이딩이 가능한 훅 메서드를 따로 두었고요.
템플릿 메서드가 추상메서드, 훅 메서드를 호출하는 구조로 되어있어
전체 알고리즘 구조를 보호하면서도 부분적인 수정이 가능해졌습니다.
이상으로 팩토리 메서드 패턴과 템플릿 메서드 패턴에 대해 정리해보았습니다. 😌
References
egloos.zum.com/ani2life/v/2887675
ko.wikipedia.org/wiki/%ED%8C%A9%ED%86%A0%EB%A6%AC_%EB%A9%94%EC%84%9C%EB%93%9C_%ED%8C%A8%ED%84%B4
scorpio-mercury.tistory.com/18
gmlwjd9405.github.io/2018/07/13/template-method-pattern.html
yaboong.github.io/design-pattern/2018/09/27/template-method-pattern/
'객체 지향 프로그래밍 > 디자인 패턴' 카테고리의 다른 글
디자인 패턴 이해하기 (2) | 2021.03.07 |
---|---|
전략 패턴, 템플릿 콜백 패턴 (6) | 2021.01.02 |
싱글톤, 프록시, 데코레이터 패턴 (0) | 2020.12.31 |