-
팩토리 메소드(Factory Method)패턴2Web 개발/디자인 패턴 2023. 6. 25. 15:37
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); } }
Client.class는 Factory를 사용하는 코드이다. Factory 패턴을 적용하지 않는 ShipFactory 클래스에서 orderShip()메서드를 가져와서 여러 공정을 거친 Ship 인스턴스를 만들어서 출력한다.
Factory 패턴을 적용하기 전의 ShipFactory 클래스에는 고잉메리호와 써니호를 만드는 코드가 모두 담겨있다. 먼저 인터페이스를 만든다. 그리고 배를 만드는 과정, 프로세스에서 배를 만드는 중에 공통과정이 아닌 배의 종 구별지을 수 있는 특수 공정 과정은 밖으로 빼내 고칠 수 있게 한다.
우선 기존에 존재하는 ShipFactory 클래스의 이름을 WhiteShipFactory로 수정한고 ShipFactory라는 이름의 인터페이스를 생성한다.
ShipFactory interface 작성
1. validate 유효성검사 과정
ShipFactory.interface
public interface ShipFactory { default Ship orderShip(String name, String email) { validate(name,email); } private void validate(String name,String email){ if (name == null || name.isBlank()) { throw new IllegalArgumentException("배 이름을 지어주세요."); } if (email == null || email.isBlank()) { throw new IllegalArgumentException("연락처를 남겨주세요."); } } }
원래 ShipFactory에서 하던 일인 메서드 orderShip을 정의해 준다. 인터페이스 이므로 굳이 public 키워드를 적어 주지 않고 default 키워드를 적어준다 orderShip에서 제일 먼저한는 것은 validate()이다.
2. Prepare 배를 만들 준비를 한다.
public interface ShipFactory { default Ship orderShip(String name, String email) { validate(name,email); prepareFor(name); } private void prepareFor(String name){ System.out.println(name + " 만들 준비 중 "); } }
두번 째로 하는 것은 prepareFor()메서드
3. Ship 인스턴스를 만드는 과정 추가
public interface ShipFactory { default Ship orderShip(String name, String email) { validate(name,email); prepareFor(name); Ship ship = createShip(); return ship; } Ship createShip(); private void validate(String name,String email){ if (name == null || name.isBlank()) { throw new IllegalArgumentException("배 이름을 지어주세요."); } if (email == null || email.isBlank()) { throw new IllegalArgumentException("연락처를 남겨주세요."); } } }
세번째로 하는 것은 실제로 Ship 인스턴스를 만드는 것이 었는데, 실제로 Ship 인스턴스를 만드는 과정은 하위 클래스에 위임 즉, 하위 클래스에서 구현을 할 것이다.
인터페이스에서 createShip메서드에 private이나 public 같은 접근제한자 요소를 정의해 주지 않았기 때문에 구현제가 될 하위 클래스에서 반드시 정의를 해주어야 한다.
그 다음에 원래 ShipFactory 클래스에서 해주던 Customizing for specific name이나 coloring 특정한 배에 특화되어 있는 로직이기 때문에 원래 ShipFactory에서 처럼 Ship 인스턴스가 만들어지고 orderShip()메서드에서 처리 할 수도 있겠지만 배를 만드는 공정에 해당하기 때문에 createShip메서드 안해서 해당 과정을 처리한다.
4. notify 배만드는 것 완료후 고객에게 완료를 알리는 이메일 보내기
public interface ShipFactory { default Ship orderShip(String name, String email) { validate(name,email); prepareFor(name); Ship ship = createShip(); sendEmailTo(email,ship); return ship; } private static void sendEmailTo(String email, Ship ship){ System.out.println(ship.getName() + "다 만들었습니다."); } }
마지막 과정인 notify역할을 하 sendEmailTo()메서드만 가져와서 ShipFactory 인터페이스의 메서드 orderShip()메서드 안에 넣어준다.
이렇게 interface에 orderShip()메서드를 정의 함으로써, 배를 만드는 공통과정은 interface에 정의 되었고, 특수과정은 외부 구현체에게 맡기는 ShipFactory interface 작성이 완료되었다.
ShipFactory interface를 구현(implements)하는 WhiteShipFactory class 작성하기
WhiteShipFactory.class
public class WhiteShipFactory implements ShipFactory{ @Override public Ship createShip() { return null; } }
고잉메리호와 써니호만 만들 것이라면 현재 정의된 Ship 클라스를 그대로 사용해서 인스턴스를 생성해도 되지만 팩토리 메소드(Factory Method)패턴1의 시나리오 처럼 Ship을 넘어서 배와 비행기의 역할을 모두하는 배를 만드는 공정과정을 대비하여 Ship역시 Ship interface를 만든 뒤 해당 interface의 구현체 클래스를 만든뒤 그 구현체의 클래스의 인스턴스를 생성할 수도 있다.
1. Ship class를 extends한 WhiteShip class 생성
public class WhiteShip extends Ship{ }
일단 Ship클래스를 extends한 WhiteShip 클래스를 만든다.
2. WhiteShip의 생성자 만든다.
WhiteShip.class
public class WhiteShip extends Ship{ public WhiteShip() { setName("whiteship"); setLogo("🛳️"); setColor("white"); } }
최초의 ShipFactory에서 if else 문으로 설정했던 배의 제조 공정들을 WhiteShip의 생성자에서 설정해준다.
3. new 생성자를 통해 WhiteShip 인스턴스를 생성한다.
public class WhiteShipFactory implements ShipFactory{ @Override public Ship createShip() { return new WhiteShip(); } }
이미 WhiteShip 클라스의 생성자에서 whiteship을 만드는데 특화된 공정을 모두하 필요한 일을 모두 해주었기 때문에 WhilteShipFactory에서는 new 생성자를 이용해 인스턴스를 생성해 주기만 하면된다.
4.interface 구현체(implements) class를 활용하여 메서드 불러오기
public class Client { public static void main(String[] args) { Client client = new Client(); Ship whiteship = new WhiteShipFactory().orderShip("Whiteship", "study3@mail.com"); System.out.println(whiteship); }
이제는 구체적인 클래스의 orderShip()메서드를 불러오는 것이아니라 사전에 정의된 interface 타입을 구현한(상속받은) class를 통해서 orderShip()메서드를 불러와야 한다.
다른 종류의 배를 만들때 OPC 원칙 지키기
써니호를 만들때 고잉메리호를 만들던 코드가 바뀌냐 안바뀌냐가 작성된 코드가 객체지향의 원칙 중 하나인 OPC(Opne Closed Principle:개방-폐쇄 원칙)를 충족하고 있는지 판가름 한다.
즉 위 코드가 변경에 닫혀있고 확장에 열려있는 구조라면 새로운 다른 종류의 배를 만드는 공정 즉 써니호를 만드는 공정을 추가할 것인데, 기존 코드가 변경이 되면 안된다.
1. BlackShip 클래스 추가
public class BlackShip extends Ship{ public BlackShip() { setName("blackShip"); setLogo("⚓"); setColor("black"); } }
우선 제품을 추가하기위해서 기존은 WhiteShip을 만드는 코드를 수정할 필요 없이 BlackShip.class만 추가하면 된다.
2.BlackShipFactory 클래 추가
public class BlackShipFactory implements ShipFactory { @Override public Ship createShip() { return new BlackShip(); } }
이렇게 하면 기존코드를 하나도 건드리지 않고 새로운 종류의 배를 만드는 공정을 추가할 수 있다. 즉 확장을 했고 기존 코드를 변경하지 않았다.
따라서 확장에 열려있고 변경에 닫혀있는 코드가 된 것이다.
3.interface 구현체(implements) class를 활용하여 메서드 불러오기
public class Client { public static void main(String[] args) { Client client = new Client(); Ship blackship = new BlackShipFactory().orderShip("Blackship", "study2@mail.com"); System.out.println(blackship); } }
하지만, Client 클래스의 코드가 바뀌었음으로 이것이 변경에 닫혀있는 코드라고 할 수있을까? 그래서 보통은 interface를 기반으로 하는 코드를 작성하고 구체적은 class의 경우는 의존성을 주입하는 방식으로 코드를 작성한다. 그럴 경우 Client 클래스의 코드도 최대한 변경하지 않은채 코드를 작성할 수 있다.
[출처 - https://www.inflearn.com/course/%EB%94%94%EC%9E%90%EC%9D%B8-%ED%8C%A8%ED%84%B4/dashboard]
'Web 개발 > 디자인 패턴' 카테고리의 다른 글
팩토리 메소드(Factory Method)패턴1 (0) 2023.06.25 싱클톤(Singleton)패턴 구현2 (0) 2023.06.17 싱글톤(Singleton)패턴1 (0) 2023.06.17 디자인 원칙 - 구현이 아닌 인터페이스에 대해 프로그래밍 (0) 2022.12.21 디자인 원칙 - 변화하는 내용을 캡슐화 (0) 2022.12.20