디자인 패턴이란?
- 소프트웨어 개발 과정에서 자주 등장하는 문제들을 효과적으로 해결하기 위해 정형화된 설계 방식
- 재사용성 향상 / 유지보수 용이 / 협업에 유리 / 유연한 설계등의 장점이 있다.
- GoF의 디자인 패턴에 등장하는 "생성", "구조", "행위" 패턴이 대표적이다.
생성패턴 : 생성 패턴은 인스턴스를 만드는 절차를 추상화하는 패턴
1. Singleton(싱글톤 패턴)
- 클래스의 인스턴스를 오직 하나만 생성해서 전역에서 공유하도록 보장하는 생성 패턴
- 스프링에서는 기본적으로 Bean이 싱글턴 스코프
- 최초 한번 new 연산자를 통해 고정된 메모리 영역을 사용하기에 해당 객체 접근 시 메모리 낭비를 방지
- 다른 객체와 강하게 결합될 수 있다는 단점, 테스트가 어려운 단점이 존재
public class Singleton {
private static final Singleton instance = new Singleton();
private Singleton() {} // 생성자 private, 외부에서 new 못함
public static Singleton getInstance() {
return instance;
}
}
2. Factory Method(팩토리 메서드 패턴)
- 코드를 별도의 팩토리 메소드로 분리하여, 상위 클래스에서 객체 생성 방식을 정의하고 하위 클래스에서 생성할 객체의 구체적인 클래스를 결정호도록하는 생성패턴
- 객체 생성 과정이 복잡하거나 변경될 가능성이 높은 경우 사용함.
- 느슨한 결합도를 유지하며, 유연한 객체 확장이 가능하다.
public interface Product {
void use();
}
public class ConcreteProductA implements Product {
@Override
public void use() {
System.out.println("A");
}
}
public class ConcreteProductB implements Product {
@Override
public void use() {
System.out.println("B");
}
}
public abstract class Creator {
public abstract Product factoryMethod(); // 서브 클래스에 위임
public void someOperation() {
Product product = factoryMethod(); // 생성 후 공통 로직 실행
product.use();
}
}
public class CreatorA extends Creator {
@Override
public Product factoryMethod() {
return new ConcreteProductA();
}
}
public class CreatorB extends Creator {
@Override
public Product factoryMethod() {
return new ConcreteProductB();
}
}
public class Main {
public static void main(String[] args) {
Creator creator = new CreatorA();
creator.someOperation();
}
}
3. Abstract Factory(추상 팩토리 패턴)
- 서로 관련있는 여러 객체를 만들어주는 인터페이스를 제공하는 패턴
- 서로 관련된 여러 개의 객체를 통일된 방식으로 생성하고 싶을 때 사용
- 제품과 일관성 유지 / 새로운 제품군 추가가 유연
- 구체 클래스에 의존하지 않고 인터페이스 설계 가능
public interface Chair {
void sit();
}
public interface Table {
void use();
}
public class ModernChair implements Chair {
public void sit() {
System.out.println("Modern Chair");
}
}
public class ModernTable implements Table {
public void use() {
System.out.println("Modern Table");
}
}
public class VictorianChair implements Chair {
public void sit() {
System.out.println("Victorian Chair");
}
}
public class VictorianTable implements Table {
public void use() {
System.out.println("Victorian Table");
}
}
public interface FurnitureFactory {
Chair createChair();
Table createTable();
}
public class ModernFurnitureFactory implements FurnitureFactory {
public Chair createChair() {
return new ModernChair();
}
public Table createTable() {
return new ModernTable();
}
}
public class VictorianFurnitureFactory implements FurnitureFactory {
public Chair createChair() {
return new VictorianChair();
}
public Table createTable() {
return new VictorianTable();
}
}
public class FurnitureClient {
private Chair chair;
private Table table;
public FurnitureClient(FurnitureFactory factory) {
this.chair = factory.createChair();
this.table = factory.createTable();
}
public void useFurniture() {
chair.sit();
table.use();
}
}
FurnitureFactory factory = new ModernFurnitureFactory();
FurnitureClient client = new FurnitureClient(factory);
client.useFurniture();
// 출력: Modern Chair, Modern Table
4. Builder(빌더 패턴)
- 복잡한 객체의 생성 과정을 분리해서, 같은 생성 절차에서 다양한 표현 결과를 만들 수 있게 해주는 생성 패턴
- 팩토리패턴이나 추상 팩토리 패턴에서 생성해야하는 클래스에 대한 속성 값이 많을 때 이슈가 있다.
- 클라이언트 프로그램으로부터 팩토리 클래스로 많은 파라미터를 넘겨줄 때 타입, 순서 등 관리가 어려워진다.
- 경우에 따라 필요 없는 파라미터들에 대해서 팩토리 클래스에 null 값을 넘겨줘야한다.
- 생성하는 sub class가 무거워지고 복잡해짐에 따라 팩토리 클래스 또한 복잡해진다.
public static void main(String[] args) {
User user = User.builder()
.name("Taegeun Song")
.age(28)
.job("Developer")
.address("incheon")
.build();
}
5. Prototype(프로토타입 패턴)
- 객체를 복제하여 새로운 객체를 생성하는 패턴으로, 기존 객체를 템플릿으로 사용하는 패턴
- 필요에 따라 복사한 객체를 수정한다.
package designpattern.prototype;
public abstract class Shape implements Cloneable {
private String type;
public void setType(String type) {
this.type = type;
}
public String getType() {
return type;
}
// clone 메서드 구현
public Shape clone() {
try {
return (Shape) super.clone(); // 얕은 복사
} catch (CloneNotSupportedException e) {
throw new RuntimeException(e);
}
}
}
package designpattern.prototype;
public class Circle extends Shape {
public Circle() {
this.setType("Circle");
}
}
package designpattern.prototype;
public class Rectangle extends Shape {
public Rectangle() {
this.setType("Rectangle");
}
}
package designpattern.prototype;
public class Prototype {
public static void main(String[] args) {
Shape circle = new Circle();
Shape rectangle = new Rectangle();
Shape clonedCircle = circle.clone();
Shape clonedRectangle = rectangle.clone();
// 주소 비교
System.out.println(circle == clonedCircle); // false면 복제 성공
System.out.println(rectangle == clonedRectangle); // false면 복제 성공
// 해시 출력
System.out.println(System.identityHashCode(circle));
System.out.println(System.identityHashCode(clonedCircle));
System.out.println(System.identityHashCode(rectangle));
System.out.println(System.identityHashCode(clonedRectangle));
// 필드 변경
clonedCircle.setType("cloend Circle");
clonedRectangle.setType("cloend Rectangle");
System.out.println(circle.getType());
System.out.println(clonedCircle.getType());
System.out.println(rectangle.getType());
System.out.println(clonedRectangle.getType());
}
}