본문 바로가기
JAVA

싱글톤 패턴 , 빌더 패턴 , 프록시 패턴 의 특징 은?

by ddahu 2023. 8. 8.

디자인 패턴

 

- 디자인 패턴은 소프트웨어 개발에서 반복적으로 발생하는 문제를 해결하기 위한 일련의 해결책이나 가이드라인의 모음이다. 이러한 패턴은 개발자들이 공통된 문제에 직면했을 때 효과적이고 테스트된 해결책을 제공 하여 개발 과정을 향상 시키고 코드 의 유지 보수성과 재사용성을 높일 수 있도록 도와주는 패턴이다.

 


 

  • 생성 패턴 : 객체의 생성과 초기화를 다루며, 객체 생성을 보다 유연하고 효율적으로 처리하는 방법을 제공한다. 대표적으로 '싱글톤 패턴' ,'팩토리 메서드 패턴' ,'추상 팩토리 패턴' 등이 있다.
  • 구조 패턴 : 클래스와 객체들의 구성을 다루며, 객체들 사이의 관계를 개선하거나 복합 객체를 구성하는 방법을 제공한다. 대표적으로 ' 어댑터 패턴 ' , '데코레이터 패턴 ', ' 파사드 패턴' 등 이있다.
  • 행위 패턴 : 객체들 사이의 동작 및 책임 분배를 다루며, 객체 간의 상호작용을 정의하는 방법이다. 대표적으로 ' 스트래티지 패턴', '옵저버 패턴', '커맨드 패턴' 등 이 있다.

 


싱글톤 패턴



- 싱글톤 패턴은 객체 생성을 제어하여 어플리케이션 전체에서 하나의 인스턴스만 존재하도록 보장하는 디자인 패턴이다. 이 패턴은 주로 리소스공유, 중복 생성 방지, 설정 정보 관리 등의 목적으로 사용된다.

 

특징

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 클래스를 거쳐 이미지를 로딩하고 표시 후 이미지를 처음 로딩 할때 실제 이미지를 로딩 한다. 이미지를 다시 로딩 할때는 프록시 객체가 이미지를 생성한후 실제 이미지를 로딩 하지 않고 이전에 로딩한 이미지를 사용하게 된다.

참고

싱글톤패턴
빌더 패턴
프록시 패턴