디자인 패턴은 소프트웨어 과정에서 자주 발생하는 문제들에 대한 전형적인 해결책입니다.
사실 패턴에 대해 아무것도 알지 못해도 수년 동안 프로그래머로 일할 수 있습니다. 그리고 실제로 많은 개발자분들이 패턴에 대한 지식 없이 업무를 수행합니다. (저도 잘 모르고 일했습니다 ㅜ 흑반성....!!)
자신도 모르는 사이에 일부 패턴을 이미 구현하고 있을 수 있습니다. 흔히 웹개발 할 때 많은 프로젝트에 개발시 사용하는 Spring Framework도 수많은 디자인 패턴으로 만들어져 있기에 이미 디자인 패턴이 적용된 프레임워크를 개발자들은 사용중인 것이죠. 그렇다면 왜 패턴을 배워야 할까요?
- 디자인 패턴은 소프트웨어 디자인의 일반적인 문제들에 대해 시도되고 검증된 해결책 모음입니다. "바퀴를 다시 발명하지 마라"는 말이 있습니다. 이미 만들어져서 잘 되는 것을 처음부터 다시 만들 필요가 없다는 것입니다. 따라서 패턴을 알고 있는 개발자는 객체 지향 디자인의 원칙들을 사용해 많은 종류의 문제를 해결하는 방법들을 배울 수 있습니다.
- 팀원들이 더 효율적으로 의사소통하도록 공통 언어가 됩니다. 예를 들어 팀원들이 디자인 패턴을 알고 있다면 제가 누군가에게 업무 처리 시 '그 문제를 위해서는 그냥 싱글턴 사용하면 돼요.'라고 말하면 모두가 무엇을 뜻했는지 알 수 있을 것입니다.
크게 3가지 그룹으로 분류할 수 있습니다.
이번 포스팅은 1탄으로 생성 패턴에 대해 알아볼건데요.
그 중에서도 자주 쓰이는 패턴인 싱글톤, 팩토리 메소드, 빌더패턴 3가지에 대해 이번 포스팅에서 다루겠습니다.
스프링 기반의 웹개발 하다 보면 가장 많이 접하는 패턴이 싱글톤입니다. @Bean으로 관리되는 객체는 디폴트가 싱글톤 객체로 생성됩니다.
public class Singleton {
// 단 1개만 존재해야 하는 객체의 인스턴스로 static 으로 선언
private static Singleton instance;
// private 생성자로 외부에서 객체 생성을 막아야 한다.
private Singleton() {
}
// 외부에서는 getInstance() 로 instance 를 반환
public static Singleton getInstance() {
// instance 가 null 일 때만 생성
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
싱글톤 패턴의 기본적인 구현 방법은 다음과 같습니다.
먼저 private static으로 Singleton객체의 instance를 선언하고 getInstance()메서드가 처음 실행 될 때만 하나의 instance가 생성되고 그 후에는 이미 생성되어진 instance를 return하는 방식으로 진행이 됩니다.
여기서 핵심은 private로 된 기본 생성자입니다. 생성자를 private로 생성을 하며 외부에서 새로운 객체의 생성을 막아줘야 합니다.
// 같은 instance인지 Test
public class Application {
public static void main(String[] args) {
Singleton singleton1 = Singleton.getInstance();
Singleton singleton2 = Singleton.getInstance();
System.out.println(singleton1);
System.out.println(singleton2);
}
}
/** Output
* vendingmachine.Singleton@15db9742
* vendingmachine.Singleton@15db9742
**/
싱글톤 객체를 생성하는 위의 코드를 실행해보면 두 객체가 하나의 인스턴스를 사용하여 같은 주소 값을 출력하는 것을 확인하실 수 있습니다.
public interface Figure {
void draw();
}
public class Circle implements Figure {
@Override
public void draw() {
System.out.println("Circle의 draw 메소드");
}
}
public class Rectangle implements Figure {
@Override
public void draw() {
System.out.println("Rectangle의 draw 메소드");
}
}
public class Square implements Figure {
@Override
public void draw() {
System.out.println("Square의 draw 메소드");
}
}
public class FigureFactory {
public Figure getFigure(String figureType) {
if(figureType == null) {
return null;
}
if(figureType.equalsIgnoreCase("CIRCLE") {
return new Circle();
} else if (figureType.equalsIgnoreCase("RECTANGLE") {
return new Rectangle();
} else if (figureType.equalsIgnoreCase("SQUARE") {
return new Square();
}
return null;
}
}
public class FactoryPattern {
public static void main(String[] args) {
FigureFactory figureFactory = new FigureFactory();
Figure fig1 = figureFactory.getFigure("CIRCLE");
// Circle의 draw 메소드 호출
fig1.draw();
Figure fig2= figureFactory.getFigure("RECTANGLE");
// Rectangle의 draw 메소드 호출
fig2.draw();
Figure fig3 = figureFactory.getFigure("SQUARE");
// Square의 draw 메소드 호출
fig3.draw();
}
}
위의 코드에서 본 것 처럼 팩토리 메서드는 객체를 생성하는 인터페이스는 미리 정의하되, 인스턴스를 만들 클래스의 결정은 서브 클래스 쪽에서 내리는 패턴 입니다.
팩토피 메서드에서는 클래스의 인스턴스를 만드는 시점을 서브 클래스로 미룹니다.