기획자의 요청이 들어와 개발자 민씨는 회사의 각종 정보를 시각화 해야 합니다.
이에 막대 모양의 차트를 제공하기로 결정했고, BarChart 클래스를 생성하였습니다.
public class BarChart {
public void render() {
System.out.println("BarChart.render");
}
}
이후 근무자, 재고, 매출을 관리하는 클래스에 데이터를 시각화 하는 rend() 메서드를 추가하였습니다. 이 메서드에는 방금 생성한 BarChart 객체가 사용됩니다.
// 근무자 관련 클래스
public class EmployeeManager {
public void rend() {
BarChart chart = new BarChart();
chart.render();
}
}
// 재고 관련 클래스
public class InventoryManager {
public void rend() {
BarChart chart = new BarChart();
chart.render();
}
}
// 매출 관련 클래스
public class SalesManager {
public void rend() {
BarChart chart = new BarChart();
chart.render();
}
}
며칠 뒤 기획이 수정되어 막대 차트를 선형 차트로 변경해 달라는 요청이 왔습니다.
이 전과 비슷하게 LineChart를 생성합니다.
public class LineChart {
public void render() {
System.out.println("LineChart.render");
}
}
이 후 rend() 메서드의 BarChart를 전부 LineChart로 변경해 줍니다.
public class EmployeeManager {
public void rend() {
LineChart chart = new LineChart();
chart.render();
}
}
public class InventoryManager {
public void rend() {
LineChart chart = new LineChart();
chart.render();
}
}
public class SalesManager {
public void rend() {
LineChart chart = new LineChart();
chart.render();
}
}
또다시 기획이 수정되어 선형 차트를 파이 차트로 변경해 달라는 요청이 왔습니다.
이번에도 PieChart를 생성하고 또 LineChart를 PieChart로 변경해야 할까요?
위 문제점을 팩토리 패턴을 이용하여 개선해 보겠습니다.
우선 차트를 나타내는 인터페이스 Chart를 생성합니다.
public interface Chart {
void render();
}
BarChart, LineChart, PieChart가 Chart 인터페이스를 구현하도록 변경합니다.
public class BarChart implements Chart {
public void render() {
System.out.println("BarChart.render");
}
}
public class LineChart implements Chart {
public void render() {
System.out.println("LineChart.render");
}
}
public class PieChart implements Chart {
public void render() {
System.out.println("PieChart.render");
}
}
각 매니저 클래스가 Chart 타입의 인스턴스를 직접 생성하지 않고, 클래스를 통해 생성된 인스턴스를 사용하도록 ChartFactory를 생성합니다.
public class ChartFactory {
public static Chart createChart() {
return new LineChart();
}
}
기존의 근무자, 재고, 매출을 관리하는 클래스들을 createChart() 클래스를 이용하여 리팩터링 해보겠습니다.
public class EmployeeManager {
public void rend() {
Chart chart = ChartFactory.createChart();
chart.render();
}
}
public class InventoryManager {
public void rend() {
Chart chart = ChartFactory.createChart();
chart.render();
}
}
public class SalesManager {
public void rend() {
Chart chart = ChartFactory.createChart();
chart.render();
}
}
변경된 코드는 Chart 클래스 생성 로직을 캡슐화 하고 있습니다.
이제 선형 차트를 파이 차트로 변경해 달라는 요청이 오면 어떻게 하면 될까요?
public class ChartFactory {
public static Chart createChart() {
// return new LineChart();
return new PieChart();
}
}
ChartFactory 클래스의 구현체를 LineChart에서 PieChart로 변경만 해주면 됩니다.
이 방법에도 아직 문제점이 있습니다.
이번에는 기획자가 매출은 막대 차트로, 근무자는 파이 차트로, 재고는 선형 차트로 변경해 달라는 요청이 왔습니다.
이는 ChartFactory를 변경하여 해결할 수 있습니다.
매니저 클래스가 필요한 차트에 따라서 ChartFactory가 해당하는 Chart 타입을 반환해 주면 되는데요.
이를 위해 ChartType의 Enum이 필요합니다.
public enum ChartType {
BAR, // 막대 차트
LINE, // 선형 차트
PIE // 파이 차트
}
이 후 ChartType에 알맞는 Chart를 반환하도록 createChart() 메서드를 오버로딩합니다.
public class ChartFactory {
public static Chart createChart() {
// return new LineChart();
return new PieChart();
}
public static Chart createChart(ChartType type) {
switch (type) {
case BAR -> {
return new BarChart();
}
case LINE -> {
return new LineChart();
}
case PIE -> {
return new PieChart();
}
default -> throw new IllegalArgumentException(type + "는 올바르지 않은 요청입니다.");
}
}
}
이제 필요에 따라 매니저 들을 수정해 주면됩니다.
매출 → 막대 차트
public class SalesManager {
public void rend() {
Chart chart = ChartFactory.createChart(ChartType.BAR); // 막대 차트
chart.render();
}
}
근무자 → 파이 차트
createChart()의 기본 반환이 파이 차트이므로 생략해도 상관없으나 명시적으로 넣어주겠습니다.
public class EmployeeManager {
public void rend() {
Chart chart = ChartFactory.createChart(ChartType.PIE); // 파이 차트
chart.render();
}
}
재고 → 선형 차트
public class InventoryManager {
public void rend() {
Chart chart = ChartFactory.createChart(ChartType.LINE); // 선형 차트
chart.render();
}
}
이번에는 기획자가 세로 막대 차트가 마음에 들지 않는다며, 가로 막대 차트로 변경해 달라는 요구사항이 왔습니다.
세로 막대 차트를 가로 막대 차트로 변경하려면 어떻게 하면 될까요?
먼저 가로 막대 차트 클래스 RowBarChart를 생성합니다.
public class RowBarChart implements Chart {
public void render() {
System.out.println("RowBarChart.render");
}
}
이제 ChartFactory의 BarChart를 RowBarChart로 변경해 주면 됩니다.
public static Chart createChart(ChartType type) {
switch (type) {
case BAR -> {
// return new BarChart();
return new RowBarChart();
}
case LINE -> {
return new LineChart();
}
case PIE -> {
return new PieChart();
}
default -> throw new IllegalArgumentException(type + "는 올바르지 않은 요청입니다.");
}
}
매니저 클래스는 변경하지 않고도 요구사항을 만족할 수 있게 되었습니다.
만약, 팩토리 패턴을 적용하지 않은 상태였다면 new BarChart()
코드를 전부 찾아서 new LowBarChart()
로 변경해야 했을 것입니다.
더 나아가서, 매출은 세로 막대, 관리자는 가로 막대로 보고 싶다는 요구 사항을 만나게 된다면 어떻게 하면 될까요?
가로 막대에 대한 Enum 타입을 추가하고 case문을 추가해 주면 됩니다.
public enum ChartType {
BAR, // 막대 차트
LINE, // 선형 차트
PIE, // 파이 차트
ROW_BAR // 가로 막대 차트
}
public static Chart createChart(ChartType type) {
switch (type) {
case BAR -> {
return new BarChart();
}
case LINE -> {
return new LineChart();
}
case PIE -> {
return new PieChart();
}
case ROW_BAR -> {
return new RowBarChart();
}
default -> throw new IllegalArgumentException(type + "는 올바르지 않은 요청입니다.");
}
}
팩토리 패턴을 적용함으로써 유연하게 객체를 생성하고, 추가적인 요청에 쉽게 대응할 수 있게 되었습니다.
팩토리 패턴에 대한 이해를 돕는 이 글을 작성하게 된 데는,
new BarChart()
CharFactory.createChart()
대체 팩토리에서 객체 생성하는게 뭐가 좋은거지? 라는 생각에서 시작을 했습니다.
이에 어느 정도 현실적인 시나리오를 가정해 생각을 정리해 보았더니 아래와 같은 결론이 도출 되었습니다.
팩토리 패턴의 본질은
입니다.
즉, 객체 생성을 담당하는 클래스를 생성하고 생성 로직을 클라이언트에서 분리함으로써, 클라이언트 코드는 구체적인 클래스의 타입을 몰라도 되며, 필요에 따라 다양한 객체를 유연하게 생성할 수 있는 것입니다.
이 글이 팩토리 패턴을 이해하는데 조금이나마 도움이 되었으면 좋겠습니다.
감사합니다.