본문 바로가기
Private/CS 정리

[CS정리] 디자인 패턴과 프로그래밍 패러다임

by CSEGR 2026. 5. 16.
728x90


[라이브러리]

개발자가 필요할 때 직접 호출해서 사용하는 기능 모음. 애플리케이션의 제어 흐름은 개발자가 가지고 있음.

[프레임워크]

애플리케이션의 전체 구조와 흐름을 미리 제공하는 틀. 프레임워크가 애플리케이션의 전체 구조와 실행 흐름을 관리하고, 개발자는 정해진 방식에 맞춰 필요한 로직만 구현함.

 

1.1 디자인 패턴

[디자인 패턴]

소프트웨어 설계 과정에서 반복적으로 발생하는 문제를 해결하기 위해 정리된 검증된 설계 방법입니다.

단순한 코드 문법이 아니라 객체와 클래스의 역할과 관계를 어떻게 구성할지에 대한 설계 구조를 의미합니다.

1.1.1. 싱글톤 패턴

[싱글톤 패턴(singleton pattern)]

하나의 클래스에 오직 하나의 인스턴스만 가지는 패턴. 보통 데이터베이스 연결 모듈에 많이 사용됨.

장) 인스턴스를 다른 모듈들이 공유하므로 인스턴스 생성 비용이 적게 듬.

단) 의존성 높음. TDD(Test Driven Development)를 할 때 걸림돌이 됨.

 

Spring 컨테이너가 Bean을 기본적으로 singleton scope로 관리합니다. 즉, @Service, @Repository, @Component 같은 Bean은 애플리케이션 실행 시 하나만 생성되고, 필요한 곳에 같은 객체가 주입됩니다.

 

TDD(Test Driven Development) : 단위 테스트를 주로함. 단위 테스트는 테스트가 서로 독립적이어야 하며 테스트를 어떤 순서로든 실행할 수 있어야 함. 하지만, 싱글톤 패턴은 미리 생성된 하나의 인스턴스를 기반으로 구현하는 패턴. 각 테스트마다 '독립적인' 인스턴스를 만들기가 어려움.

 

[의존성 주입]

싱글톤은 모듈 간의 결합을 강하게 만들 수 있음 -> 의존성 주입(DI, Dependency Injection)을 통해 모듈 간의 결합을 조금 더 느슨하게 만들어 해결 가능.

의존성(= 종속성) : A가 B에 의존성이 있다.(= B의 변경 사항에 대해 A 또한 변해야 함.)

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

메인 모듈(상위 모듈)은 하위 모듈에 대한 의존성이 떨어짐 -> '디커플링 된다.'

((의존성 주입은 상위 모듈이 하위 구현체를 직접 생성하지 않고, 외부에서 주입받게 함으로써 상위 모듈이 구체 클래스가 아닌 추상화에 의존하도록 만드는 방식이다. 따라서 상위 모듈은 하위 구현체 변경에 덜 영향을 받는다.))

class OrderService {

    private Payment payment;

    public OrderService(Payment payment) {
        this.payment = payment;
    }

    public void order() {
        payment.pay();
    }
}

장점) 모듈들을 쉽게 교체할 수 있는 구조 -> 테스팅하기 쉬움. 마이그레이션하기 수월.

단점) 모듈들이 더욱더 분리. 클래스 수가 늘어나 복잡성이 증가될 수 있음. -> 약간의 런타임 페널티 생김.

 

원칙 : "상위 모듈은 하위 모듈에서 어떠한 것도 가져오지 않아야함. 둘 다 추상화에 의존해야함."

 

1.1.2. 팩토리 패턴

[팩토리 패턴(factory pattern)]

객체 생성 과정을 캡슐화하는 디자인 패턴(객체 생성 로직을 별도의 팩토리 클래스나 메서드로 분리하여) 상속 관계에 있는 두 클래스에서 상위 클래스가 중요한 뼈대를 결정하고, 하위 클래스에서 객체 생성에 관한 구체적인 내용을 결정하는 패턴.

장점

1. 결합도가 낮음.(상위 클래스와 하위 클래스 분리)

2. 유연성 높음.(상위 클래스에서는 인스턴스 생성 방식에 대해 전혀 알 필요가 없음.)

3. 유지 보수성 증가.(객체 생성 로직이 따로 떼어져 있기 때문에 코드를 리팩터링하더라도 한 곳만 고칠 수 있게 됨.)

 

[Enum]

: 상수의 집합을 정의할 때 사용되는 타입. 예를 들어 월, 일, 색상 등의 상수 값을 담음. 상수뿐만 아니라 메서드를 집어넣어 관리할 수 있음.

코드 리팩토링시, 상수 집합에 대한 로직 수정 시 이 부분만 수정하면 된다는 장점.

본질적으로 스레드 세이프(thread safe) -> 싱글톤 패턴 만들 때 도움됨.

 

1.1.3. 전략 패턴

[전략 패턴(strategy pattern) = 정책 패턴(policy pattern)] 

: 객체의 행위를 바꾸고 싶은 경우 '직접' 수정하지 않고 전략이라고 부르는 '캡슐화한 알고리즘'을 컨텍스트 안에서 바꿔주면서 상호 교체가 가능하게 만드는 패턴. 즉, 알고리즘을 캡슐화하여 실행 중에 알고리즘을 선택할 수 있게 함.

ex) 어떤 아이템을 살 때 HANACard로 사는 것과 KAKAOCard로 사는 것을 구현. 결제 방식의 '전략'만 바꿔서 두 가지 방식으로 결제

 

1.1.4. 옵저버 패턴

[옵저버 패턴(observer pattern)]

어떤 객체(subject)의 상태 변화를 관찰하다가 상태 변화가 있을 때마다 메서드 등을 통해 옵저버 목록에 있는 옵저버들에게 변화를 알려주는 디자인 패턴. 옵저버 패턴은 주로 이벤트 기반 시스템에 사용함.

주체 : 객체의 상태 변화를 보고 있는 관찰자

옵저버 : 이 객체의 상태 변화에 따라 전달되는 메서드 등을 기반으로  '추가 변화 사항'이 생기는 객체들.

 

ex) 트위터

어떤 사람인 주체를 '팔로우'했다면 주체가 포스팅을 올리게 되면 알림이 '팔로워'에게 감.

MVC(Model-View-Controller) 패턴에도 사용됨.

모델(model)이라는 주체에서 변경 사항이 생겨 update() 메서드로 옵저버인 View에 알려주고 이를 기반으로 컨트롤러(controller) 등이 작동하는 것.

 

[상속(extends)]

자식 클래스가 부모 클래스의 메서드 등을 상속받아 사용. 자식 클래스에서 추가 및 확장을 할 수 있는 것.

-> 재사용성, 중복성의 최소화

 

[구현(implements)]

부모 인터페이스(interface)를 자식 클래스에서 재정의하여 구현하는 것. 상속과는 달리 반드시 부모 클래스의 메서드를 재정의하여 구현해야함.

 

상속 vs 구현

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

 

[프록시(proxy) 객체]

어떠한 대상의 기본적인 동작(속성 접근, 할당, 순회, 열거, 함수 호출 등)의 작업을 가로챌 수 있는 객체

 

1.1.5. 프록시 패턴과 프록시 서버

[프록시(proxy) 패턴]

대상 객체(subject)에 접근하기 전 그 접근에 대한 흐름을 가로채 대상 객체 앞단의 인터페이스 역할을 하는 디자인 패턴

 

[프록시(proxy)서버]

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

 

[nginx]

: 비동기 이벤트 기반의 구조와 다수의 연결을 효과적으로 처리 가능한 웹 서버.

 

- 사용자는 80 또는 443 포트로만 접근하고, Nginx가 내부적으로 8080 포트의 Spring Boot 서버로 요청을 전달하기 때문에 실제 애플리케이션 포트를 외부에 직접 노출하지 않을 수 있습니다.

- 또한 정적 파일을 gzip으로 압축해 전송량을 줄일 수 있음.

- access log와 error log를 통해 요청 흐름과 장애 원인을 애플리케이션 서버 앞단에서 확인할 수 있습니다.

 

[CloudFlare] - 프록시 서버로 쓰임.

: 전 세계적으로 분산된 서버가 있고 이를 통해 어떠한 시스템의 콘텐츠 전달을 빠르게 할 수 있는 CDN 서비스

- 웹 서버 앞단에 프록시 서버로 두어 DDOS 공격 방어나 HTTPS 구축에 쓰임.

- 의심스러운 트래픽인지를 먼저 판단에 CAPTCHA 등을 기반으로 이를 일정 부분 막아주는 역할 수행.

 

- DDOS 방어

: 짧은 기간 동안 네트워크에 많은 요청을 보내 네트워크 마비시켜 웹 사이트의 가용성을 방해하는 사이버 공격.

CloudFlare는 의심스러운 트래픽, 특히 사용자가 접속하는 것이 아닌 시스템을 통해 오는 트래픽을 자동 차단.

 

- HTTPS 구축

: CloudFlare를 사용하면 별도의 인증서 설치 없이 좀 더 손쉽게 HTTPS를 구축할 수 있음.

 

 

[CDN(Content Delivery Network)]

CDN(Content Delivery Network)은 전 세계 여러 지역의 서버에 정적 자원을 미리 복사해두고 가까운 서버에서 전달해주는 네트워크. 각 사용자가 인터넷에 접속하는 곳과 가까운 곳에서 콘텐츠를 캐싱 또는 배포함.

 

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

CORS(Cross-Origin Resource Sharing)

: 서버가 웹 브라우저에서 리소스를 로드할 때 다른 오리진을 통해 로드하지 못하게 하는 HTTP 헤더 기반 메커니즘.

: 프론트엔드 개발 시 프론트엔드 서버를 만들어서 백엔드 서버와 통신할 때 주로 CORS 에러를 마주치는데, 이를 해결하기 위해 프론트엔드에서 프록시 서버를 만들기도 함. 예를 들어 프론트엔드에서 127.0.0.1:3000 으로 테스팅을 하는데 백엔드 서버는 127.0.0.1:12010이라면 포트 번호가 다르기 떄문에 CORS 에러가 나타남. 이때 프록시 서버를 둬서 프론트엔드 서버에서 요청되는 오리진을 127.0.0.1:12010으로 바꿈.

 

 

- 오리진 : 프로토콜과 호스트 이름, 포트의 조합. https://cse-gr.tistory.com:80/194 라는 주소에서 오리진은 https://cse-gr.tistory.com:80을 뜻함.

 

- 127.0.0.1 : 루프백(loopback) IP로, 본인 PC 서버의 IP 뜻함. localhost나 127.0.0.01을 주소창에 입력하면 DNS를 거치지 않고 바로 본인 PC 서버로 연결됨.

 

1.1.6. 이터레이터 패턴

[이터레이터 패턴(iterator pattern)]

이터레이터(iterator)를 사용하여 컬렉션(collection)의 요소들에 접근하는 디자인 패턴.

순회할 수 있는 여러 가지 자료형의 구조와는 상관없이 이터레이터라는 하나의 인터페이스로 순회 가능.

Iterator<String> iterator = list.iterator();

while (iterator.hasNext()) {
    String value = iterator.next();
    System.out.println(value);
}

 

1.1.7.  노출모듈 패턴

[노출모듈 패턴(revealing module pattern)]

내부 구현은 숨기고, 외부에서 사용할 기능만 공개하기 위한 패턴. 주로 javaScript에 쓰임. 

 

- public : 클래스에 정의된 함수에서 접근 가능하며 자식 클래스와 외부 클래스에서 접근 가능한 범위

- protected : 클래스에 정의된 함수에서 접근 가능, 자식 클래스에서 접근 가능 하지만 외부 클래스에서 접근 불가능한 범위

- private : 클래스에 정의된 함수에서 접근 가능하지만, 자식 클래스와 외부 클래스에서 접근 불가능한 범위

 - 즉시 실행 함수 : 함수를 정의하자마자 바로 호출하는 함수. 초기화 코드 실행, 라이브러리 내 전역 변수의 충돌 방지 등에 사용.


- public : 동일 패키지 전부 , 다른 패키지 전부

- protected : 동일 패키지 전부, 다른 패키지 안됨.

- default : 동일 패키지에서 클래스 내부/ 하위 클래스 안됨. 다른 패키지 안됨.

- private : 클래스에 정의된 함수에서 접근 가능. 하위 클래스 안됨. 다른 패키지 안됨.

1.1.8.  MVC 패턴

[MVC 패턴]

: 모델(Model), 뷰(View), 컨트롤러(Controller) 로 이루어진 디자인 패턴

장점) 애플리케이션의 구성 요소를 세 가지 역할로 구분하여 개발 프로세스에서 각각의 구성 요소에만 집중해서 개발 가능. 재사용성과 확장성 용이

단점) 애플리케이션이 복잡해질수록 모델과 뷰의 관계가 복잡해짐.

 

[모델(model)]

애플리케이션의 데이터인 데이터베이스, 상수, 변수 등을 뜻함.

 

[MVC 패턴의 예 스프링]

Model Entity, DTO, Service, Repository, DB 데이터
View Thymeleaf, JSP 같은 화면 파일. 사용자 인터페이스 요소
Controller @Controller 클래스

 

1.1.9. MVP 패턴

MVP 패턴은 MVC 패턴으로부터 파생되었으며 MVC에서 C에 해당하는 컨트롤러가 프레젠터(Presenter)로 교체된 패턴

뷰와 프레젠터는 일대일 관계이기 때문에 MVC 패턴보다 더 강한 결합을 지닌 디자인 패턴.

 

 

1.1.10. MVVM 패턴

MVVM 패턴은 MVC의 C에 해당하는 컨트롤러가 뷰모델(view model)로 바뀐 패턴

뷰모델은 뷰를 더 추상화한 계층이며, MVVM 패턴은 MVC 패턴과는 다르게 커맨드와 데이터 바인딩을 가지는 것이 특징.

뷰와 뷰모델 사이의 양방향 데이터 바인딩을 지원하며 UI를 별도의 코드 수정 없이 재사용할 수 있고 단위 테스팅하기 쉽다는 장점이 있음.

 

[데이터 바인딩]

화면에 보이는 데이터와 웹 브라우저의 메모리 데이터를 일치시키는 기법. 뷰모델을 변경하면 뷰가 변경됨.

[커맨드]

여러 가지 요소에 대한 처리를 하나의 액션으로 처리할 수 있게 하는 기법.

 

 

1.2. 프로그래밍 패러다임

 

1.2.2. 객체지향 프로그래밍(OOP, Object-Oriented Programming)

데이터와 함수를 하나의 독립적인 '객체'로 묶어, 그 객체들의 상호작용을 통해 프로그램을 구성하는 방식.

객체들의 집합으로 프로그램의 상호 작용을 표현하며 데이터를 객체로 취급하여 객체 내부에 선언된 메서드를 활용하는 방식.

설계에 많은 시간이 소요되며 처리 속도가 다른 프로그래밍 패러다임에 비해 상대적으로 느림.

특징은 추상화, 캡슐화, 상속성, 다형성

 

1. 추상화(abstraction)

: 복잡한 내부 구현을 숨기고, 객체가 제공해야 하는 핵심 기능이나 역할만 드러내는 것입니다.

예를 들어 결제 시스템에서 카카오페이, 토스페이, 카드 결제는 내부 구현이 다르지만, 서비스 입장에서는 모두 “결제한다”는 공통 기능으로 바라볼 수 있습니다. 이를 PaymentService 인터페이스로 추상화하면 상위 로직은 구체 결제 방식에 의존하지 않고 결제 기능만 사용할 수 있습니다.

 

2. 캡슐화(encapsulation)

: 객체의 속성와 메서드를 하나로 묶고, 내부 상태를 외부에서 직접 수정하지 못하도록 제한하는 것입니다.

Java에서는 필드를 private으로 숨기고, 필요한 메서드만 public으로 제공하여 객체의 상태 변경을 통제합니다. 이를 통해 잘못된 값이 들어오는 것을 막고, 객체의 무결성을 유지할 수 있습니다.

 

3. 상속성(inheritance)

: 상위 클래스의 특성을 하위 클래스가 이어받아서 재사용하거나 추가, 확장하는 것을 의미.

공통 기능을 부모 클래스에 두고, 자식 클래스는 이를 상속받아 중복을 줄일 수 있습니다. 코드의 재사용 측면, 계층적인 관계 생성, 유지 보수성 측면에서 중요

 

4. 다형성(polymorphism)

: 하나의 메서드나 클래스가 다양한 방법으로 동작하는 것.

Java에서는 상속과 오버라이딩, 인터페이스 구현을 통해 다형성을 구현할 수 있습니다.

예를 들어 PaymentService 인터페이스를 두고 KakaoPayService, TossPayService가 이를 구현하면, 주문 서비스는 구체 결제 방식에 의존하지 않고 PaymentService 타입으로 결제 기능을 사용할 수 있습니다. 이를 통해 새로운 구현체를 추가해도 기존 코드를 크게 수정하지 않아도 됩니다.

 

[오버로딩(overloading)]

: 같은 이름을 가진 메서드를 여러 개 두는 것. 메서드의 타입, 매개변수의 유형 및 개수 등으로 여러 개를 둘 수 있음. 컴파일 중에 발생하는 '정적' 다형성

public class Calculator {
    public int add(int a, int b) {
        return a + b;
    }

    public int add(int a, int b, int c) {
        return a + b + c;
    }
}

 

 

 [오버라이딩(overriding)]

: 주로 메서드 오버라이딩(method overriding)을 말하며 상위 클래스로부터 상속받은 메서드를 하위 클래스가 재정의하는 것을 의미. 컴파일 중에 발생하는 '동적' 다형성. 즉, 부모가 가진 기능을 자식 클래스가 자기 방식대로 재정의하는 것

class Animal {
    void makeSound() {
        System.out.println("동물이 소리를 냅니다.");
    }
}

// 자식 클래스 1
class Dog extends Animal {
    @Override // 오버라이딩임을 명시 (생략 가능하나 권장)
    void makeSound() {
        System.out.println("멍멍!");
    }
}

// 자식 클래스 2
class Cat extends Animal {
    @Override
    void makeSound() {
        System.out.println("야옹~");
    }
}

public class Main {
    public static void main(String[] args) {
        Animal myDog = new Dog();
        Animal myCat = new Cat();

        myDog.makeSound(); // 출력: 멍멍!
        myCat.makeSound(); // 출력: 야옹~
    }

 

[설계 원칙 : SOLID]

객체지향 프로그래밍을 설계할 때는 SOLID 원칙을 지켜주어야함.

S는 단일 책임 원칙, O 개방-폐쇄 원칙, L은 리스코프 치환 원칙, I는 인터페이스 분리 원칙, D는 의존 역전 원칙

 

1. 단일 책임 원칙(SRP, Single Responsibility Principle)

: 모든 클래스는 각각 하나의 책임만 가져야하는 원칙.

A라는 로직이 존재한다면 어떠한 클래스는 A에 관한 클래스여야 하고 이를 수정한다고 했을 때도 A와 관련된 수정이어야함.

 

예를 들어 회원 관리 클래스가 로그인도 하고, 이메일도 보내고, 로그도 저장하면 수정 이유가 너무 많아집니다. 로그인 정책이 바뀌어도 수정해야 하고, 이메일 정책이 바뀌어도 수정해야 하기 때문입니다. 그래서 로그인은 로그인 클래스, 이메일은 이메일 클래스처럼 역할별로 분리하는 것이 단일 책임 원칙입니다.

 

2. 개방-폐쇄 원칙(OCP, Open Closed Principle)

: 유지 보수 사항이 생긴다면 코드를 쉽게 확장할 수 있도록 하고 수정할 때는 닫혀 있어야 하는 원칙. 기존의 코드는 잘 변경하지 않으면서도 확장은 쉽게 할 수 있어야함.

 

예를 들어 결제 시스템에서 처음에는 카카오페이만 지원했는데, 나중에 토스페이나 네이버페이를 추가해야 할 수 있습니다. 이때 기존 주문 서비스 코드를 계속 수정하는 구조라면 변경에 취약합니다. 그래서 결제 인터페이스를 두고 새로운 결제 방식은 구현체만 추가하도록 만들면 기존 코드는 수정하지 않고 기능만 확장할 수 있습니다.

 

3. 리스코프 치환 원칙(LSP, Liskov Substitution Principle)

: 부모 타입의 객체를 사용하는 코드에서 자식 타입의 객체로 대체해도 프로그램의 정상 동작이 깨지지 않아야 한다는 원칙

 

예를 들어 Bird 클래스에 “난다”라는 기능이 있는데, Penguin이 Bird를 상속받고 “저는 못 납니다”라고 예외를 던지면 문제가 됩니다. Bird 타입이면 당연히 날 수 있다고 기대했는데, Penguin을 넣었더니 기존 코드가 깨지기 때문입니다. 즉, 자식 클래스는 부모 클래스가 기대하는 동작을 깨면 안 된다는 원칙입니다.

 

4. 인터페이스 분리 원칙(ISP, Interface Segregation Principle)

: 하나의 일반적인 인터페이스보다 역할별로 작은 인터페이스로 나누는 원칙

 

예를 들어 프린터 인터페이스에 출력, 스캔, 팩스 기능이 모두 들어있다면 단순 프린터도 스캔과 팩스 기능을 억지로 구현해야 합니다. 그래서 출력 기능은 Printable, 스캔 기능은 Scannable처럼 역할별 인터페이스로 나누면 필요한 기능만 구현할 수 있습니다.

 

5. 의존 역전 원칙(DIP, Dependency Inversion Principle)

: 상위 비즈니스 모듈이 하위 구현체에 직접 의존하지 않고, 인터페이스 같은 추상화에 의존하도록 만드는 원칙.

 

예를 들어 주문 서비스가 카카오페이 클래스에 직접 의존하면 나중에 토스페이로 변경할 때 주문 서비스 코드도 수정해야 합니다. 그래서 중간에 결제 인터페이스를 두고 주문 서비스는 인터페이스에만 의존하게 만들면, 실제 결제 구현체는 바꿔 끼울 수 있게 됩니다. 즉, 중요한 비즈니스 로직이 구체 구현체에 직접 묶이지 않도록 만드는 원칙입니다.

 

 

728x90

'Private > CS 정리' 카테고리의 다른 글

[CS 정리] 네트워크 (+ 질문)  (0) 2026.04.20