[CS] 1.1. 디자인 패턴 개념과 3가지 종류

ofohj·2023년 5월 16일
0

CS

목록 보기
1/14
post-thumbnail

1. 개념

프로그램을 설계할 때 발생하는 문제점들을 해결할 수 있도록 설정한 '규약'


2. 관련 도구

디자인 패턴을 기반으로 만들어지는 것으로 라이브러리와 프레임워크가 있습니다.

라이브러리

  • 재사용 가능한 코드와 기능의 모음
  • 개발자가 필요한 기능을 직접 호출하여 사용

프레임워크

  • 애플리케이션 개발을 위한 구조와 규칙 제공
  • 폴더명, 파일명등에 대한 규칙 존재 👉 정해진 구조에 따라 코드 작성
  • 프레임워크가 제공하는 틀 안에서 개발 및 확장

Q1: 라이브러리와 프레임워크의 차이점이 뭔가요?
A1: 규칙을 중심으로 답하기!
라이브러리는 규칙이 존재하지 않아 개발자가 필요한 기능을 자유롭게 선택할 수 있습니다. 반면, 프레임워크는 규칙이 존재하여 보다 제한적입니다. 하지만 제공된 규칙과 구조를 통해 개발 시간을 단축하고 일관성을 유지할 수 있습니다.


3. 싱글톤 패턴⭐⭐⭐

개념

하나의 클래스에 하나의 인스턴스만 가지는 패턴

장점

하나의 인스턴스를 다른 모듈들이 공유하기 때문에 인스턴스 생성 비용 감소
→ 그래서 인스턴스 생성에 비용이 많이드는 I/O 바운드 작업에 싱글톤 사용

I/O 바운드

  • Input과 Output에 대한 작업이 시스템 자원보다 더 많은 시간을 소비하는 상황
  • I/O: 디스크, 네트워크, 마우스 등과 같은 외부 장치와의 데이터 입출력
  • 예시) 디스크 연결, 네트워크 통신, 데이터베이스 연결

단점

  • 의존성이 높아짐
  • TDD시 걸림돌
    • TDD(Test Driven Development): 테스트를 중요시하는 개발
    • TDD에는 단위테스트가 필요 → 단위 테스트는 독립적인 환경 요구 → 싱글톤은 독립적인 인스턴스 만들기가 어려움

구현 방법⭐

💡 리마인드 - 싱글톤 패턴

하나의 클래스에 하나의 인스턴스를 만들어 이 인스턴스를 여러 모듈들이 공유하는 형태

1. 단순 매서드 호출

방법
인스턴스 선언 후 없으면 생성

문제점
멀티스레드라는 자바의 특성상 이 방법을 사용하면 원자성이 결여됨
👉 인스턴스 2개 생성 가능 👉 싱글톤 개념 불만족

2. synchronized

방법
인스턴스 반환 전까지 다른 스레드가 접근할 수 없도록 lock을 걸어줌

문제점
호출할때마다 걸리는 lock이 성능 저하를 유발

3. 이중 확인 잠금(DCL)

방법
매번 lock을 걸지 않고 인스턴스가 null일때만 lock을 걸어줌으로서 성능 저하 해결

참고) volatile
서로 다른 캐시메모리가 아닌 메인메모리에서 변수를 가져오게 만듦
👩‍💻코드: private volatile Singleton instane

4. 정적 멤버

방법
인스턴스 선언과 동시에 생성

문제점
멀티스레드로 인해 발생하는 문제점은 해결가능하지만, 자원 낭비가 발생

5. 정적 블록

방법
위 방법과 유사하나, 생성시 블록을 사용

6. 정적 멤버와 Lazy Holder(중첩 클래스)⭐⭐

⚡ lazy instantiation이라고도 부름

방법
필요할 때 만 정적 멤버를 선언하여 불필요한 자원할당 방지

코드

class Singleton:
    __instance = None  # 인스턴스를 저장할 클래스 변수

    @staticmethod
    def getInstance():
        if Singleton.__instance is None:  # 인스턴스가 없는 경우
            Singleton()  # 인스턴스 생성
        return Singleton.__instance  # 인스턴스 반환

    def __init__(self):
        if Singleton.__instance is not None:  # 이미 인스턴스가 생성된 경우
            raise Exception("이미 인스턴스가 생성되었습니다.")
        else:
            Singleton.__instance = self  # 클래스 변수에 인스턴스 저장


# 사용 예시
singleton_instance1 = Singleton.getInstance()  # 인스턴스 생성
singleton_instance2 = Singleton.getInstance()  # 이미 생성된 인스턴스 반환

print(singleton_instance1 is singleton_instance2)  # True (두 인스턴스가 동일한지 비교)

7. enum⭐⭐

방법
스레드세이프한 점을 보장하며 인스턴스 생성

코드

from enum import Enum, auto

class Singleton(Enum):
    INSTANCE = auto()  # enum 멤버로 인스턴스 생성

    def __init__(self):
        self.some_property = "example"  # 추가적인 속성 정의

# 사용 예시
singleton_instance1 = Singleton.INSTANCE
singleton_instance2 = Singleton.INSTANCE

print(singleton_instance1 is singleton_instance2)  # True (두 인스턴스가 동일한지 비교)
print(singleton_instance1.some_property)  # example (추가적인 속성 접근)

4. 팩토리 패턴

개념

객체를 사용하는 코드에서 생성 부분을 떼어내 추상화한 패턴

  • 상위 클래스: 중요한 뼈대 결정
  • 하위 클래스: 객체 생성에 관한 내용 결정

💡 주요사항
떼어냄, 즉 클래스 분리를 위해 느슨한 결합으로 구성됨

5. 이터레이터 패턴

개념

각기 다른 자료구조를 한 개의 인터페이스로 순회 가능

장점

  1. 분리된 순회 로직
  • 클라이언트 코드와 컬렉션 사이의 의존성 감소
  • 유지보수성과 재사용성 향상
  1. 다양한 순회 방식
    클라이언트 코드의 필요에 따라 순회 방식 선택 가능
    👉 자신만의 순회 상태 유지 가능

단점

  1. 추가적인 객체 생성
    이터레이터 패턴은 컬렉션과 별도의 이터레이터 객체 생성 필요
    👉 메모리와 성능 면에서 약간의 오버헤드 발생 가능
    👉👉 하지만 대부분의 경우 이 오버헤드는 무시할 정도로 작음

예시 코드(파이썬)

# 문자열로만 이뤄진 컬렉션의 이터레이터 구현
class StringIterator:
    def __init__(self, collection):
        self.collection = collection
        self.index = 0

    def __iter__(self):
        return self

    def __next__(self):
        if self.index >= len(self.collection):
            raise StopIteration
        value = self.collection[self.index]
        self.index += 1
        return value

# 숫자로만 이뤄진 컬렉션의 이터레이터 구현
class NumberIterator:
    def __init__(self, collection):
        self.collection = collection
        self.index = 0

    def __iter__(self):
        return self

    def __next__(self):
        if self.index >= len(self.collection):
            raise StopIteration
        value = self.collection[self.index]
        self.index += 1
        return value

# 문자열 컬렉션 생성
string_collection = ["apple", "banana", "cherry"]

# 숫자 컬렉션 생성
number_collection = [1, 2, 3, 4, 5]

# 문자열 컬렉션 순회
string_iter = StringIterator(string_collection)
for item in string_iter:
    print(item)

# 숫자 컬렉션 순회
number_iter = NumberIterator(number_collection)
for item in number_iter:
    print(item)

6. 의존성

의존성의 높고 낮음은 좋고 나쁨으로 정의할 순 없다.
하지만 일반적으로 과도한 의존성은 코드의 유지보수성과 확장성을 저해한다.

지금 공부하는 CS는 면접 목적도 있기 때문에!
면접관님께서

👩‍💻 의존성이 너무 높을 경우 어떻게 해야하는가?

를 질문하신다면

의존성 주입

을 대답할 수 있다.

개념

메인 모듈이 ❌직접❌ 하위 모듈에 연결되어 의존성을 주는 것이 아니라, 중간에 의존성 주입자를 넣어 메인모듈이 ⭕간접적⭕으로 의존성을 주입하는 것이다.

효과

  • 메인 모듈 간 의존성을 느슨하게 만듦
  • 모듈이 쉽게 교체 가능한 구조로 변경

장점

  • 단위 테스팅과 마이그레이션(다른 os로의 이동)이 좀 더 쉬워짐

단점

  • 모듈이 추가되는 것이기 때문에 복잡도 증가

의존관계 역전원칙

의존성 주입을 할 때 적용되는 법칙입니다.

  • 상위 모듈은 하위 모듈에 의존해서는 안된다.
  • 추상화는 세부사항에 의존하지 않아야 한다.

💡 참고

  • 추상화(Abstraction)
    객체들 간의 공통된 특성과 동작을 추상적, 독립적으로 정의

  • 세부사항(Details)
    코드의 특정 부분이나 구현 세부 사항을 나타내며, 추상화와는 반대

👉 세부사항은 구현의 변화나 변경에 따라 달라짐


7. 정리

✅ 디자인 패턴

프로그램을 설계할 때 발생하는 문제점들을 해결할 수 있도록 설정한 '규약'

✅ 관련 도구

  • 라이브러리
  • 프레임워크

✅ 싱글톤패턴

  • 개념: 하나의 클래스에 하나의 인스턴스만 가지는 패턴

  • 필요성: 객체의 유일성을 보장해주어야하는 경우, 인스턴스 생성 비용이 경우

  • 사용사례: 데이터베이스 연결, 네트워크 연결

  • 효과적인 구현 방법: lazy instantiation, enum

✅ 팩토리 패턴

  • 객체 생성 부분에서 클래스를 분리하여 느슨한 결합으로 구성

✅ 이터레이터 패턴

  • 각기 다른 자료구조를 하나의 인터페이스로 순회

✅ 의존성 주입

  • 유지보수 효율성을 높이기 위해 의존성을 낮추는 방법
  • 의존성 주입자를 넣어 메인모듈과 하위모듈을 간접적으로 연결

0개의 댓글