디자인 패턴이란?
실무에서 개발을 하면서 가장 두려웠던 점 중 하나가 내가 짜고 있는 이 코드가 다른 개발자에게 어떻게 보일까 하는 부분이었던 것 같다. 이 코드가 과연 읽기 좋은 코드일까? 이 코드가 과연 1년 뒤, 2년 뒤 애플리케이션이 확장했을 때도 유연하게 확장될 수 있는 코드일까? 등을 고민하면서 내가 짜고 있는 코드의 질이 어떠한지 항상 두려워하고 고민했던 것 같다.
디자인 패턴은 이러한, 어쩌면 개발자들에게는 태생적이고 본질적일 수 밖에 없는 '잘 짜여진 코드일까'에 대한 불안감을 해소하기 위한 집단적인 노력의 결과물이라고 할 수 있다. 즉, 디자인 패턴은 잘 짜여진 코드가 무엇인가를 고민하고 이 패턴을 구조화한 집단적인 베스트 프렉티스인 것이다. 이러한 의미에서 디자인 패턴은 어쩌면 선대 개발자가 후대 개발자들에게 물려주는 '코드 백과사전'인지도 모르겠다.
생성 패턴이란?
생성 패턴은 디자인 패턴을 범주화해 나누어 낸 하위 패턴 단위 중 하나로 객체의 생성과 관련된 패턴을 일컫는다. 주로 객체를 만드는 절차를 추상화해 시스템으로부터 객체의 생성을 다른 객체의 작용으로부터 분리하기 위해 사용한다. 생성 패턴의 종류는 Factory, Factory Method, Abstract Factory, Builder, Singleton, Prototype 패턴 등이 있다.
팩토리 메소드 패턴이란?
팩토리 메소드 패턴은 디자인 패턴 중 하나로 범주 상 생성 패턴에 속하는 패턴이다. 객체의 생성 역할을 VO(Value Object) 외의 별도의 오브젝트에게 할당하여 VO와 VO의 생성을 분리시키는 디자인 패턴이다. 객체의 책임을 잘게 나누고 여러 개의 오브젝트에게 책임을 할당하기 때문에 객체지향 설계 원칙 중 단일책임원칙(SRP, Single Responsibility Principle)를 조금 더 쉽게 이행할 수 있다는 특징을 가지고 있다. 또한 팩토리 오브젝트와 VO 오브젝트가 인스턴스를 기반으로 동작하고 이를 구현하는 자식 클래스들을 병렬적으로 두게 함으로써 프로그램의 코드가 변경되었을 때, 혹은 변경되어야 하는 시점에 기존의 코드의 규칙을 변경하지 않을 수 있어 개방폐쇄원칙(OCP, Open Closed Principle)도 조금 더 쉽게 이행할 수 있다는 특징을 가지고 있다.
팩토리 메소드 패턴의 형태와 예시 코드
팩토리 메소드 패턴의 형태는 아래와 같다. 직접 손으로 그린 것이라 조금 형편 없기는 하지만 두 가지 부분만 유념하면 될 것 같다.
첫 번째, VO와 VO를 생성하는 Creator의 영역이 구분되어 있다. Creator가 바로 팩토리인데, 팩토리 메소드 패턴에 따르면 팩토리 클래스의 네이밍을 '팩토리'로 하는 것은 권장되지 않는다고 한다. 팩토리의 성격을 나타낼 수 있는 보다 구체적인 네이밍을 사용하는 것을 권장한다고 한다. (그러나 자바 라이브러리들을 보면 많은 팩토리 클래스들이 xxx 팩토리 등으로 끝나는 것을 볼 수 있다. xxx 팩토리 괜찮은 것 같은데)
두 번째, 각각의 역할을 정의해 놓은 인터페이스와 그 인터페이스를 구현한 구체적인 클래스가 나뉘어 있다. 이는 팩토리 메소드가 조금 더 유연하게 애플리케이션 내에서 동작할 수 있도록 한 것인데, 가령 배송을 담당하는 애플리케이션이 출시할 당시에는 트럭으로 인한 배송만 있었는데, 고객의 요구에 의해 선박 배송이 추가되었을 경우에 이렇게 인터페이스를 기반으로 설계를 해 놓았다면 기존의 코드에 손을 대지 않고도 유연하게 선박 배송 코드를 추가할 수 있을 것이다.
다음으로는 아주 간단한 예제 코드를 살펴보자. 예제에 나오는 클래스들이 많기 때문에 VO -> Factory -> 애플리케이션이 실행되는 메인 클래스 순서대로 코드를 살펴보도록 하겠다.
VO
Animal Interface
package designPattern.create.factoryMethod.ex2;
public interface Animal {
void eat();
void sleep();
}
Dog class implements Animal
package designPattern.create.factoryMethod.ex2;
public class Dog implements Animal {
@Override
public void eat() {
System.out.println("개가 밥을 먹는다. 냠냠 맛있다.");
}
@Override
public void sleep() {
System.out.println("개가 잠을 잔다.");
}
}
Cat class implements Animal
package designPattern.create.factoryMethod.ex2;
public class Cat implements Animal {
@Override
public void eat() {
System.out.println("고양이가 밥을 먹는다. 냠냠 맛있다.");
}
@Override
public void sleep() {
System.out.println("고양이가 잠을 잔다.");
}
}
Factory
AnimalEmulator abstract class
package designPattern.create.factoryMethod.ex2;
public abstract class AnimalEmulator {
abstract Animal create();
}
DogEmulator class extends AnimalEmulator
package designPattern.create.factoryMethod.ex2;
public class DogEmulator extends AnimalEmulator {
@Override
public Animal create() {
return new Dog();
}
}
CatEmulator class extends AnimalEmulator
package designPattern.create.factoryMethod.ex2;
public class CatEmulator extends AnimalEmulator {
@Override
public Animal create() {
return new Cat();
}
}
Main 클래스
package designPattern.create.factoryMethod.ex2;
import java.util.Scanner;
public class Main {
private static AnimalEmulator animalEmulator;
public static void main(String[] args) throws Exception {
Scanner in = new Scanner(System.in);
configure(in.nextLine());
Animal animal = animalEmulator.create();
if (animal instanceof Dog) {
System.out.println("강아지를 고르셨군요!");
} else if (animal instanceof Cat) {
System.out.println("고양이를 고르셨군요!");
}
animal.eat();
animal.sleep();
}
private static void configure(String command) throws Exception {
if (command == null || command.equals("")) throw new Exception("명령어를 입력해주세요");
if (command.equals("강아지 만들어줘")) {
animalEmulator = new DogEmulator();
} else if (command.equals("고양이 만들어줘")) {
animalEmulator = new CatEmulator();
} else {
throw new Exception("'강아지 만들어줘' 혹은 '고양이 만들어줘'의 명령어만 허용 가능합니다.");
}
}
}
위의 예시는 물론 어마어마하게 단순화한 예시이기 때문에 실제 실무에서 디자인 메소드 패턴을 이용하기 위해서는 조금 더 복잡한 예시와 조금 더 많은 연습이 필요할 수 있다. 다만 이 예제는 팩토리 메소드 패턴의 핵심을 설명하는 예제이기에 실제 실무를 진행하거나 실제 프로젝트를 할 때 "팩토리 디자인 패턴이 뭐더라"의 질문을 해소할 수 있는 가장 간소한 코드라고 생각했다.
마치며, 팩토리 메소드 패턴의 장단점
장점
- VO와 VO 생성의 강한 결합을 피할 수 있다. -> 애플리케이션이 보다 유연해질 수 있다.
- 단일 책임 원칙 : VO의 생성을 다른 오브젝트에 위임함으로써 단일 책임 원칙을 이행하는 것이 조금 더 유리하다.
- 개방 폐쇄 원칙 : 기존의 코드를 건드리지 않고 현재 존재하는 코드에 새로운 코드를 도입할 수 있다.
단점
- 팩토리 메소드 패턴은 인터페이스와 서브 클래스를 나누고, 팩토리 클래스를 생성하는 등 여러 개의 새로운 객체의 도입을필요로 한다. 따라서 코드가 팩토리 메소드 패턴을 사용하지 않을 때보다 훨씬 복잡해질 수 있다.
'JAVA' 카테고리의 다른 글
왜 JUnit을 써야 하는가? (feat. main 메소드로 테스트를 하는 부분의 단점) (0) | 2023.01.20 |
---|---|
java 리플렉션 적용해보기 (2) | 2022.11.25 |
[JPA] 연관관계 매핑 기초 (0) | 2022.05.21 |
[번역] cron4j quickstart (0) | 2022.04.01 |
Java의 날짜/시간 API. Date와 Calendar는 왜 사용하면 안될까? (0) | 2022.03.03 |