My Space

디자인 패턴

2021. 6. 9. 22:56
반응형

디자인 패턴이란?

- 소프트웨어를 설계할 때 특정 맥락에서 자주 발생하는 고질적인 문제들이 또 발생했을 때

   재사용 할 수 있는 해결책

- 이미 만들어져서 잘 되는 것은 다시 만드는게 아니라 가져다 쓰면 된다.

- 패턴은 공통의 언어를 만들어주며 팀원 사이의 의사 소통을 원활하게 해주는 중요한 역할

 

1. 싱글톤 패턴

- 클래스의 인스턴스를 하나만 생성하고, 어디서든 그 인스턴스를 참조할 수 있도록 하는 패턴

- 생성자가 여러 번 호출되더라도 실제로 생성되는 객체는 하나

 

사용이유

- 고정된 메모리 영역을 가지고 하나의 인스턴스만 사용하기 때문에 메모리 낭비 방지

- 싱글톤 클래스의 인스턴스는 전역이기 때문에 다른 클래스의 인스턴스들이 데이터를 

   공유하기 쉬움

- DBCP처럼 공통된 객체를 여러 개 생성해야 하는 상황에 많이 사용

 

여러가지 구현 방법

1. Eager initialization(이른 초기화)
필드에 인스턴스를 가지고 바로 초기화 하는 방식
장점: 빠르다
단점: 사용하지 않더라도 인스턴스가 항상 생성되 메모리를 잡아먹는다,
      예외 처리를 할 수 있는 방법이 없다.
public class EagerSingleton {
    private static EagerSingleton instance = new EagerSingleton();
    // 생성자
    private EagerSingleton() {
    }
    public static EagerSingleton getInstance() {
    	return instance;
    }
}

2. Static block initialization
  위의 Eager방식과 유사, 인스턴스가 static block 내에서 생성됨, 예외처리 가능
 
3. Lazy initialization
  인스턴스를 바로 초기화 하지 않고 원할때 인스턴스의 null 값을 체크해 사용
  Eager initialization의 단점을 보완
  하지만, 스레드 세이프 하지 않음(동시에 접근할 경우 여러개의 인스턴스 생성)
  
4. Thread safe initialization
   synchronized를 이용해 동시에 접근해도 하나의 스레드만 접근 가능하도록 설정
   하지만, 성능 저하를 야기하는 비효율적인 방법

5. Double-Checked Locking
   null 체크를 synchronized 블록 밖에서 한번, 안에서 한번 총 두 번 실행

6. Bill Pugh Solution (현재로서 가장 좋은 방법)
Double Checked에 비해 구현이 간단
Lazy Loading이 가능
Thread safe 만족
public class BillPughSingleton {
    // 생성자
    private BillPughSingleton() {
    }
    // inner class
    private static class SingletonHelper {
    	// 인스턴스 호출시 초기화 후 final로 인해 재할당이 되지 않는다.
    	private static final BillPughSingleton INSTANCE = new BillPughSingleton();
    }
    public static BillPughSingleton getInstance() {
    	return SingletonHelper.INSTANCE;
    }
}
   

 

 

  싱글톤 정적 클래스(static)
원리 하나의 인스턴스를 생성하여 재사용 인스턴스 생성 X
인터페이스 구현 가능 불가능
Override 가능 불가능
Load 필요에 따라 lazy 가능 Static binding으로 빠르게 로딩
OOP O X

 

싱글톤 패턴 사용 예제

//싱글톤 패턴이 아니라 객체 생성시 서로다른 객체들이 만들어짐
@Test
@DisplayName("스프링 없는 순수한 DI 컨테이너")
void pureContainer() {
        AppConfig appConfig = new AppConfig();
        //1. 조회: 호출할 때 마다 객체를 생성
        MemberService memberService1 = appConfig.memberService();

        //2. 조회: 호출할 때 마다 객체를 생성
        MemberService memberService2 = appConfig.memberService();

        //참조값이 다른 것을 확인
        System.out.println("memberService1 = " + memberService1);
        System.out.println("memberService2 = " + memberService2);

        //memberService1 != memberService2
        Assertions.assertThat(memberService1).isNotSameAs(memberService2);
}

//싱글톤
public class SingletonService {
    //1. static 영역에 객체를 딱 1개만 생성
    private static final SingletonService instance = new SingletonService();

    //2. public으로 열어서 객체 인스턴스가 필요하면 이 static 메서드를 통해서만 조회하도록 허용
    public static SingletonService getInstance() {
        return instance;
    }

    //3. 생성자를 private으로 선언해서 외부에서 new 키워드를 사용한 객체 생성을 못하게 막는다.
    private SingletonService() {

    }

    public void logic() {
        System.out.println("싱글톤 객체 로직 호출");
    }
}
//싱글톤 테스트
@Test
@DisplayName("싱글톤 패턴을 적용한 객체 사용")
void singletonServiceTest() {
        SingletonService instance1 = SingletonService.getInstance();
        SingletonService instance2 = SingletonService.getInstance();

        System.out.println("instance1 = " + instance1);
        System.out.println("instance2 = " + instance2);

        Assertions.assertThat(instance1).isSameAs(instance2);
}

설명

SingletonService에서 static영역에 instance 객체를 하나만 생성해 놓고,

getInstance()를 통해서만 객체를 가져다 쓰기 때문에 어디서 사용하든 참조값이 같다.

 

스프링 컨테이너를 사용하면 기본적으로 객체를 모두 싱글톤으로 만들어서 관리해준다.

 

싱글톤 패턴 문제점(스프링 컨테이너를 이용하면 해결)

  • 싱글톤 패턴을 구현하는 코드 자체가 많이 들어간다
  • 의존관계상 클라이언트가 구체 클래스에 의존한다. (DIP를 위반한다.)
  • 클라이언트가 구체 클래스에 의존해서 OCP 원칙을 위반할 가능성이 높다.
  • 테스트하기 어렵다.
  • 내부 속성을 변경하거나 초기화 하기 어렵다.
  • private 생성자로 자식 클래스를 만들기 어렵다.
  • 결론적으로 유연성이 떨어진다.
  • 안티패턴으로 불리기도 한다.

스프링 컨테이너 사용 코드

@Configuration
public class AppConfig {
    @Bean
    public MemberService memberService() {
        return new MemberServiceImpl(memberRepository());
    }
    @Bean
    public MemberRepository memberRepository() {
        return new MemoryMemberRepository();
    }
}

@Test
@DisplayName("스프링 컨테이너와 싱글톤")
void springContainer() {
        //스프링 컨테이너 사용
        ApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
        
        MemberService memberService1 = ac.getBean("memberService", MemberService.class);
        MemberService memberService2 = ac.getBean("memberService", MemberService.class);

        //싱글톤으로 인해 참조값이 동일
        System.out.println("memberService1 = " + memberService1);
        System.out.println("memberService2 = " + memberService2);

        //memberService1 != memberService2
        Assertions.assertThat(memberService1).isSameAs(memberService2);
}

//설명
@Bean은 싱글톤으로 관리되는 것으로 등록될때 싱글톤으로 객체가 생성이 된다.

 

 

참고 유튜브 링크

https://youtu.be/C6CczyrkYXU

 

 

아래 패턴들 추가적으로 정리해보자

 

 

 

'Theory' 카테고리의 다른 글

클래스, 객체, 인스턴스  (0) 2021.07.21
웹 표준, 웹 접근성 (추가 작성 할 것)  (0) 2021.06.23
트랜잭션(Transaction)  (0) 2021.06.06
SID, Service Name 차이  (0) 2021.05.06
어노테이션  (0) 2021.04.16

공유하기

facebook twitter kakaoTalk kakaostory naver band
loading