0. 시작하며
생성 디자인 패턴 중 하나인 팩토리 메서드 패턴을 알아 보겠습니다. 이 패턴을 공부하며 느낀점은 객체 지향 원칙 중 OCP를 위해 만들어진게 아닌가 싶을 정도로 정확하게 해당 원칙을 지킬 수 있도록 해주었습니다. 기존 코드에서 팩토리 메서드 패턴을 적용해가는 과정을 작성하며 함께 보도록 하겠습니다.
1. 기존 코드
Client 코드
public class Client {
public static void main(String[] args) {
Phone iPhone = PhoneFactory.order("iPhone", "nrz@mail.com");
System.out.println(iPhone);
Phone galaxy = PhoneFactory.order("galaxy", "nrz@mail.com");
System.out.println(galaxy);
}
}
Phone 객체
public class Phone {
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;
}
@Override
public String toString() {
return "Ship{" +
"name='" + name + '\'' +
", color='" + color + '\'' +
", logo='" + logo + '\'' +
'}';
}
}
PhoneFactory
public class PhoneFactory{
public static Phone order(String name, String email) {
// validate
if (name == null || name.isBlank()) {
throw new IllegalArgumentException("핸드폰 이름을 지어주세요.");
}
if (email == null || email.isBlank()) {
throw new IllegalArgumentException("연락처를 남겨주세요.");
}
prepareFor(name);
Phone phone = new Phone();
phone.setName(name);
// Customizing for specific name
if (name.equalsIgnoreCase("iPhone")) {
phone.setLogo("🍎");
} else if (name.equalsIgnoreCase("galaxy")) {
phone.setLogo("✨");
}
// coloring
if (name.equalsIgnoreCase("iPhone")) {
phone.setColor("space_gray");
} else if (name.equalsIgnoreCase("galaxy")) {
phone.setColor("black");
}
// notify
sendEmailTo(email, phone);
return phone ;
}
private static void prepareFor(String name) {
System.out.println(name + " 만들 준비 중");
}
private static void sendEmailTo(String email, Ship ship) {
System.out.println(ship.getName() + " 다 만들었습니다.");
}
}
우리는 핸드폰을 만드는 공장의 개발자라고 하겠습니다. 원래 갤럭시만 만들 수 있었는데 엄청난 영업을 통해 아이폰도 만들어낼 수 있게 되었고, 추후 화웨이나 다른 스마트폰 기기도 만들지도 모르는 상황에 놓여있는 우리는 다음과 같은 코드에서 변경이 필요하다고 느낄 수 있습니다.
더 많은 물건을 만들 수 있게 될 수록 위 factory 코드의 if분기는 더욱 많아지고 복잡해질 수 밖에 없습니다. 즉 매번 기존 코드를 변경해야하므로 야근하는 날이 많아질것입니다. 이때 factory 메소드 패턴을 통해 추상화하고 기존 코드의 변경없이 새로운 객체를 다양한 방법으로 확장해줄 수 있습니다.
2. 팩토리 메서드 패턴
Client 코드
public class Client {
public static void main(String[] args) {
Phone iPhone = new IPhoneFactory().order("iPhone", "nrz@mail.com");
System.out.println(iPhone);
Phone galaxy = new GalaxyFactory().order("galaxy", "nrz@mail.com");
System.out.println(galaxy);
}
}
Phone 객체
public class Phone {
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;
}
@Override
public String toString() {
return "Ship{" +
"name='" + name + '\'' +
", color='" + color + '\'' +
", logo='" + logo + '\'' +
'}';
}
}
Factory Interface
public interface PhoneFactory {
default Phone order(String name, String email){
validate(name, email);
prepareFor(name);
Phone phone = createPhone();
sendEmailTo(email, phone);
return phone;
}
Phone createPhone();
private void validate(String name, String email){
if (name == null || name.isBlank()) {
throw new IllegalArgumentException("핸드폰 이름을 지어주세요.");
}
if (email == null || email.isBlank()) {
throw new IllegalArgumentException("연락처를 남겨주세요.");
}
}
private void prepareFor(String name) {
System.out.println(name + " 만들 준비 중");
}
private void sendEmailTo(String email, Phone phone) {
System.out.println(phone.getName() + " 다 만들었습니다.");
}
}
이처럼 Java 8부터 생겨난 default 메서드와 private 를 사용하여 구현체를 만들 수 있습니다. 이를통해 공통적으로 구현하는 부분은 Interface에서 구현할 수 있고 제품의 특징에따라 달라지는 부분은 해당 객체에서 만들어서 가져올 수 있습니다.
Galaxy
public class Galaxy extends Phone{
public Galaxy(){
setName("galaxy");
setColor("black");
setLogo("✨");
}
}
public class GalaxyFactory implements PhoneFactory{
@Override
public Phone createPhone() {
return new Galaxy();
}
}
iPhone
public class IPhone extends Phone{
public IPhone(){
setName("iPhone");
setColor("space_gray");
setLogo("🍎");
}
}
public class IPhoneFactory implements PhoneFactory{
@Override
public Phone createPhone() {
return new IPhone();
}
}
하지만 자세히 보신분은 아시겠지만 Client 코드에 변경이 일어났으므로 OCP원칙을 지키지 못했습니다. 클라이언트 코드에서 인터페이스를 적용하면 해당 변경을 최소화할 수 있습니다.
public class Client {
public static void main(String[] args) {
Client client = new Client();
client.buy(new GalaxyFactory(),"galaxy", "nrz@mail.com");
client.buy(new IPhoneFactory(), "iPhone", "nrz@mail.com");
}
private void buy(PhoneFactory phoneFactory, String name, String email){
System.out.println(phoneFactory.order(name,email));
}
}
Client내부에서 인터페이스를 인자로 받는 메서드를 만들고 각 상품에 맞는 팩토리를 인자로 주는 형태로 변경을 최소화할 수 있습니다.
3. 팩토리 메서드 패턴 장점과 단점
장점
- OCP를 잘 지킬 수 있다( 즉, 기존 코드를 건드리지 않고 새로운 인스턴스생성을 다른 방법으로 확장이 가능하다)
- Factory 코드의 가독성이 좋아진다
- 유지보수하기 용이하다
단점
- 클래스가 많아짐
- 개발자가 구채적인 구현 로직을 보려면 한번 더 들어가야함
지금까지 OCP를 강력하게 지킬 수 있게해주는 생성 패턴 Factory Method 패턴을 알아보았습니다. 위와 같은 장, 단점을 고려해볼때 정말 확장할 가능성이 없는 경우라면 굳이 적용할 필요는 없는거같습니다. 하지만 서비스 확장성을 염두해두고 개발을 해야한다면 장점이 많은 패턴이라고 생각됩니다.
4. 레퍼런스
https://www.inflearn.com/course/%EB%94%94%EC%9E%90%EC%9D%B8-%ED%8C%A8%ED%84%B4
코딩으로 학습하는 GoF의 디자인 패턴 강의 - 인프런
디자인 패턴을 알고 있다면 스프링 뿐 아니라 여러 다양한 기술 및 프로그래밍 언어도 보다 쉽게 학습할 수 있습니다. 또한, 보다 유연하고 재사용성이 뛰어난 객체 지향 소프트웨어를 개발할
www.inflearn.com
'Java' 카테고리의 다른 글
[JAVA] Builder 패턴을 알아보자 (0) | 2024.03.24 |
---|---|
[Java] ConcurrentHashMap사용하기 (0) | 2023.12.24 |
[Java] CompletableFuture 사용하기 (0) | 2023.12.24 |
[Java] 디자인 패턴과 싱글톤 (0) | 2023.09.06 |
객체 지향 프로그래밍 (0) | 2023.08.30 |