객체 생성의 인터페이스를 정의하지만
인스턴스를 만들 클래스의 선택은 서브클래스에 맡기는 디자인 패턴!
Truck을 이용해 물품을 배달하는 서비스가 있다고 해보자
public class Truck {
public void delivery() {
System.out.println("Truck Delivery!");
}
}
public class DeliveryService {
public static void main(String[] args) {
Truck truck = new Truck();
truck.delivery();
}
}
위 경우 트럭말고 다른 운송 체계인 Ship(배) 추가 된다고 해보자
Truck 과 결합된 모든 클래스들의 수정을 해야할 것이다.
이는 엄청난 비용이 소모될 것이다.
자바에 대해 익숙한 사람이라면 처음에 가장 생각나는 것이 추상 클래스 또는 인터페이스의 활용일 것이다.
interface Transport {
void delivery();
}
class Truck implements Transport {
@Override
public void delivery() {
System.out.println("Truck Delivery!");
}
}
class Ship implements Transport {
@Override
public void delivery() {
System.out.println("Ship Delivery");
}
}
public class DeliveryServiceInterface {
public static void main(String[] args) {
Transport transport = new Truck();
transport.delivery();
}
}
이렇게 인터페이스를 이용해 인터페이스의 메서드로 추상화가 가능하다.
Client 클래스가 있다면 이런 방식으로 transport를 사용하는 행동을 숨길수 있다.
이것을 우리는 전략 패턴이라 부르기로 했다.
interface Transport {
void delivery();
}
class Truck implements Transport {
@Override
public void delivery() {
System.out.println("Truck Delivery!");
}
}
class Ship implements Transport {
@Override
public void delivery() {
System.out.println("Ship Delivery");
}
}
class Client {
private Transport transport;
public void setTransport(Transport transport) {
this.transport = transport;
}
public void deliveryTransport() {
this.transport.delivery();
}
}
public class DeliveryServiceStrategy {
public static void main(String[] args) {
Client client = new Client();
client.setTransport(new Ship());
client.deliveryTransport();
}
}
위 코드로 ship을 추가 할때 코드 수정관련 문제를 해결 가능 할까…?
애초에 ship을 추가 할때 무슨 문제가 생겼던가?
결론적으론 ship 을 추가 할때 문제중 하나는 해결이 된것이다.
Ship을 사용하다 Truck 을 사용하고 싶을때! 의 문제
(설명을 덧붙히자면)
main에서 ship을 쓰다가 truck으로 service에 transport를 바꿔도 아래에 있는
client.deliveryTransport()의 코드는 건들게 없다.
하지만 전략패턴은 행동을 추상화하는 행동 패턴이다.
팩토리 메서드에서는 객체를 생성하는 생성을 추상화하는 생성 패턴이다.
이 말은 즉 new Truck, new Ship 같은 코드의 역할을 추상화하는 패턴이라는 것이다.
이제 나의 생각은 그만 말하고 팩토리 메서드 패턴의 코드를 보자
package DesignPattern.FactoryMethod.Factory;
// Transport 인터페이스, Truck 클래스, Ship 클래스
abstract class TransportCreator {
public abstract Transport createTransport();
public void deliveryTransport(){
Transport transport = createTransport();
transport.delivery();
}
}
class TruckFactory extends TransportCreator {
@Override
public Transport createTransport() {
return new Truck();
}
}
class ShipFactory extends TransportCreator {
@Override
public Transport createTransport() {
return new Ship();
}
}
public class DeliveryService {
public static void main(String[] args) {
TransportCreator truckFactory = new TruckFactory();
truckFactory.deliveryTransport();
TransportCreator shipFactory = new ShipFactory();
shipFactory.deliveryTransport();
}
}
위코드를 얼핏 보면 그저 생성자 위치를 바꾸기만 한게 아닌가 싶다
결국 Truck을 사용하려면 TruckFactory 를 클라이언트 측에서 사용해야하고
Ship을 사용하려면 ShipFactory를 사용해야 하니 코드를 더 부풀려 적은게 아닌가 싶은 생각도 든다.
하지만 위 코드는 Truck에서 Ship으로 사용을 바꾸는게 아닌, Truck에서 Truck을 상속한 RedTruck, FiveTonTruck 등으로 변경핼야 할때 진가를 발휘한다
( 갑자기 모든 트럭을 빨간 트럭으로 바꿔야함! )(설명을 덧붙히자면)
동적으로 바뀌는 Truck, Ship 에 대해서는 행동패턴인 전략패턴을 사용
정적으로 바뀌는 Truck, RedTruck, FiveTonTruck 에 대해서는 팩토리 메서드 패턴을 사용하면 된다.
RedTruck은 Truck을 사속한 확장된 클래스이다.
만약 이전 코드(인터페이스를 이용한) 에서 Truck에서 RedTruck으로 바꿔야 하는 일이 생기면 어떻게 될까
public class DeliveryServiceInterface {
public static void main(String[] args) {
Transport transport = new RedTruck();
transport.delivery();
}
}
정말 귀찮게도 클라이언트 측 코드를 손봐야 하는 일이 생겨버린다.
클라이언트는 트럭이 빨간색인지, 검은색인지 궁금하지않고 그저 배달만 되면 되는 것인데…
하지만 팩토리 메서드 패턴에서는?
class TruckFactory extends TransportCreator {
@Override
public Transport createTransport() {
return new RedTruck();
}
}
위처럼 create 부분만 변경하면 클라이언트는 그저 truckFactory.deliveryTransport(); 코드를 이용해 이전 코드를 계속 사용하면 된다.
참고 문헌
https://refactoring.guru/ko/design-patterns/factory-method