디자인 패턴

hsso_o·2024년 9월 5일
0

스터디

목록 보기
39/44
  • 프로그램을 설계할 때 발생했던 문제점들을 객체 간의 상호 관계 등을 이용해 해결할 수 있도록 하나의 ‘규약’ 형태로 만들어 놓은 것을 의미

싱글톤 패턴

  • 하나의 클래스에 오직 하나의 인스턴스만 가지는 패턴
    • 하나의 인스턴스를 만들어 놓고 해당 인스턴스를 다른 모듈들이 공유하며 사용함
  • DB 연결 모듈, 전역 상태 관리 등

장점

  • 인스턴스 생성 비용 감소

단점

  • 의존성 높아짐
  • TDD 시, 각 테스트마다 독립적인 인스턴스 만들기가 어려움

방식

  • Lazy Initialization 방식 (지연 초기화)
    public class Singleton{
    
        // 생성자는 외부에서 호출못하게 private 으로 지정
        private Singleton() {}
         
        // 인스턴스를 생성하고 반환하는 정적 메서드 제공 
        // (synchronized로 멀티스레드 안전성 보장)
        public static synchronized Singleton getInstance() {
            if (instance == null) {
                instance = new Singleton();
            }
            return instance;
        }
    }
  • Eager Initialization 방식 (이른 초기화)
    public class Singleton {
        
        // 클래스 로딩 시 인스턴스를 미리 생성
        private static final Singleton instance = new Singleton();
        
        // 생성자는 외부에서 호출못하게 private 으로 지정
        private Singleton() {}
        
        // 인스턴스를 반환하는 정적 메서드 제공
        public static Singleton getInstance() {
            return instance;
        }
    }

📗 의존성 주입

  • 의존성 주입을 통해 모듈 간의 결합을 좀 더 느슨하게 만들어 해결 가능
  • 메인 모듈이 직접 다른 하위 모듈에 대한 의존성을 주기 보다는 의존성 주입자가 이 부분을 가로채 메인 모듈이 ‘간접적’으로 의존성을 주입하는 방식
  • 의존성 주입 원칙
    • 상위 모듈은 하위 모듈에서 어떠한 것도 가져오지 않아야 함
    • 추상화에 의존 → 추상화는 세부 사항에 의존X
  • 장점
    • 모듈을 쉽게 교체 가능해 테스팅 및 마이그레이션 쉬움
    • 애플리케이션ㄴ 의존성 방향이 일관됨
    • 모듈 간의 관계 명확해짐
  • 단점
    • 클래스 수가 늘어나 복잡성 증가
    • 런타임 패널티가 생기기도 함

팩토리 패턴

출처 : [https://refactoring.guru/ko/design-patterns/factory-method]

  • 객체를 사용하는 코드에서 객체 생성 부분을 떼어내 추상화한 패턴
  • 상속 관계에 있는 두 클래스에서 상위 클래스가 중요한 뼈대 결정(실제 객체 생성)
    → 하위 클래스는 객체 생성의 구체적 내용을 결정
  • 클래스의 객체를 동적으로 생성 → 생성 패턴

장점

  • 상위 - 하위 클래스 분리로 느슨한 결합, 유연성
  • 객체 생성 로직이 따로라 리팩토링 하더라도 한 곳만 고쳐서 유지보수성 증가

단점

  • 인터페이스 및 구체적인 클래스와 팩토리 클래스가 모두 필요하므로, 클래스 파일이 많아지고 구조가 복잡해질 수 있음 → 클래스 증가, 복잡성 증가
  • 예시 코드
    // 1. 제품 인터페이스
    interface Car {
        void drive();
    }
    
    // 2. 구체적인 제품 클래스들
    class Sedan implements Car {
        @Override
        public void drive() {
            System.out.println("Sedan 주행 중...");
        }
    }
    
    class SUV implements Car {
        @Override
        public void drive() {
            System.out.println("SUV 주행 중...");
        }
    }
    
    // 3. 팩토리 클래스
    class CarFactory {
        // 팩토리 메서드
        public Car createCar(String carType) {
            if (carType == null) {
                return null;
            }
            if (carType.equalsIgnoreCase("Sedan")) {
                return new Sedan();
            } else if (carType.equalsIgnoreCase("SUV")) {
                return new SUV();
            }
            return null;
        }
    }
    
    // 4. 클라이언트 코드
    public class Main {
        public static void main(String[] args) {
            CarFactory carFactory = new CarFactory();
    
            // Sedan 생성
            Car sedan = carFactory.createCar("Sedan");
            sedan.drive(); // Sedan 주행 중...
    
            // SUV 생성
            Car suv = carFactory.createCar("SUV");
            suv.drive(); // SUV 주행 중...
        }
    }
    

전략 패턴(정책 패턴)

출처 : [https://velog.io/@kyle/디자인-패턴-전략패턴이란]

  • 객체의 행위를 바꾸고 싶은 경우, 직접 수정하지 않고 전략(캡슐화한 알고리즘)을 컨텍스트 안에서 변경하며 상호 교체가 가능하게 만드는 패턴
    • EX. 결제할 때, BC카드로 사거나, 농협 카드로 사는 등 결제 전략만 변경해 두 가지 방식으로 결제
    • 특정 작업을 수행하는 알고리즘을 동적으로 교체 → 행위 패턴

장점

  • 알고리즘의 분리
  • 런타임에 전략을 바꿀 수 있으므로 프로그램의 동작을 동적으로 변경 → 유연성 증가
  • 새로운 전략을 추가할 때 기존 코드 수정 없이 새로운 전략 클래스를 추가 → 확장성

단점

  • 클래스 수 증가

  • 알고리즘 선택의 책임이 클라이언트에게 있음

  • 예시 코드

    // 결제 전략 인터페이스
    interface PaymentStrategy {
        void pay(int amount);
    }
    
    // 카드 결제 전략
    class CreditCardStrategy implements PaymentStrategy {
        private String cardNumber;
    
        public CreditCardStrategy(String cardNumber) {
            this.cardNumber = cardNumber;
        }
    
        @Override
        public void pay(int amount) {
            System.out.println(amount + "원을 카드로 결제합니다. 카드 번호: " + cardNumber);
        }
    }
    
    // 페이팔 결제 전략
    class PayPalStrategy implements PaymentStrategy {
        private String email;
    
        public PayPalStrategy(String email) {
            this.email = email;
        }
    
        @Override
        public void pay(int amount) {
            System.out.println(amount + "원을 페이팔로 결제합니다. 페이팔 계정: " + email);
        }
    }
    
    // Context 클래스 (결제 처리)
    class ShoppingCart {
        private PaymentStrategy paymentStrategy;
    
        public void setPaymentStrategy(PaymentStrategy paymentStrategy) {
            this.paymentStrategy = paymentStrategy;
        }
    
        public void checkout(int amount) {
            paymentStrategy.pay(amount);
        }
    }
    
    // 클라이언트 코드
    public class Main {
        public static void main(String[] args) {
            ShoppingCart cart = new ShoppingCart();
    
            // 카드로 결제
            cart.setPaymentStrategy(new CreditCardStrategy("1234-5678-9876-5432"));
            cart.checkout(5000); // 5000원을 카드로 결제합니다.
    
            // 페이팔로 결제
            cart.setPaymentStrategy(new PayPalStrategy("user@example.com"));
            cart.checkout(10000); // 10000원을 페이팔로 결제합니다.
        }
    }
    

옵저버 패턴

출처 : [https://medium.com/@jiyeeon1208/옵저버-패턴-observer-pattern-d4368116e279]

  • 어떤 객체의 상태 변화를 관찰하다가 상태 변화 시 메서드 등을 통해 옵저버 목록에 있는 옵저버들에게 변화를 알려주는 디자인 패턴
    • 주체 : 객체의 상태 변화를 보고 있는 관찰자.
    • 옵저버 : 객체의 상태 변화에 따라 추가 변화 사항이 생기는 객체
  • 주로 이벤트 기반 시스템에 사용, MVC 패턴에도 사용됨
    • 모델에서 변경 사항 → update를 뷰에게 알려주고 → 이를 기반으로 컨트롤러 등이 작동

장점

  • 느슨한 결합 : 주제와 옵저버 간의 결합도가 낮음
  • 동적 연결 : 런타임에 옵저버 동적으로 추가/제거 가능
  • 상태 변경 시, 자동으로 알림이 전달되므로 상태 변경 관리가 간편해짐

단점

  • 복잡성 증가 → 많은 옵저버 등록 시 알림 시간 증가 및 코드 복잡

  • 옵저버가 많아질수록, 상태를 변경할 때 어떤 옵저버가 어떻게 반응할지 예측 어려움

  • 알림을 보낼 때 성능 저하가 발생할 수 있음

  • 예시 코드

    // 1. 옵저버 인터페이스 (Observer)
    interface Observer {
        void update(String message);
    }
    
    // 2. 주제 인터페이스 (Subject)
    interface Subject {
        void addObserver(Observer observer);
        void removeObserver(Observer observer);
        void notifyObservers();
    }
    
    // 3. 구체적인 주제 (ConcreteSubject)
    class NewsAgency implements Subject {
        private List<Observer> observers = new ArrayList<>();
        private String news;
    
        public void setNews(String news) {
            this.news = news;
            notifyObservers();  // 뉴스가 업데이트되면 옵저버들에게 알림
        }
    
        @Override
        public void addObserver(Observer observer) {
            observers.add(observer);
        }
    
        @Override
        public void removeObserver(Observer observer) {
            observers.remove(observer);
        }
    
        @Override
        public void notifyObservers() {
            for (Observer observer : observers) {
                observer.update(news);
            }
        }
    }
    
    // 4. 구체적인 옵저버 (ConcreteObserver)
    class NewsSubscriber implements Observer {
        private String name;
    
        public NewsSubscriber(String name) {
            this.name = name;
        }
    
        @Override
        public void update(String message) {
            System.out.println(name + " received news: " + message);
        }
    }
    
    // 5. 클라이언트 코드
    public class Main {
        public static void main(String[] args) {
            NewsAgency newsAgency = new NewsAgency();
            NewsSubscriber subscriber1 = new NewsSubscriber("Subscriber 1");
            NewsSubscriber subscriber2 = new NewsSubscriber("Subscriber 2");
    
            newsAgency.addObserver(subscriber1);
            newsAgency.addObserver(subscriber2);
    
            newsAgency.setNews("Breaking News: New Observer Pattern Example!");
        }
    }
    

프록시 패턴

프록시 패턴

출처 : [https://refactoring.guru/ko/design-patterns/proxy]

  • 어떤 객체에 대한 접근을 제어하거나, 객체의 대리인 역할을 하는 클래스(프록시)를 통해 간접적으로 그 객체를 제어하는 패턴
    • 대상 객체에 접근 하기 전 그 접근에 대한 흐름을 가로채 해당 접근을 필터링,수정하는 등의 역할

장점

  • 지연 초기화(Lazy Initialization): 실제 객체의 생성 비용이 클 경우, 프록시가 대신 작업을 처리하다가, 실제 객체가 필요할 때만 생성
  • 민감한 객체에 대한 접근을 프록시를 통해 제어
  • 프록시 통해 요청을 캐시하거나 성능 최적화 가능
  • 로그, 트랜잭션 처리 등 추가 작업 수행 가능

단점

  • 응답 속도 지연

  • 복잡성 증가

  • 예시 코드

    // 1. 주제 인터페이스 (공통 인터페이스)
    interface Service {
        void request();
    }
    
    // 2. 실제 객체 (RealSubject)
    class RealService implements Service {
        @Override
        public void request() {
            System.out.println("실제 서비스 요청 처리");
        }
    }
    
    // 3. 프록시 객체 (Proxy)
    class ProxyService implements Service {
        private RealService realService;
    
        @Override
        public void request() {
            if (realService == null) {
                realService = new RealService();  // 실제 객체 생성 (지연 초기화)
            }
            System.out.println("프록시에서 접근 제어 처리 중...");
            realService.request();  // 실제 서비스 요청
        }
    }
    
    // 4. 클라이언트 코드
    public class Main {
        public static void main(String[] args) {
            Service service = new ProxyService();  // 프록시 객체 사용
            service.request();  // 프록시를 통해 실제 객체에 접근
        }
    }
    

이터레이터 패턴

  • 이터레이터를 사용해 컬렉션의 요소들에 접근하는 디자인 패턴
  • 이를 통해 순회할 수 있는 여러 자료형의 구조와 상관없이 하나의 인터페이스로 순회 가능

노출 모듈 패턴

  • 즉시 실행 함수를 통해 private, public 같은 접근 제어자를 만드는 패턴

MVC 패턴

출처 : [https://velog.io/@jw01987/MVC-패턴]

  • 모델, 뷰, 컨트롤러로 이뤄진 디자인 패턴

    • 모델 : 애플리케이션의 데이터(DB, 상수, 변수 등)
    • 뷰 : 사용자 인터페이스 요소 → 모델을 기반으로 사용자가 볼 수 있는 화면
    • 컨트롤러 : 하나 이상의 모델과 하나 이상의 뷰를 잇는 다리 역할 → 메인 로직 담당
  • 예시 코드

    // Model (데이터 및 로직)
    class Model {
        private String data;
    
        public String getData() {
            return data;
        }
    
        public void setData(String data) {
            this.data = data;
        }
    }
    
    // View (UI)
    class View {
        public void display(String data) {
            System.out.println("Displaying: " + data);
        }
    }
    
    // Controller (사용자 요청 처리 및 데이터 전달)
    class Controller {
        private Model model;
        private View view;
    
        public Controller(Model model, View view) {
            this.model = model;
            this.view = view;
        }
    
        public void setData(String data) {
            model.setData(data);  // 모델에 데이터 설정
        }
    
        public void updateView() {
            view.display(model.getData());  // 뷰에 데이터 전달
        }
    }
    
    // 클라이언트 코드
    public class MVCPatternExample {
        public static void main(String[] args) {
            Model model = new Model();
            View view = new View();
            Controller controller = new Controller(model, view);
    
            controller.setData("Hello, MVC Pattern!");  // 모델 데이터 설정
            controller.updateView();  // 뷰 업데이트
        }
    }
    

  • MVP 패턴
    • MVC에서 C에 해당하는 컨트롤러가 프레젠터로 교체된 패턴
    • 뷰와 프레젠터는 일대일 관계 → MVC 패턴보다 더 강한 경합을 지닌 디자인 패턴
  • MVVM
    • MVC에서 C에 해당하는 컨트롤러가 뷰모델로 바뀐 패턴
      • 뷰모델은 뷰를 더 추상화한 계층
      • 커맨드와 데이터 바인딩을 가지는 것이 특징임
      • 뷰와 뷰모델 사이의 데이터 바인딩 지원
    • Vue.js가 해당 패턴을 가지고 있음
profile
아뇨 소혠데요-

0개의 댓글