-
팩토리 메소드(Factory Method)패턴1Web 개발/디자인 패턴 2023. 6. 25. 12:57
Factory Method
시나리오
팩토리 메소드 패턴이 해결하려는 문제는 어떤 인스턴스를 생성하는 책임을 구체적인 클래스가 아니 추상적인 인터페이스의 메서드로 감싸는 것이다.
어떤 배를 만드는 공장이 있다고 가정할 때 그 공장은 고잉메리호라는 배 딱 한 종류만을 만들었다. 근데 사업이 잘되어 추후에 써니호라는 것도 만들게 되었다. 처음에는 고잉메리호만 만들었으므로 ShipFactory라는 클래스안에 orderShip이라는 메서드가 있다고 가정한다 해당 메서드는 당연히 static 메서드여도 되고 인스터스 메서드여도 된다. 해당 메서드는 Static 메서드라고 가정을 한다. 그래서 orderShip이라는 static메서드에 고잉메리호 라는 배를 만들어 주었다. 당연히 해당 메소드에는 배를 만드는 과정이 있을 것이다. 예를 들면 배를 만들기 위해 재료들을 준비하고, 배를만들고, 만들어진 배에 후 작업을 한다. 색을 입힌다던가 로고를 새긴다던가 그리고 배가 다만들어진 후 의뢰처에 연락을 해서 배가 다만들어 졌으니 배송위한 알림을 할 것이다. 이러한 일련의 과정이 static 메서드인 orderShip에서 이루어 질 것이다.
하지만 이제 써니호를 만들어야 하는 상황이 된다면 어떻게 하겠는가? 기존에 있는 orderShip이라는 메서드 안에다 주문한 배가 써니호이면 다른 로고를 달고 또 다른 색깔을 입히고, 그안에 if else 문이 들어가면서 코드가 점차복잠해지기 시작할 것이다.
그 다음에 해당 공장의 사업이 너무 잘되서 그냥 단순한 배가 아니라 하늘을 날 수도 있고 배처럼 쓸수도 있는 상품을 만들어서 판매하게 되어 위와 같은 상품도 만들 수 있도록 바뀌어야 한다면 배를 만드는 모든 과정에 하나의 구체적인 클래스(Concrete Class)모두 담아 두기에는 로직이 너무 복잡해질 것이다. 그렇기 때문에 추상화되어 있는 Factory를 만들어서 이문제를 해결해야한다.
Factory
구체적으로 어떤 인스턴스를 만들지는 서브 클래스가 정한다.
- 다양한 구현체(Product)가 있고, 그 중에서 특정한 구현체를 만들 수 있는 다양한 팩토리(Creator)를 제공할 수 있다.
우선 Factory 역할을 할 인터페이스를 만든다. 그 인터페이스 안에 정의되어 있는 메서드는 여러개가 있을 수 있다. Java의 경우는 Java8부터 기본 메서드를 구현할 수 있으므로 굳이 추상 클래스를 만들 필요없이 기본적인 구현은 인터페이스 안에 넣어 둘 수 있다. 기본적인 구현이 있고, 그 구현구 중 일부 바뀌어야 하는 것들을 추상 메서드로 빼내어서 하위 클래스에서 만들수 있도록 해주는 것이다.
그렇게 하면 하위클래스 즉 구체적인 클래스에서 구체적인 인스턴스들을 만들 수 있게 된다. 그리고 이에 대응하는 Product라는 즉 Factory에서 만들어내는 object에 타입이 Product인 것이고, 얼마든지 다양한 Product들을 만들 수 있도록 Product도 인터페이스로 만들고 각각의 구체적인 결과물 인스터스들은 Factory안에서 만들수 있도록 설계를 하면 확장에 용의한 그런 구조의 코드가 된다.
구체 코드(Factory 패턴 적용전)
Client.class
public class Client { public static void main(String[] args) { Client client = new Client(); Ship whiteship = ShipFactory.orderShip("Whiteship", "study1@mail.com"); System.out.println(whiteship); Ship blackship = ShipFactory.orderShip("Blackship", "study2@mail.com"); System.out.println(blackship); } }
Ship.class
public class Ship { private String name; private String color; private String logo; public String getName() { return name; } public void setName(String name) { this.name = name; } public String getColor() { return color; } public void setColor(String color) { this.color = color; } public String getLogo() { return logo; } public void setLogo(String logo) { this.logo = logo; } }
ShipFactory.class
public class ShipFactory { public static Ship orderShip(String name, String email) { //validate if (name == null || name.isBlank()) { throw new IllegalArgumentException("배 이름을 지어주세요."); } if (email == null || email.isBlank()) { throw new IllegalArgumentException("연락처를 남겨주세요."); } Ship ship = new Ship(); ship.setName(name); //Customizing for specific name if(name.equalsIgnoreCase("whiteship")){ ship.setLogo("\uD83D\uDEE5"); }else if(name.equalsIgnoreCase("blackship")){ ship.setLogo("⚓"); } //coloring if(name.equalsIgnoreCase("whiteship")){ ship.setColor("whiteship"); }else if(name.equalsIgnoreCase("blackship")){ ship.setColor("black"); } return ship; } }
ShipFactory 즉 배를 만드는 공장에서의 로직은 배이름에 따라서 색을 따르게 입히고, 로고를 다르게 입히고 하는 코드가 orderShip()메서드안에 모두 있다.
ShipFactory코드에 또 다른 종류의 배를 만드는 코드가 추가된다고 할 경우 위 코드에서 orderShip()메서드에 name 인자가 바뀔때 마다 if else문 의 경우의 수가 더 추가 될 수 있고, 또 다른 경우 Ship 인스턴스 자체에 변화가 일어날 수 있다.
현재 Ship이라는 클래스는 구체적인 클래스이다 보니까 만들어낼 제품의 특성이 있으면 Ship 클래스를 수정해야한다. 그렇게 된다면 새로운 요구사항(변경사항)이 있을때 마다 기존의 코드를 수정해야한다.
그렇게 되면 해당 코드는 변경에 닫혀 있지 않게 된다. 객체지향의 원칙중에 하나로 OPC(Opne Closed Principle:개방-폐쇄 원칙)이라는 것이 존재한다. 확장에는 열려 있고 변경에는 닫혀있어야한다는 의미를 가진다.
어떤 요구사항으로 인해서 기존 코드가 계속해서 바뀌게 된다면 해당 코드는 변경에 닫혀있기 않은 것이다.
그렇다면 위의 코드를 어떻게 바꿔서 확장에는 열려있고 변경에는 닫혀있게 만들수 있을까?
[출처 - https://www.inflearn.com/course/%EB%94%94%EC%9E%90%EC%9D%B8-%ED%8C%A8%ED%84%B4/dashboard]
'Web 개발 > 디자인 패턴' 카테고리의 다른 글
팩토리 메소드(Factory Method)패턴2 (0) 2023.06.25 싱클톤(Singleton)패턴 구현2 (0) 2023.06.17 싱글톤(Singleton)패턴1 (0) 2023.06.17 디자인 원칙 - 구현이 아닌 인터페이스에 대해 프로그래밍 (0) 2022.12.21 디자인 원칙 - 변화하는 내용을 캡슐화 (0) 2022.12.20