ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [CS] Design Pattern
    Computer Science/CS 2024. 3. 20. 16:03
    728x90

    디자인 패턴

     

    디자인 패턴은 개발하면서 발생하는 반복적인 문제들을 어떻게 해결할 것인지에 대한 해결방안으로 실제 현업에서 비즈니스 요구 사항을 프로그래밍으로 처리하면서 만들어진 다양한 해결책 중에서 많은 사람들이 인정한 모범 사례(Best Practive)다.

     

    디자인 패턴의 장점

    • 재사용성 : 반복적인 문제에 대한 일반적인 해결책을 제공하므로, 이를 재사용하여 유사한 상황에서 코드를 더 쉽게 작성할 수 있다.
    • 가독성 : 일정한 구조로 정리하고 명확하게 작성하여 개발자가 코드를 이해하고 유지보수하기 쉽게 만든다.
    • 유지보수성 : 코드를 쉽게 모듈화 할 수 있으며, 변경이 필요한 경우 해당 모듈만 수정하여 유지보수가 쉬워진다.
    • 확장성 : 새로운 기능을 추가하거나 변경할 때 디자인 패턴을 활용하여 기존 코드를 변경하지 않고도 새로운 기능을 통합할 수 있다.
    • 안정성과 신뢰성 : 수많은 사람들이 인정한 모범 사례로 검증된 솔루션을 제공한다

     

    디자인 패턴은 용도에 따라 3가지로 나눌 수 있다.

    1. 생성 패턴

        : 싱글톤 패턴, 추상 팩토리, 팩토리 메소드

    2. 행동 패턴

        : 옵저버, 전략, 템플릿 메소드, 반복자

    3. 구조 패턴

        : 컴포지트, 프록시, 어댑터, 

     

    1. 생성 패턴

    Creational Pattern

    객체 인스턴스를 생성하는 패턴으로, 클라이언트와 그 클라이언트가 생성해야 하는 객체 인스턴스 사이의 연결을 끊어주는 패턴


    싱글톤 패턴(Singleton Pattern)

    생성자가 여러차례 호출되어도 실제로 생성되는 객체는 하나이다.

    최초 생성 이후에 호출된 생성자는 최초의 생성자가 생성한 객체를 리턴한다.

    클래스의 인스턴스가 딱 1개만 생성되는 것을 보장하는 디자인 패턴이다.

     

    프로그램 시작부터 종료시까지 어떤 클래스의 인스턴스가 메모리 상에 단 하나만 존재할 수 있게 하고, 이 인스턴스에 대해 어디에서나 접근할 수 있도록 하는 패턴이다.

     

    사용하는 이유?

    우리가 만들었던 DI컨테이너에 요청을 할 때마다 새로운 객체를 생성한다.

    요청이 많은 트래픽 사이트에서는 계속 객체를 생성하게 되면 메모리 낭비가 심해지기 때문이다.

     

    스프링에서 사용하는 패턴

    스프링 컨테이너는 싱글톤 패턴을 적용하지 않아도 객체 인스턴스를 싱글톤으로 관리한다.

    이러한 기능 덕분에 싱글톤 패턴의 모든 단점을 해결하고 객체를 싱글톤으로 유지할 수 있다.

    스프링에서 싱글톤 관련 코드를 작성하지 않아도 스프링 빈에서 객체를 1개 설정한다.

    스프링 빈이 싱글톤 패턴으로 관리되는 빈이다.

     

    ▷구현

    (다양한 방법이 있다)

    공통적으로 가지는 특징이 있다.

    - private생성자만을 정의해 외부 클래스로부터 인스턴스 생성을 차단한다.

    - 싱글톤을 구현하고자 하는 클래스 내부에 멤버 변수로써 private static객체 변수를 만든다.

    - public static메소드를 통해 외부에서 싱글톤 인스턴스에 접근할 수 있도록 접점을 제공한다.

     

    싱글톤을 구현하는 6가지 방법이있다.

     

    (1) Eager Initialization

    static을 통해 해당 클래스를 Class Loader가 로딩할 때 객체를 생성해 준다.

    public class Singleton {
        private static Singleton singleton = new Singleton(); //static을 통해 class가 로드될 때 객체 생성
        private Singleton(){}
        public static Singleton getInstance(){ //함수를 통해 객체 접근
            return singleton;
        }
    }

    하지만 이 방법은 객체를 사용하지 않더라도 객체가 무조건 생성되기 때문에 자원낭비가 될 수 있다는 단점이 있다.

    또한, Exception에 대한 처리를 하지 않는다.

     

    (2) Static Block Initialization

    1번 방식에서 Static Block을 사용하여 Exception처리하는 코드가 추가된 방법이다.

    package org.example.DesignPattern.Singleton;
    
    public class Singleton {
        private static Singleton singleton;
        private Singleton(){}
        static { //static block을 통해 클래스가 처음 로딩될 때 객체를 생성
            try{
                singleton = new Singleton();
            }
            catch (Exception e){
                throw new RuntimeException("Exception occured in creating singleton instance");
            }
        }
        public static Singleton getInstance(){
            return singleton;
        }
    }

    static block은 초기화 블록(Initialization block)이라고 불리며 클래스가 처음 로딩될 때 한번만 수행되는 블록을 의미한다. 

    하지만 이 방법도 클래스 로딩 단계에서 객체를 생성하기 때문에 자원의 비효율성을 해결할 수 없다.

     

    (3) Lazy Initialization

    static으로 선언된 getInstance() 메서드를 통해 객체를 생성해 주는 방법이다.

    public class Singleton {
        private static Singleton singleton;
        private Singleton(){}
        public static Singleton getInstance(){
        //객체가 존재하지 않으면 생성해주고 존재하면 기존 객체를 반환
            if(singleton == null){
                singleton = new Singleton();
            }
            return singleton;
        }
    }

    getInstance()메서드를 호출하여 객체가 존재하지 않으면 새로운 객체를 하나 생성해주고, 존재하면 기존 객체를 반환해준다.

    이 방법은 1,2단계의 문제점인 자원의 비효율성을 해결해 줄 수 있지만 "동기화" 문제가 발생한다.

    만약 한 번에 여러 곳에서 getInstance()메서드를 호출한다면 여러 개의 객체가 생성될 수 있다.

     

    즉, 이 방법은 single-thread환경에서는 괜찮은 방법이지만, multi-thread환경에서는 동기화 문제가 발생할 수 있다.

     

    (4-1) Thread Safe Singleton

    동기화 문제를 해결하기 위해 Java키워드인 synchronized를 추가한다.

    public class Singleton {
        private static Singleton singleton;
        private Singleton(){}
        //동기화 문제를 해결하기위해 synchronized 키워드 추가
        public static synchronized Singleton getInstance(){
            if(singleton == null){
                singleton = new Singleton();
            }
            return singleton;
        }
    }

    synchronized키워드를 사용하면 어떤 한순간에는 하나의 스레드 만이 임계 영역(Critical Section)안에서 실행하는 것이 보장된다.

    multi-thread환경에서도 안전하게 동작하는 것을 보장해 준다.

     

    하지만 이 방법도 문제가 존재한다.

    synchronized를 사용하는 비용은 저렴한 편은 아니다.

    우리는 해당 객체를 안전하게 한 번 생성하기 위해 synchronized를 사용하는 것인데 이 방법은 해당 객체를 생성한 후 접근할 때에도 계속해서 synchronized를 호출하게 된다.

     

    즉, 싱글톤 객체를 자주 사용해야 한다면 synchronized가 자주 호출되면 많은 비용이 발생하게 되고 이에 따른 성능 저하가 발생하게 된다.

     

    (4-2) Double Checked Locking

    메서드에 synchronized를 붙이지 않고 메서드 내부에 synchronized를 사용하여 두번의 검사를 통해 싱글톤 객체를 생성 및 반환하는 방법이다.

    public class Singleton {
        private static Singleton singleton;
        private Singleton(){}
        public static Singleton getInstance(){
          //synchronized( 인스턴스 변수 및 클래스 타입)
          //특정 영역만 동기화, 메서드 영역보다 범위가 작음
            synchronized (Singleton.class){
                if(singleton == null){
                    singleton = new Singleton();
                }
            }
            return singleton;
        }
    }

    객체가 null일 경우에만 synchronized가 실행되도록 하여 객체가 생성된 후에는 synchronized가 실행되지 않는다.

    즉, 무분별한 synchronized 호출의 비용을 절약할 수 있다.

     

    (5) Bill Pugh Singleton Implementation

    Inner Static Helper Class를 사용하는 방식으로 현재 가장 널리 사용되고 있는 싱글톤 패턴 구현 방법이다.

    public class Singleton {
        private Singleton(){}
        private static class SingletonHelper{
            private static final Singleton SINGLETON = new Singleton();
        }
        public static Singleton getInstance(){
            return SingletonHelper.SINGLETON;
        }
    }

    SingletonHelper클래스는 Inner Class로 선언되어 있기 때문에 Singleton클래스가 Class Loader에 의해 로딩될 때 로딩되지 않다가 getInstance()가 호출될 때 JVM메모리에 로드되고 객체를 생성하게 된다.

    또한 클래스가 로드될 때 객체가 생성되기 때문에 multi-thread환경에서도 안전하게 사용이 가능하다.

     

    하지만 이 방법도 Java의 Reflection을 사용하면 private 생성자, 메서드에 접근 가능해지면 단 하나의 객체라는 조건을 깨뜨린다.

     

    (6) Enum Singleton

    Enum클래스를 사용하여 싱글톤 패턴을 구현하는 방법이다.

    public enum EnumSingleton {
        INSTANCE;
        public static void doSomething(){
            //do something
        }
    }

    동기화, Reflection의 문제를 하지만 Eager, Static Block Initialization방식처럼 Lazy Loading이 아니기 때문에 자원의 비효율성을 해결해주지 못하는 단점이 존재한다.


     

     

    2. 행동 패턴

    Behavioral Pattern

    클래스와 객체들이 상호작용하는 방법과 역할을 분담하는 방법을 다루는 패턴

     

     

    3. 구조 패턴

    Structural Pattern

    클래스와 객체를 더 큰 구조로 만들 수 있게 구성을 사용하는 패턴

     

     

     

     

     

    참고

    https://m.hanbit.co.kr/channel/category/category_view.html?cms_code=CMS8616098823

    https://sorjfkrh5078.tistory.com/108

    https://readystory.tistory.com/116

     

     

     

    728x90

    댓글

© 2022. code-space ALL RIGHTS RESERVED.