ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [CS] 디자인 패턴과 프로그래밍 패러다임
    CS 2024. 10. 22. 17:37

    디자인 패턴

    • 디자인 패턴이란 프로그램을 설계할 때 발생했던 문제점들을 객체 간의 상호 관계 등을 이용하여 해결할 수 있도록 하나의 ‘규약’ 형태로 만들어 놓은 것이다.

    싱글톤 패턴

    • 싱글톤 패턴은 하나의 클래스에 오직 하나의 인스턴스만 가지는 패턴이다.
      • 보통 데이터베이스 연결 모듈에 많이 사용한다.
    • 예시 코드
    • class Singleton { private static class singleInstanceHolder { private static final Singleton INSTANCE = new Singleton(); } public static Singleton getInstance() { return singleInstanceHolder.INSTANCE; } } public class HelloWorld { public static void main(String[] args) { Singleton a = Singleton.getInstance(); Singleton b = Singleton.getInstance(); System.out.println(a.hashCode()); System.out.println(b.hashCode()); if (a == b) { System.out.println(true); } } }
    • 장점 하나의 인스턴스를 만들어 놓고 해당 인스턴스를 다른 모듈들이 공유하며 사용하기 때문에 인스턴스를 생성할 때 드는 비용이 줄어든다.
    • 단점
      • 단위 테스트에 걸림돌이 된다.
        • 단위 테스트는 테스트가 서로 독립적이어야 하며 테스트를 어떤 순서로든 실행할 수 있어야 한다. 하지만 싱글톤 패턴은 미리 생성된 하나의 인스턴스를 기반으로 구현하는 패턴이므로 각 테스트마다 독립적인 인스턴스를 만들기가 어렵다.
      • 의존성이 강해진다. (= 모듈 간의 결합을 강하게 만들 수 있다.)

    의존성 주입

    • DI (Dependency Injection)
    • 의존성 주입으로 모듈 간의 결합을 느슨하게 만들어 싱글톤 패턴의 단점 해결 가능.

    A가 B에 의존성이 있다 == B의 변경 사항에 대해 A 또한 변해야 한다.

    A가 B에 의존성이 있다 == B의 변경 사항에 대해 A 또한 변해야 한다.

    • 메인 모듈이 직접 다른 하위 모듈에 대한 의존성을 주입 → 의존성 주입자(dependency injector)가 간접적으로 의존성을 주입하는 방식
      • 메인 모듈(상위 모듈)은 하위 모듈에 대한 의존성이 떨어지게 됨 (= 디커플링 된다)

    장점

    • 모듈들을 쉽게 교체할 수 있는 구조가 되어 테스팅하기 쉽고 마이그레이션 하기도 수월함.
    • 구현할 때 추상화 레이어를 넣고 이를 기반으로 구현체를 넣어 주기 때문에 애플리케이션 의존성 방향이 일관되고, 애플리케이션을 쉽게 추론할 수 있으며, 모듈 간의 관계들이 조금 더 명확해짐.

    단점

    • 모듈들이 더욱더 분리되므로 클래스 수가 늘어나 복잡성이 증가될 수 있으며, 약간의 런타임 패널티가 생기기도 한다.

    원칙

    • 상위 모듈은 하위 모듈에서 어떠한 것도 가져오지 않아야 한다.
    • 상위 모듈과 하위 모듈 둘 다 추상화에 의존해야 하며, 이때 추상화는 세부 사항에 의존하지 말아야 한다.

    팩토리 패턴

    • 팩토리 패턴은 객체를 사용하는 코드에서 객체 생성 부분을 떼어내 추상화한 패턴이자 상속 관계에 있는 두 클래스에서 상위 클래스가 중요한 뼈대를 결정하고, 하위 클래스에서 객체 생성에 관한 구체적인 내용을 결정하는 패턴이다.
    • 상위 클래스와 하위 클래스가 분리되기 때문에 느슨한 결합을 가지며 상위 클래스에서는 인스턴스 생성 방식에 대해 전혀 알 필요가 없기 때문에 더 많은 유연성을 갖게 된다.
    • 객체 생성 로직이 따로 떼어져 있기 때문에 코드를 리팩터링하더라도 한 곳만 고칠 수 있게 되어 유지 보수성이 증가된다.
    • 예시 코드
    • enum CoffeeType { LATTE, ESPRESO } abstract class Coffee { protected String name; public String getName() { return name; } } class Lattee extends Coffee { public Lattee() { 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 } }

    전략 패턴

    • 전략 패턴은 객체의 행위를 바꾸고 싶은 경우 ‘직접’ 수정하지 않고 전략이라고 부르는 ‘캡슐화한 알고리즘’을 컨텍스트 안에서 바꿔주면서 상호 교체가 가능하게 만드는 패턴이다.
      • 컨텍스트: 상황, 맥락, 문맥을 의미하며 개발자가 어떠한 작업을 완료하는 데 필요한 모든 관련 정보
    • 정책 패턴이라고도 함

    옵저버 패턴

    • 주체가 어떤 객체의 상태 변화를 관찰하다가 상태 변화가 있을 때마다 메서드 등을 통해 옵저버 목록에 있는 옵저버들에게 변화를 알려주는 디자인 패턴이다.
      • 주체: 객체의 상태 변화를 보고 있는 관찰자
      • 옵저버: 이 객체의 상태 변화에 따라 전달되는 메서드 등을 기반으로 ‘추가 변화 사항’이 생기는 객체들
    • 주로 이벤트 기반 시스템에 사용한다. MVC 패턴에도 사용된다.
      • 예시 주체라고 볼 수 있는 모델에서 변경 사항이 생겨 update() 메서드로 옵저버인 뷰에 알려주고 이를 기반으로 컨트롤러 등이 동작하는 것이다.
    • 예시 코드
    enum CoffeeType {
        LATTE,
        ESPRESO
    }
    
    abstract class Coffee {
        protected String name;
        
        public String getName() {
            return name;
        }
    }
    
    class Lattee extends Coffee {
        public Lattee() {
            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
        }
    }

    자바의 상속 (extends)

    • 상속은 자식 클래스가 부모 클래스의 메서드 등을 상속받아 사용하며 자식 클래스에서 추가 및 확장을 할 수 있는 것
    • 재사용성, 중복성의 최소화

    자바의 구현 (implements)

    • 구현은 부모 인터페이스를 자식 클래스에서 재정의하여 구현하는 것을 의미한다.
    • 상속과는 달리 반드시 부모 클래스의 메서드를 재정의하여 구현해야 한다.

    → 상속은 일반 클래스, abstract 클래스를 기반으로 구현하며, 구현은 인터페이스를 기반으로 구현한다.

    프록시 패턴과 프록시 서버

    • 프록시 패턴은 대상 객체에 접근하기 전 그 접근에 대한 흐름을 가로채 해당 접근을 필터링하거나 수정하는 등의 역할을 하는 계층이 있는 디자인 패턴이다.
    • 이를 통해 객체의 속성, 변환 등을 보완하며 보안, 데이터 검증, 캐싱, 로깅에 사용한다.

    프록시 서버

    • 서버와 클라이언트 사이에서 클라이언트가 자신을 통해 다른 네트워크 서비스에 간접적으로 접속할 수 있게 해주는 컴퓨터 시스템이나 응용 프로그램을 가리킨다.
    • nginx
      • 비동기 이벤트 기반의 구조와 다수의 연결을 효과적으로 처리 가능한 웹 서버이다.
      • 서버를 구축할 때 앞단에 nginx를 두면, 익명 사용자가 직접적으로 서버에 접근하는 것을 차단하고, 간접적으로 한 단계를 더 거치게 만들어 보안을 강화할 수 있다.
        • nginx를 프록시 서버로 둬서 실제 포트를 숨길 수 있다.
    • CloudFlare
      • 전 세계적으로 분산된 서버가 있고 이를 통해 어떠한 시스템의 콘텐츠 전달을 빠르게 할 수 있는 CDN 서비스이다.
        • CDN(Content Delivery Network): 각 사용자가 인터넷에 접속하는 곳과 가까운 곳에서 콘텐츠를 캐싱 또는 배포하는 서버 네트워크를 말한다. 이를 통해 사용자가 웹 서버로부터 콘텐츠를 다운로드하는 시간을 줄일 수 있다.
      • DDOS 공격 방어
        • 의심스러운 트래픽(특히 사람이 아닌 시스템을 통해 오는 트래픽)을 자동으로 차단해서 DDOS 공격으로부터 보호한다.
          • DDOS: 짧은 시간 동안 네트워크에 많은 요청을 보내 네트워크를 마비시켜 웹 사이트의 가용성을 방해하는 사이버 공격 유형
      • HTTPS 구축
        • HTTPS 구축할 때 인증서 기반으로 구축할 수도 있지만, CloudFlare를 사용하면 별도의 인증서 설치 없이 좀 더 쉽게 HTTPS를 구축할 수 있다.
    • CORS와 프런트엔드의 프록시 서버
      • Cross-Origin-Resource-Sharing: 서버가 웹 브라우저에서 리소스를 로드할 때 다른 오리진을 통해 로드하지 못하게 하는 HTTP 헤더 기반 메커니즘
      • 예시
      • 프런트엔드에서는 127.0.0.1:3000으로 테스팅을 하는데 백엔드 서버는 127.0.0.1:12010이라면 포트 번호가 다르기 때문에 CORS 에러가 나타난다. 이때 프록시 서버를 둬서 프런트엔드 서버에서 요청되는 오리진을 127.0.0.1:12010으로 바꾼다.

    [프록시 서버에서의 캐싱]

    • 캐시 안에 정보를 담아두고, 캐시 안에 있는 정보를 요구하는 요청에 대해 다시 멀리 있는 원격 서버에 요청하지 않고 캐시 안에 있는 데이터를 활용하는 것을 말한다.
    • 불필요하게 외부와 연결하지 않기 때문에 트래픽을 줄일 수 있다는 장점이 있다.

    이터레이터 패턴

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

    MVC 패턴

    • 애플리케이션의 구성 요소를 세 가지 역할로 구분하여 개발 프로세스에서 각각의 구성 요소에만 집중해서 개발할 수 있다.
    • 장점 재사용성과 확장성이 용이하다.
    • 단점 애플리케이션이 복잡해질수록 모델과 뷰의 관계가 복잡해진다.

    모델

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

    • 사용자 인터페이스 요소를 나타낸다.
      • 모델을 기반으로 사용자가 볼 수 있는 화면
    • 모델이 가지고 있는 정보를 따로 저장하지 않아야 한다.
    • 변경이 일어나면 컨트롤러에 이를 전달해야 한다.

    컨트롤러

    • 하나 이상의 모델과 하나 이상의 뷰를 잇는 다리 역할을 한다.
    • 메인 로직을 담당한다.
    • 모델과 뷰의 생명주기를 관리한다.
    • 모델이나 뷰의 변경 통지를 받으면 이를 해석하여 각각의 구성 요소에 해당 내용에 대해 알려준다.

    MVP 패턴

    • MVC 패턴의 컨트롤러가 프레젠터로 교체된 패턴이다.
    • 뷰와 프레젠터는 일대일 관계이기 때문에 MVC 패턴보다 더 강한 결합을 가진다.

    MVVM 패턴

    • MVC 패턴의 컨트롤러가 뷰모델(view model)로 바뀐 패턴이다.
    • 뷰모델은 뷰를 더 추상화한 계층이다.
    • MVC 패턴과 다르게 커맨드와 데이터 바인딩을 가지는 것이 특징이다.
    • 뷰와 뷰모델 사이의 양방향 데이터 바인딩을 지원하며 UI를 별도의 수정 없이 재사용할 수 있고 단위 테스팅하기 쉽다는 장점이 있다.
      • 커맨드: 여러 가지 요소에 대한 처리를 하나의 액션으로 처리할 수 있게 하는 기법이다.
      • 데이터 바인딩: 화면에 보이는 데이터와 웹 브라우저의 메모리 데이터를 일치시키는 기법으로, 뷰모델을 변경하면 뷰가 변경된다.

    프로그래밍 패러다임

    • 프로그래머에게 프로그래밍의 관점을 갖게 해주는 역할을 하는 개발 방법론
    • jdk 1.8 이전의 자바는 객체지향 프로그래밍을 지원했다. 하지만, 1.8 이후로 함수형 프로그래밍 패러다임을 지원하기 위해서 람다식, 생성자 레퍼런스, 메서드 레퍼런스를 도입했고 선언형 프로그래밍을 위해 스트림(stream) 같은 표준 API 등도 추가했다.
    • 프로그래밍 패러다임은 크게 선언형, 명령형으로 나눈다.
      • 선언형은 함수형이라는 하위 집합을 갖는다.
      • 명령형은 객체지향, 절차지향으로 나눈다.

    선언형과 함수형 프로그래밍

    • ‘무엇을’ 풀어내는가에 집중하는 패러다임이다.
    • 선언형 패러다임의 일종이다.

    함수형 프로그래밍

    • 작은 ‘순수 함수’들을 블록처럼 쌓아 로직을 구현하고 ‘고차 함수’를 통해 재사용성을 높인 프로그래밍 패러다임 → 자바스크립트에 적합

    순수함수

    • 출력이 입력에만 의존하는 것

    고차 함수

    • 함수가 함수를 값처럼 매개변수로 받아 로직을 생성할 수 있는 것

    일급 객체

    • 고차 함수를 쓰기 위해서는 해당 언어가 일급 객체라는 특징을 가져야 한다.
      • 변수나 메서드에 함수를 할당할 수 있다.
      • 함수 안에 함수를 매개변수로 담을 수 있다.
      • 함수가 함수를 반환할 수 있다.

    객체지향 프로그래밍

    OOP(Object-Oriented Programming)

    • 객체들의 집합으로 프로그램의 상호 작용을 표현하며, 데이터를 객체로 취급하여 객체 내부에 선언된 메서드를 활용하는 방식
    • 설계에 많은 시간이 소요되며 처리 속도가 다른 프로그래밍 패러다임에 비해 상대적으로 느리다.

    객체지향 프로그래밍의 특징

    1. 추상화
      1. 복잡한 시스템으로부터 핵심적인 개념 또는 기능을 간추려내는 것
    2. 캡슐화
      1. 객체의 속성과 메서드를 하나로 묶고 일부를 외부에 감추어 은닉하는 것
    3. 상속성
      1. 상위 클래스의 특성을 하위 클래스가 이어받아서 재사용하거나 추가, 확장하는 것
      2. 코드의 재사용 측면, 계층적인 관계 생성, 유지보수성 측면에서 중요
    4. 다형성
      1. 하나의 메서드나 클래스가 다양한 방법으로 동작하는 것
      2. 오버로딩
        1. 같은 이름을 가진 메서드를 여러 개 두는 것
        2. 메서드의 타입, 매개변수의 유형, 개수 등으로 여러 개를 둘 수 있으며 컴파일 중에 발생하는 ‘정적’ 다형성
      3. 오버라이딩
        1. 상위 클래스로부터 상속받은 메서드를 하위 클래스가 재정의하는 것
        2. 런타임 중에 발생하는 ‘동적’ 다형성

    설계 원칙

    객체지향 프로그래밍을 설계할 때 지켜야 할 SOLID 원칙

    단일 책임 원칙 (SRP; Single Responsibility Principle)

    • 모든 클래스는 각각 하나의 책임만 가져야 한다.

    개방-폐쇄 원칙 (OCP; Open Closed Principle)

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

    리스코프 치환 원칙 (LSP; Liskov Substitution Principle)

    • 프로그램의 객체는 프로그램의 정확성을 깨뜨리지 않으면서 하위 타입의 인스턴스로 바꿀 수 있어야 한다.
    • → 부모 객체에 자식 객체를 넣어도 시스템이 문제없이 돌아가게 만드는 것

    인터페이스 분리 원칙 (ISP; Interface Segregation Principle)

    • 하나의 일반적인 인터페이스보다 구체적인 여러 개의 인터페이스를 만들어야 한다.

    의존 역전 원칙 (DIP; Dependency Inversion Principle)

    • 자신보다 변하기 쉬운 것에 의존하던 것을 추상화된 인터페이스나 상위 클래스를 두어 변하기 쉬운 것의 변화에 영향받지 않게 하는 원칙
    • 상위 계층은 하위 계층의 변화에 대한 구현으로부터 독립해야 한다.

    절차형 프로그래밍

    • 로직이 수행되어야 할 연속적인 계산 과정으로 이루어져 있다.
    • 일이 진행되는 방식으로 그저 코드를 구현하기만 하면 되기 때문에 코드의 가독성이 좋으며 실행 속도가 빠르다. → 계산이 많은 작업 등에 쓰인다.
    • 하지만, 모듈화하기가 어렵고 유지 보수성이 떨어진다.

    패러다임의 혼합

    • 어떤 패러다임이 가장 좋은가에 대한 답은 없다.
    • 비즈니스 로직이나 서비스의 특징을 고려해서 패러다임을 정하는 것이 좋다.
    • ex) 한 프로젝트 내에서도 머신 러닝 파이프라인은 절차지향형 & 거래 관련 로직은 함수형 프로그래밍 적용
Designed by Tistory.