- 디자인 패턴은 소프트웨어 개발에서 반복적으로 발생하는 문제를 해결하기 위한 일련의 해결책이나 가이드라인의 모음이다. 이러한 패턴은 개발자들이 공통된 문제에 직면했을 때 효과적이고 테스트된 해결책을 제공 하여 개발 과정을 향상 시키고 코드 의 유지 보수성과 재사용성을 높일 수 있도록 도와주는 패턴이다.
생성 패턴 : 객체의 생성과 초기화를 다루며, 객체 생성을 보다 유연하고 효율적으로 처리하는 방법을 제공한다. 대표적으로 '싱글톤 패턴' ,'팩토리 메서드 패턴' ,'추상 팩토리 패턴' 등이 있다.
구조 패턴 : 클래스와 객체들의 구성을 다루며, 객체들 사이의 관계를 개선하거나 복합 객체를 구성하는 방법을 제공한다. 대표적으로 ' 어댑터 패턴 ' , '데코레이터 패턴 ', ' 파사드 패턴' 등 이있다.
행위 패턴 : 객체들 사이의 동작 및 책임 분배를 다루며, 객체 간의 상호작용을 정의하는 방법이다. 대표적으로 ' 스트래티지 패턴', '옵저버 패턴', '커맨드 패턴' 등 이 있다.
싱글톤 패턴
- 싱글톤 패턴은 객체 생성을 제어하여 어플리케이션 전체에서 하나의 인스턴스만 존재하도록 보장하는 디자인 패턴이다. 이 패턴은 주로 리소스공유, 중복 생성 방지, 설정 정보 관리 등의 목적으로 사용된다.
특징
1. 단일 인스턴스 : 싱글톤 패턴은 클래스의 인스턴스가 하나만 생성 되도록 보장한다. 어떤 이유로든 여러 개의 인스턴스 생성을 방지 할 수 있습니다.
2. 전연적인 접근성 : 싱글톤 인스턴스는 어플리케이션 전역에서 접근 가능하다. 이를 통해 어디서든지 동일한 인스턴스를 사용 할 수 있다.
3. 지연 로딩 : 인스턴스가 필요한 시점에 생성됩니다. 이를 통해 자원의 낭비를 최소화 하고, 초기 부하를 줄일 수 있다.
4. 인스턴스 공유 : 여러 부분에서 같은 인스턴스를 사용하므로, 리소스를 공유하거나 중복 생성을 피할 수 있다.
5. 스레드 안정성 : 싱글톤 인스턴스를 스레드 안전하게 구현하면 여러 스레드에서 동시 접근 해도 문제가 발생하지 않습니다.
자바 싱글턴 패턴 코드
public class Coin {
private static final int ADD_MORE_COIN = 10;
private int coin;
private static Coin instance = new Coin(); // eagerly loads the singleton
private Coin() {
// private to prevent anyone else from instantiating
}
public static Coin getInstance() {
return instance;
}
public int getCoin() {
return coin;
}
public void addMoreCoin() {
coin += ADD_MORE_COIN;
}
public void deductCoin() {
coin--;
}
}
public class Singleton {
private static final Singleton INSTANCE = new Singleton();
private Singleton() {}
public static Singleton getInstance() {
return INSTANCE;
}
}
설명
Instance 는 싱글톤 클래스의 인스턴스 이며 클래스 로딩시 즉시 초기화 되어 단일 인스턴스를 보장 하게 해준다.
생성자 (' private singleton()') : 외부에서 인스턴스 생성을 방지하기 위해 private 로 선언해야한다.
'getInstance()' : 싱글톤 인스턴스를 반환하는 정적 메서드 이다.
빌더 패턴
- 빌더패턴은 객체 생성 과정을 분리하여 복잡한 객체를 생성하는 디자인 패턴 중 하나이다. 주로 많은 매개변수를 가지고 있는 복잡한 객체를 생성 할 때 사용되며, 가독성이 좋고 유연한 코드 작성을 도와준다.
특징
1. 객체 생성 과정 분리 : 빌더 패턴은 객체 생성 과정을 객체의 표현과 분리하여, 클라이언트 코드에서 객체 생성과정을 명확하게 분리 할 수 있다. 이렇게 하면 복잡한 객체 생성 로직이 클라이언트 코드에 노출되지않는다.
2. 가독성 향상 : 생성자에 매개 변수가 많을 경우 , 이를 순차적으로 전달하는 것은 가독성을 저해 할 수 있으므로 빌더 패턴의 메서드 체이닝을 통해 보다 가독성을 향상 시킬수 있다.
3. 선택적 매개변수 지원 : 객체의 일부 매개변수를 선택적으로 지정 할 수 있도록 도와 줍니다. 빌더 패턴을 사용하면 필요한 매개변수만 설정하고 나머지는 기본값으로 초기화가 가능하다.
4. 불변성 보장 : 빌더 패턴을 사용하면 객체의 생성 후에도 변경이 불가능 하도록 불변성을 보장 할 수 있다. 이는 스레드 안전성과 데이터 무결성을 강화하는데 도움이 된다.
5. 유연한 객체 생성 : 빌더 패턴을 활용하면 서로 다른 설정 옵션을 가진 여러 종류의 객체를 생성 할 수 있다. 이는 같은 빌더 인터페이스를 사용하여 다양한 형태의 객체를 생성 할 수 있음을 의미 한다.
* 빌더 패턴은 자바 Spring boot 에서 Lombok을 사용하여 @Builder 어노테이션으로 간단하게 구현 할 수 있다.
프록시 패턴
- 프록시 패턴은 객체 지향 디자인 패턴 중 하나로, 다른 객체에 대한 대리자 또는 대변 역할을 수행하는 객체를 제공하여 객체 간의 간접적인 접근을 제어하거나 추가적인 동작을 수행할 수 있도록 하는 패턴이다. 주로 객체의 생성 비용이 높거나, 객체에 접근 제어, 객체의 캐싱 , 객체의 변경을 추적 하기 위해 사용된다.
특징
1. 간접 접근 제공 : 프록시는 실제 객체와 동일한 인터페이스를 구현하며, 클라이언트는 프록시를 통해 실제 객체에 간접적으로 접근 한다. 이를 통해 실제 객체의 내부동작을 클라이언트에 노출 시키지 않을 수 있다.
2. 클라이언트와 실제 객체 간의 느슨한 결합 : 클라이언트가 프록시를 통해 객체의 접근하므로, 클라이언트와 실제 객체 간의 의존성이 줄어든다. 실제 객체의 변경이나 대체가 필요한 경우에도 클라이언트 코드의 수정을 최소화 할 수 있다.
3. 지연 초기화 및 비용 절감 : 프록시는 실제 객체의 생성과 초기화를 지연 시킬 수 있다. 실제 객체의 생성 비용이 높을 때나 객체가 실제로 필요한 시점에 생성할 때 유용하다
4. 보안 및 접근 제어 : 프록시를 사용하여 실제 객체에 대한 접근을 제어하거나 보안 검사를 수행 할 수 있다 . 클라이언트가 직접 객체에 접근할 필요 없이 프록시가 접근을 제어 할 수있다.
5. 캐싱 : 프록시는 실제 객체의 결과를 캐싱하여 동일한 요청에 대한 반복 계산을 피할 수 있어 성능 향상을 도모 할 수 잇다.
자바 프록시 패턴 코드
import java.util.*;
interface Image {
public void displayImage();
}
//on System A
class RealImage implements Image {
private String filename;
public RealImage(String filename) {
this.filename = filename;
loadImageFromDisk();
}
private void loadImageFromDisk() {
System.out.println("Loading " + filename);
}
@Override
public void displayImage() {
System.out.println("Displaying " + filename);
}
}
//on System B
class ProxyImage implements Image {
private String filename;
private Image image;
public ProxyImage(String filename) {
this.filename = filename;
}
@Override
public void displayImage() {
if (image == null)
image = new RealImage(filename);
image.displayImage();
}
}
class ProxyExample {
public static void main(String[] args) {
Image image1 = new ProxyImage("HiRes_10MB_Photo1");
Image image2 = new ProxyImage("HiRes_10MB_Photo2");
image1.displayImage(); // loading necessary
image2.displayImage(); // loading necessary
}
}
설명
RealImage 클래스 는 실제 이미지를 로딩하고 표시하는 역할을 수행
ProxyImage 클래스 는 이미지 로딩 도중에 로딩 메시지를 표시하고 실제 이미지를 로딩을 지연시키는 역할
main 클래스에서 ProxyImage 클래스를 거쳐 이미지를 로딩하고 표시 후 이미지를 처음 로딩 할때 실제 이미지를 로딩 한다. 이미지를 다시 로딩 할때는 프록시 객체가 이미지를 생성한후 실제 이미지를 로딩 하지 않고 이전에 로딩한 이미지를 사용하게 된다.