CS [디자인 패턴]

chaean·2024년 12월 17일

CS

목록 보기
3/5
post-thumbnail

1. 디자인패턴

프로그램을 설계할 때 발생했던 문제점들을 객체 간의 상호 관계 등을 이용하여 해결할 수 있도록 하나의 “규약” 형태로 만들어 놓은 것

  • GOF (Gang Of Four) 패턴

→ 4남자가 정의한 23개의 디자인패턴

→ 1. 생성 패턴, 2. 구조 패턴, 3. 행동 패턴

생성패턴

객체 생성방법이 들어간 디자인패턴

1. 싱글톤

인스턴스가 오직 하나만 존재하도록 보장하는 패턴.

시스템에서 특정 클래스의 인스턴스가 하나만 필요할 때 사용
    public class Singleton {
        // 클래스의 유일한 인스턴스를 저장하는 정적 변수
        private static Singleton instance;
    
        // 생성자는 private으로 외부에서 인스턴스를 만들지 못하게 한다.
        private Singleton() {}
    
        // 인스턴스를 반환하는 public 메서드
        public static Singleton getInstance() {
            if (instance == null) {
                instance = new Singleton();  // 최초에만 객체 생성
            }
            return instance;
        }
    }
    
    public class Main {
        public static void main(String[] args) {
            Singleton s1 = Singleton.getInstance();  // 첫 번째 인스턴스
            Singleton s2 = Singleton.getInstance();  // 두 번째 인스턴스 (s1과 동일)
    
            System.out.println(s1 == s2);  // true 출력, 두 인스턴스는 동일
        }
    }

2. 팩토리

상속 관계에 있는 두 클래스에서 상위 클래스가 중요한 뼈대를 결정하고,
하위 클래스에서 객체 생성에 관한 구체적인 내용을 결정하는 패턴

유연성, 유지보수성 ⬆️
    // 제품 인터페이스
    public interface Product {
        void use();
    }
    
    // 구체적인 제품 클래스 1
    class ConcreteProductA implements Product {
        @Override
        public void use() {
            System.out.println("ConcreteProductA 사용");
        }
    }
    
    // 구체적인 제품 클래스 2
    class ConcreteProductB implements Product {
        @Override
        public void use() {
            System.out.println("ConcreteProductB 사용");
        }
    }
    
    // 팩토리 클래스
    class ProductFactory {
        // 팩토리 메서드: 입력에 따라 다른 제품을 생성
        public Product createProduct(String type) {
            if (type.equals("A")) {
                return new ConcreteProductA();
            } else if (type.equals("B")) {
                return new ConcreteProductB();
            } else {
                throw new IllegalArgumentException("잘못된 제품 타입");
            }
        }
    }
    
    // 클라이언트 코드
    public class Main {
        public static void main(String[] args) {
            ProductFactory factory = new ProductFactory();
    
            // 제품 A 생성
            Product productA = factory.createProduct("A");
            productA.use();  // "ConcreteProductA 사용"
    
            // 제품 B 생성
            Product productB = factory.createProduct("B");
            productB.use();  // "ConcreteProductB 사용"
        }
    }

etc.. 추상팩토리. 빌더, 프로토타입패턴

구조패턴

객체, 클래스 등으로 큰 구조를 만들 때 유연하고 효율적으로 만드는 방법이 들어간 디자인 패턴

1. 프록시

프록시패턴이란 객체가 어떤 대상 객체에 접근하기 전,
그 접근에 대한 흐름을 가로채서 해당 접근을 필터링하거나 수정하는 등의 역할을 하는 
계층이 있는 디자인패턴.

일반적으로 프록시서버로 Cloudflare를 둬서 불필요한, 공격적인 트래픽을 막음

etc.. 어댑터, 브리지, 복합체, 데코레이터, 퍼사드, 플라이웨이트패턴

행동패턴

객체나 클래스 간의 알고리즘, 책임 할당에 관한 디자인 패턴

1. 옵저버

객체의 상태 변화를 관찰하고, 상태가 변경되었을 때 이를 자동으로 알림
받을 수 있도록 설계하는 디자인 패턴

이 패턴은 발행/구독(Publish/Subscribe) 모델로도 불리며,
객체 간의 느슨한 결합(loose coupling)을 유지하면서 관계를 설정할 수 있음.

< 트위터의 메인 로직, MVC에도 적용되어있음>

목적 : 객체 간의 일대다(OneToMany) 의존성을 정의하여, 한 객체의 상태가 변경되면 이를 의존하고 있는 다른 객체들에게 자동으로 알림을 전달하는 것

import java.util.ArrayList;
import java.util.List;

// Subject 인터페이스
interface Subject {
    void attach(Observer observer);  // 옵저버 등록
    void detach(Observer observer);  // 옵저버 제거
    void notifyObservers();          // 옵저버들에게 알림
}

// Observer 인터페이스
interface Observer {
    void update(float temperature);  // 주제로부터 알림을 받을 메서드
}

// ConcreteSubject 구현 (온도 센서)
class TemperatureSensor implements Subject {
    private List<Observer> observers = new ArrayList<>();
    private float temperature;

    @Override
    public void attach(Observer observer) {
        observers.add(observer);
    }

    @Override
    public void detach(Observer observer) {
        observers.remove(observer);
    }

    @Override
    public void notifyObservers() {
        for (Observer observer : observers) {
            observer.update(temperature);  // 온도를 옵저버에게 알림
        }
    }

    // 온도를 설정하고 변경 사항 알림
    public void setTemperature(float temperature) {
        this.temperature = temperature;
        System.out.println("Temperature updated to: " + temperature);
        notifyObservers();  // 옵저버들에게 알림
    }
}

// ConcreteObserver 구현 (디스플레이 장치)
class TemperatureDisplay implements Observer {
    private String displayId;

    public TemperatureDisplay(String displayId) {
        this.displayId = displayId;
    }

    @Override
    public void update(float temperature) {
        System.out.println(displayId + " received temperature update: " + temperature);
    }
}

// Main 클래스 (클라이언트 코드)
public class Main {
    public static void main(String[] args) {
        // 주제 생성
        TemperatureSensor sensor = new TemperatureSensor();

        // 옵저버 생성
        TemperatureDisplay display1 = new TemperatureDisplay("Display 1");
        TemperatureDisplay display2 = new TemperatureDisplay("Display 2");

        // 옵저버 등록
        sensor.attach(display1);
        sensor.attach(display2);

        // 온도 업데이트 및 알림 전송
        sensor.setTemperature(25.5f);  // Display 1, Display 2에 업데이트 전달
        sensor.setTemperature(30.0f);  // Display 1, Display 2에 업데이트 전달

        // 옵저버 제거
        sensor.detach(display1);

        // 온도 업데이트 (Display 1은 알림을 받지 않음)
        sensor.setTemperature(22.0f);
    }
}

3. 전략

전략이라고 부르는 “캡슐화한 알고리즘”을 컨텍스트 안에서 바꿔주면서
상호교체가 가능하게 만드는 디자인 패턴
// 1. Strategy 인터페이스 정의
public interface CalculationStrategy {
    int calculate(int num1, int num2);
}

// 2. 구체적인 전략 클래스 정의
public class AdditionStrategy implements CalculationStrategy {
    @Override
    public int calculate(int num1, int num2) {
        return num1 + num2;
    }
}

public class SubtractionStrategy implements CalculationStrategy {
    @Override
    public int calculate(int num1, int num2) {
        return num1 - num2;
    }
}

public class MultiplicationStrategy implements CalculationStrategy {
    @Override
    public int calculate(int num1, int num2) {
        return num1 * num2;
    }
}

// 3. Context 클래스 정의
public class Calculator {
    private CalculationStrategy strategy;

    // 전략 설정 메서드
    public void setStrategy(CalculationStrategy strategy) {
        this.strategy = strategy;
    }

    // 계산 실행 메서드
    public int executeStrategy(int num1, int num2) {
        return strategy.calculate(num1, num2);
    }
}

// 4. Main 클래스에서 테스트
public class Main {
    public static void main(String[] args) {
        Calculator calculator = new Calculator();

        // 덧셈 전략 설정 및 실행
        calculator.setStrategy(new AdditionStrategy());
        System.out.println("Addition: " + calculator.executeStrategy(10, 5)); // 출력: 15

        // 뺄셈 전략 설정 및 실행
        calculator.setStrategy(new SubtractionStrategy());
        System.out.println("Subtraction: " + calculator.executeStrategy(10, 5)); // 출력: 5

        // 곱셈 전략 설정 및 실행
        calculator.setStrategy(new MultiplicationStrategy());
        System.out.println("Multiplication: " + calculator.executeStrategy(10, 5)); // 출력: 50
    }
}

장점

  • 코드 재사용성
  • 동적으로 전략 변경
  • 새로운 전략을 추가해도 기존 코드에 영향을 주지 않음

단점

  • 클래스가 늘어남에 따라 복잡도 증가
  • 전략이 많아지면 관리가 복잡

etc.. 이터레이터, 책임연쇄, 커맨드, 중재자, 메멘토, 상태 템플릿메서드, 비지터패턴

이 외에도 flux패턴, MVC패턴, MVVM패턴 등 수많은 디자인 패턴이 존재

2. 라이브러리 ? 프레임워크 ?

라이브러리

공통으로 사용될 수 있는 특정한 기능들을 모듈화한 것
폴더명, 파일명 등에 대한 규칙이 없고 자유로움

ex) axios

프레임워크

공통으로 사용될 수 있는 특정한 기능들을 모듈화 한것
폴더명, 파일명 등에 대한 규칙이 존재하고 엄격함

ex) vue.js, Django, Spring Boot

3. [DEEP DIVE] 싱글톤 패턴 구현 [Java]

public class Singleton {
	private static Singleton instance;
	private Singleton() { }
	
	public static synchronized Singleton getInstance() { 
		if (instance == null) {
			instance = new Singleton();
			}
			return instance;
	}
}

synchronized 키워드로 잠금을 할 수 있음.

최초에 접근한 스레드가 해당 메서드 호출시에 다른 스레드가 접근하지 못하도록 잠금(lock)을 걸어준다.

Synchronized

멀티스레딩 환경에서 동기화를 위해 사용되는 키워드.

동기화란 여러 스레드가 동시에 공유 자원에 접근하는 것을 제어하여 상호 배타적으로 접근하도록 보장하는 기법.

public class Singleton {
	private final static Singleton instance = new Singleton();
	
	private Singleton() { }
	
	public static Singleton getInstance() { 
		return instance;
	}
}

위와 같이 선언하면 static에 의해 getInstace()를 호출하지 않더라도

Singleton 클래스가 로드되기만 하면 메모리에 즉시 할당하는 문제가 있음

최종 코드

class Singleton {
	private Singleton() {} // private 생성자로 클래스 외부에서 인스턴스 생성을 방지
	
	private static class singleInstanceHolder {
		private static final Singleton INSTANCE = new Singleton();
	}
		public static Singleton getInstance() {
		return singleInstanceHolder.INSTANCE;
	} 
}

private 생성자를 사용함으로써 클래스의 인스턴스가 클래스 외부에서 임의로 생성되는 것을 방지하며, 싱글턴 인스턴스에 접근하기 위해서는 getInstance() 메서드를 강제하게 됩니다.

4. DI/DIP

DI (Dependency Injection : 의존성 주입)

메인 모듈이 ‘직접’ 다른 하위 모듈에 대한 의존성을 주기보다는 
중간에 의존성 주입자(dependency injector)가 이 부분을 가로채 메인 모듈이
‘간접’적으로 의존성을 주입하는 방식

→ 메인 모듈, 하위모듈간의 의존성을 조금 더 느슨하게 만들어 모듈을 쉽게 교체 가능한 구조로 만듦.

import java.util.*;

class B {
	public void go() {
		System.out.println("B의 go()함수"); }
}

class A {
	public void go() { 
		new B().go();
	} 
}

public class main {
	public static void main(String args[]) {
		new A().go(); 
	}
}

DIP (Dependency Inversion Principle : 의존관계역전원칙)

의존성 주입(DI)를 할 때는 의존관계역전원칙(DIP)가 적용됨.

이는 2가지 규칙을 지키는 상태를 뜻함

  • 상위 모듈은 하위 모듈에 의존해서는 안된다. 둘 다 추상화에 의존해야 한다
  • 추상화는 세부사항에 의존해서는 안 된다. 세부 사항은 추상화에 따라 달라져야한다.

장점

  1. 외부에서 모듈을 생성하여 이런식으로 집어넣는
    구조가 되기 때문에 모듈들을 쉽게 교체할 수 있는 구조가 됩니다.
  2. 단위 테스팅 + 마이그레이션이 쉬워짐
  3. 애플리케이션 의존성 방향이 좀 더 일관되어 코드를 추론하기 쉬워짐

단점

  1. 결국엔 모듈이 더 추가되기때문에 복잡도 증가
  2. 종속성 주입이 컴파일 할 때가 아니라 런타임 때 일어나기 때문에 컴파일을 할 때 종속성 주입에 관한 에러를 잡기가 어려워질 수 있음

5. MVC / MVP / MVVM 패턴

MVC패턴

Model, View, Controller로 이루어진 디자인 패턴

Model

역할: 애플리케이션의 데이터와 비즈니스 로직을 담당. (Entity)

주요 작업: 데이터 처리, 저장, 업데이트 (예: 데이터베이스 접근)

예시: 사용자 정보, 계산 로직.

View

역할: 사용자 인터페이스(UI)를 담당, 데이터를 화면에 표시.

주요 작업: UI 렌더링, 사용자 입력 받기.

예시: HTML, 화면 템플릿, 버튼, 텍스트 필드.

Controller

역할: 사용자 입력을 받아 Model과 View를 연결.

주요 작업: 사용자 요청 처리, Model 업데이트, View 갱신.

예시: HTTP 요청을 처리하는 메서드, 폼 데이터 처리.


MVC 패턴장점:

모듈화: 코드가 분리되어 유지보수 용이.
테스트 용이: 각 컴포넌트를 독립적으로 테스트 가능.
유연성: UI(View)를 변경해도 Model에 영향을 미치지 않음.

단점:

복잡성: 소규모 애플리케이션에서는 과도할 수 있음.

MVP 패턴

Controller가 Presenter로 교체된 패턴.
V와 P는 1:1 관계이므로 MVC보다 더 강한 결합성을 지닌 디자인 패턴
UI와 로직을 분리하는 디자인 패턴

MVVM 패턴

Controller가 View Model로 바뀐 패턴.
VM은 View를 추상화한 계층
VM : V = 1: N 이라는 관계를 가짐

VM은 커맨드와 데이터바인딩을 가짐

  • 커맨드: 여러요소에 대한 처리를 하나의 액션으로 처리할 수 있는 기법
  • 데이터바인딩 : 화면에 보이는 데이터와 브라우저 상의 메모리 데이터를 일치시키는 방법.

ex) Vue.js

차이점

Spring MVC

MVC패턴이 적용된 Web모듈의 Spring Web MVC ?

디스패처 서블릿의 요청 처리 과정

  1. 클라이언트가 요청(Reqeust) 했을 때 가장먼저 디스패처 서브릿이 이를 받음
    ex) url, form data

  2. 하나 이상의 Handler Mapping을 참고해서 적절한 컨트롤러를 설정. 이후 컨트롤러로 요청을 보냄

  3. 컨트롤러는 DB에 접근하는 등 비즈니스 로직을 수행

  4. 그리하여 사용자에게 전달해야 할 정보인 모델을 생성

  5. 뷰를 구현하기 위한 View Reslover를 참고

  6. 해당 정보를 기반으로 뷰를 렌더링

  7. 응답(Response) 반환

Q. 전략패턴, 의존성주입(DI)의 차이는?

둘 다 무언가를 쉽게 교체하기 위한 디자인패턴이라는 공통점. But,

  • 전략패턴 : 동일한 행동 계약을 기반으로 다양한 구현이 명시되어있는 인터페이스를 만드는 것을 퐇마
  • 의존성주입 : 일부 동작을 구현하고 의존성을 주입하기만 하는 패턴

Q. 컨텍스트란?

  1. 어떤 종류의 상태, 환경을 캡슐화 한 것을 말함. ex) React의 Context Hook, Redux
  2. 작업이 중단 되고 나중에 같은 지점에서 계속 될 수 있도록 저장하는 최소 데이터 집합 ex) 컨텍스트 스위칭

profile
백엔드 개발자

0개의 댓글