[CS] 1. 디자인 패턴

임정규·2025년 10월 28일

CS

목록 보기
1/2
post-thumbnail

CS 면접 질문 대비해서 CS 공부를 하고자한다.
공부할 때, 참고하는 책은 "면접을 위한 CS 전공지식 노트"이다.
한 단원씩 읽고, 메모장으로 정리하고 다음날 다시 정리해보면서 공부할 예정이다.

1. 싱글톤 패턴

싱글톤 패턴 개념

하나의 클래스에서 오직 하나의 인스턴스만 가지는 패턴이다.
하나의 클래스에서 여러개의 개별적인 인스턴스를 만들어낼 수 있지만, 하나만 생성해서 관리하는 것이다.
ex) 데이터베이스 연결

클래스, 객체, 인스턴스
1. 클래스: 변수와 메서드를 가지는 명세서, 설계도
2. 객체: 클래스로 구현할 어떤 것
3. 인스턴스: 객체를 실체화 시킨 것

인스턴스를 하나만 생성해서 관리하니깐, 구현과 유지보수가 쉬워보이지만 아니다.
단점은 다음과 같다.

  1. TDD에서 걸림돌이 된다.
    단위 테스트 위주로 하는 테스트 주도 개발에서 테스트가 서로 독립적이고, 테스트를 어떤 순서로도 실행할 수 있어야되는데, 미리 생성된 인스턴스를 기반으로 구현하는 패턴이라 독립적인 인스턴스를 만들기가 어렵다.
  2. 결합도 문제
    모듈간의 결합도를 강하게 만든다. 이 때, DI (Dependency Injection)을 활용하여 결합도를 느슨하게 만들 수 있다.
  3. Java의 멀티쓰레드 환경
    싱글톤 패턴으로 인스턴스를 생성할 때, 생성된 인스턴스가 있는지 먼저 검사를 하는 방식으로 구현이 되어있는 경우, 여러개의 쓰레드가 해당 인스턴스를 여러개 생성할 수 있다. (Thread Safe 문제 발생)

DI (Dependency Injection)
메인 모듈이 직접 하위 모듈에 의존성을 주기보단, 별도의 의존성 주입자 (Dependency Injecter)가 이 부분을 가로채 메인 모듈이 간접적으로 의존성을 주입하는 방식

  • A가 B에 의존성이 있다는 것은 B의 변경 사항에 A도 변해야 된다는 것을 의미

의존성 주입의 장점

  1. 상위 모듈이 하위 모듈에 대한 의존성이 감소된다.
  2. 모듈을 쉽게 교체할 수 있는 구조가 된다. (테스팅, 마이그레이션 수월)
  3. 추상화 레이어를 기반으로 구현체를 넣기 때문에 애플리케이션의 의존성 방향이 일관적 (쉬운 기능 추론, 모듈 간의 관계 명확)

의존성 주입의 단점

  1. 모듈들이 분리되어 클래스 수가 늘어나 복잡성이 증가 (약간의 런타임 패널티 발생!)

참고
SOLID에서 DIP (의존성 주입 원칙)
상위 모듈은 하위 모듈에서 어떠한 것도 가져오지 않아야 한다.
둘 다 추상화에 의존! 추상화는 세부 사항에 의존하지 말아야 한다.

Thread Safe 문제해결방법

  1. 싱글톤 패턴으로 인스턴스를 생성하는 메서드에 synchronized 접근제어자를 추가하여 쓰레드 하나가 접근 중일 때, 다른 쓰레드는 접근을 하지 못하게 락을 걸 수 있다. 대신, 인스턴스를 가져올 때마다 락이 걸리기 때문에 불필요한 오버헤드가 발생한다는 단점이 있다.
  2. 정적멤버방식으로 JVM이 클래스를 로드할 때 미리 싱글톤 객체를 생성할 수도 있다. 다만, 이는 자원낭비로 이어질 수 있다.

다른 여러가지 방법도 많지만, 여기까지 정리하겠다.

2. 팩토리 패턴

객체를 생성하는 부분을 따로 분리하여 구현하는 패턴이다.
상속관계에 있는 두 클래스에서 상위 클래스는 객체 생성에 필요한 중요한 뼈대, 하위 클래스에서는 객체 생성에 관한 구체적인 내용을 결정한다.

팩토리 패턴 종류
1. 단순 팩토리 패턴
2. 팩토리 메서드 패턴
3. 추상 팩토리 패턴

Java 단순 팩토리 패턴

enum CoffeeType {
	LATTE,
    ESPRESSO
}

abstract class Coffee {
	protected String name;
    
    public String getName() {
    	return name
    }
}

class Latte extends Coffee {
	public Latte() {
    	name = "latte";
    }
}

class Espresso extends Coffee {
	public Espresso() {
    	name = "Espresso"
    }
}

class CoffeeFactory {
	public static Coffee createCoffee(CoffeeType type) {
    	switch (type) {
        	case LATTE:
            	return new Latte();
            case ESPRESSO:
            	return new Espresso();
            default:
            	throw new IllegalArgumentException("Invalid coffee type: " + type);
        }
    }
}

public class Main {
	public static void main(String[] args) {
    	Coffee coffee = CoffeeFactory.createCoffee(CoffeeType.LATTE);
        System.out.println(coffee.getName()); // latte
    }
}

팩토리 밑에 객체 뼈대를 담당하는 상위 클래스를 두고, 해당 클래스를 상속하여 객체 생성을 담당하는 하위 클래스를 두어 구현한다.
팩토리에서는 생성할 객체의 타입을 받아 분기처리해서 객체를 반환한다.

단순 팩토리 패턴 장점

  1. 상위 클래스와 하위 클래스가 분리되기 때문에 느슨합 결합을 가짐
  2. 상위 클래스에서는 인스턴스 생성 방식에 대해 전혀 알 필요가 없어 더 많은 유연성을 가지게 됨
  3. 객체 생성 로직이 분리되어 있어서 유지 보수성이 증가

단순 팩토리 패턴 단점

  1. 새로운 클래스를 추가하거나 제거할 때마다 조건문을 수정해야 한다.
    SOLID의 OCP 위반 (확장에는 열려있으나, 수정에는 닫혀있어야한다.)

그래서 팩토리 메서드 패턴과 추상 팩토리 패턴이 나왔다.

구분설명장점단점
팩토리 메서드 패턴객체 생성 책임을 하위(서브) 팩토리 클래스에 위임- 새로운 클래스 추가·제거 시 클라이언트 코드 변경 없음
- 객체 생성과 사용 분리로 유연성 향상
- 객체 종류 증가 시 팩토리 클래스 수도 증가
- 구조 복잡도 상승
추상 팩토리 패턴관련된 객체의 그룹(제품군) 을 생성하는 인터페이스 제공- 제품군 일관성 유지
- 객체 생성 책임 분산으로 단일 팩토리의 과부하 방지
- 구현 복잡
- 코드량 증가

3. 전략 패턴

정책 패턴이라고도 하며, 객체의 행위를 바꾸고 싶은 경우 직접 수정하지 않고, 전략이라고 부르는 캡슐화한 알고리즘을 컨텍스트 안에서 바꿔주면서 상호 교체가 가능하게 만드는 패턴

ex) 결제 시스템 (카카오페이? 토스페이?), 로그인 방식 (서비스 내? OAuth?)

4. 옵저버 패턴

주체가 어떤 객체의 상태 변화를 관찰하다가 상태 변화가 있을 때마다 메서드 등을 통해 옵저버 목록에 있는 옵저버들에게 변화를 알려주는 패턴

주체: 객체의 상태 변화를 보고 있는 관찰자
옵저버: 객체 상태 변화에 따라 추가 변화가 생기는 객체들
ex) 유튜브 알림, 트위터 알림, MVC

  • MVC: 모델에서 변경 사항이 생겨, update로 뷰에 알려주고 컨트롤러가 작동한다.
  • 주체와 객체를 분리하는 경우와 한번에 두는 경우가 있다.
import java.util.ArrayList;
import java.util.List;

interface Subject {
	public void register(Observer obj);
    public void unregister(Observer obj);
    public void notifyObservers();
    public Object getUpdate(Observer obj);
}

interface Observer {
	public void update();
}

class Topic implements Subject {
	private List<Observer> observers;
    private String massage;
    
    public Topic() {
    	this.observers = new ArrayList<>();
        this.message = "";
    }
    
    @Override
    public void register(Observer obj) {
    	if (!observers.contains(obj)) observers.add(obj);
    }
    
    @Override
    public void unregister(Observer obj) {
    	observers.remove(obj);
    }
    
    @Override
    public void notifyObservers() {
    	this.observers.forEach(Observer::update);
    }
    
    @Override
    public Object getUpdate(Observer obj) {
    	return this.message;
    }
    
    public void postMessage(String msg) {
    	System.out.println("Message sended to Topic: " + msg);
        this.message = msg;
        notifyObservers(); // 옵저버에게 상태변화 알림
    }
}

class TopicSubscriber implements Observer {
	private String name;
    private Subject topic;
    
    public TopicSubscriber(String name, Subject topic) {
    	this.name = name;
        this.topic = topic;
    }
    
    // 구독중인 옵저버들에게 상태 변화에 대한 알림이 감
    @Override
    public void update() {
    	String msg = (String) topic.getUpdate(this);
        System.out.println(name + ":: got message >> " + msg);
    }
}

public class HelloWorld {
	public static void main(String[] args) {
    	Topic topic = new Topic();
        Observer a = new TopicSubscriber("a", topic);
        Observer b = new TopicSubscriber("b", topic);
        Observer c = new TopicSubscriber("c", topic);
        topic.register(a);
        topic.register(b);
        topic.register(c);
        
        topic.postMessage("hello everyone!");
    }
}

/*
Message sended to Topic: hello everyone!
a:: got message >> hello everyone!
b:: got message >> hello everyone!
c:: got message >> hello everyone!
*/

자바 상속과 구현
1. 상속(extends): 자식 클래스가 부모 클래스의 메서드 등을 상속받아 사용하며 자식 클래스에서 추가 및 확장을 할 수 있는 것을 말한다. 이를 통해 재사용성, 중복성의 최소화가 이루어 짐
2. 구현(implements): 부모 인터페이스를 자식 클래스에서 재정의하여 구현하는 것, 상속과는 달리 반드시 부모클래스의 메서드를 재정의하여 구현해야한다.

  • 상속은 일반 클래스, abstract 클래스 기반으로 구현, 구현은 인터페이스 기반으로 구현

5. 프록시 패턴

대상 객체에 접근하기 전에 그 접근에 대한 흐름을 가로채 해당 접근을 필터링하거나 수정하는 역할을 하는 계층이 있는 패턴
1. 객체의 속성, 변환 등을 보완
2. 보안, 데이터 검증, 캐싱, 로깅에 사용

프록시 서버

서버와 클라이언트 사이에서 클라이언트가 자신을 통해 다른 네트워크 서비스에 간접적으로 접속할 수 있게 해주는 컴퓨터 시스템이나 응용 프로그램

예시
1. nginx: 비동기 이벤트 기반의 구조와 다수의 연결을 효과적으로 처리 가능한 웹 서버
-> 서버 앞단의 프록시 서버로 활용
-> 실제 포트를 숨기고, 정적 자원 gzip으로 압축, 메인 서버 앞단에서 로깅 가능
2. CloudFlare: 웹 서버 앞단에 프록시 서버로 두어 DDOS 공격 방어나 HTTPS 구축에 쓰인다.
-> 의심스러운 트래픽은 CAPTCHA를 기반으로 일정부분 막아줌
3. CDN :각 사용자가 인터넷에 접속하는 곳과 가까운 곳에서 콘텐츠를 캐싱 또는 배포하는 서버 네트워크
-> 이를 통해 웹 서버로 부터 콘텐츠를 다운로드하는 시간을 줄일 수 있음

CORS와 프론트엔드의 프록시 서버

CORS(Cross-Origin Resource Sharing): 서버가 웹 브라우저에서 리소스를 로드할 때, 다른 오리진을 통해 로드하지 못하게 하는 HTTP 헤더 기반 메커니즘

  • 오리진: 프로토콜과 호스트 이름, 포트의 조합을 말한다.

프론트엔드 포트와 백엔드 포트가 다르면 테스팅 중 CORS 에러가 난다.
프록시 서버를 통해 프론트엔드에서 요청되는 오리진을 동일하게 수정해주면 테스팅이 진행이 된다.

6. 이터레이터 패턴

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

  • 이터레이터 프로토콜: 이터러블한 객채들을 순회할 때 쓰이는 규칙
  • 이터러블한 객체: 반복 가능한 객체로 배열을 일반화한 객체

7. 노출모듈 패턴

즉시 실행 함수를 통해 private, public 같은 접근제어자를 만드는 패턴
ex) 자바스크립트는 접근 제어자가 없어, 전역 범위에서 스크립트가 실행된다.
-> 해당 패턴으로 접근 제어자를 구현하기도 한다.

접근 제어자 정리
1. public: 클래스에 정의된 함수에서 접근 가능, 자식 클래스와 외부 클래스에서 접근 가능
2. protected: 클래스에 정의된 함수에서 접근 가능, 자식 클래스는 접근 가능, 외부 클래스는 접근 불가
3. private: 클래스에 정의된 함수에서 접근 가능, 자식 클래스와 외부 클래스에서 접근 불가
4. 즉시 실행 함수: 함수를 정의하자마자 바로 호출하는 함수, 초기화 코드, 라이브러리 내 전역 변수의 충돌 방지 등에 사용한다.

8. MVC 패턴

Model, View, Controller로 이루어진 패턴
애플리케이션의 구성 요소를 세 가지 역할로 구분하여 개발 프로세스에서 각각의 구성 요소에만 집중해서 개발할 수 있다.

  • 장점: 재사용성과 확장성이 용이
  • 단점: 애플리케이션이 복잡해질수록 모델과 뷰의 관계가 복잡

Model

  1. 애플리케이션의 데이터인 데이터베이스, 상수, 변수 등을 의미
  2. 뷰에서 데이터를 생성하거나 수정하면 컨트롤러를 통해 모델을 생성하거나 갱신

View

  1. 사용자 인터페이스 요소 -> 모델을 기반으로 사용자가 볼 수 있는 화면
  2. 모델이 가지고 있는 정보를 따로 저장X, 단순히 화면에 표시하는 정보만 가지고 있어야 한다.
  3. 변경이 일어나면 컨트롤러에 이를 전달

Controller

  1. 하나 이상의 모델과 하나 이상의 뷰를 잇는 다리 역할 -> 이벤트 등 메인 로직을 담당
  2. 모델과 뷰의 생명주기 관리, 모델이나 뷰의 변경 통지를 받으면 이를 해석해서 각각의 구성 요소에 해당 내용을 알려준다.

Spring: 디스패처 서블릿의 요청 처리 과정을 이해해보자...

MVC로부터 파생된 패턴

  • MVP 패턴
  1. MVC의 Controller가 Presenter로 교체된 패턴
  2. 뷰와 프레젠터는 일대일 관계여서 MVC 패턴보다 더 강한 결합을 지닌 패턴
  • MVVM 패턴
  1. MVC의 Controller가 뷰모델(view model)로 바뀐 패턴
  2. 뷰모델은 뷰를 더 추상화한 계층, MVC패턴과 다르게 커맨드와 데이터 바인딩을 가짐
  3. 뷰와 뷰모델 사이의 양방향 데이터 바인딩 지원, UI를 별도의 코드 수정 없이 재사용가능, 단위 테스팅하기 쉽다는 장점
  4. 값 대입만으로 변수가 변경, 양방향 바인딩, html을 토대로 컴포넌트를 구축, 재사용 가능한 컴포넌트를 기반으로 UI를 구축가능 (Vue.js)
  • 커맨드: 여러 가지 요소에 대한 처리를 하나의 액션으로 처리할 수 있게 하는 기법
  • 데이터 바인딩: 화면에 보이는 데이터와 웹 브라우저의 메모리 데이터를 일치, 뷰모델을 변경하면 뷰가 변경된다.
profile
아키텍처 설계부터 고민하는 개발자

0개의 댓글