본문 바로가기
개발/기타

[CS Study] 1. 디자인 패턴과 프로그래밍 패러다임 (2)

by 카펀 2022. 8. 1.

# 본 글은 인하대학교 단풍나무숲 CS 스터디에 작성한 내용을 그대로 가져온 글입니다.

디자인 패턴과 프로그래밍 패러다임(1)

목차

  1. MVC 패턴
  2. MVP 패턴
  3. MVVM 패턴
  4. 선언형과 함수형 프로그래밍
  5. 객체지향 프로그래밍
  6. 절차형 프로그래밍

1. MVC 패턴

정의

Model, View, Controller로 애플리케이션의 구성 요소를 구분하여, 각각의 기능에 집중하는 디자인 패턴

상세

Model, View, Controller는 각각 무엇을 뜻할까? 주로 아래와 같은 기준으로 구분된다.

  • Model: 애플리케이션을 구성하는 정보. DB 등의 데이터 계층을 나타냄
    • 데이터와 관련된 책임을 가진다
    • 비지니스 로직을 처리한다
  • View: 사용자에게 보여지는 화면, UI를 나타냄
    • Model이 처리한 데이터를 받아서 합산한다
    • 데이터, 로직은 없어야 한다
  • Controller: Model과 View를 연결해 주는 중개자 레이어
    • 사용자의 요청에 맞는 서비스를 실행 (Model에서 진행됨)
    • Model에서 처리한 값을 View에 전달
    • Spring의 Controller, Service, Dao, Dto, Repository는 이 계층에 해당
  • Controller를 제외한 View, Model은 다른 레이어를 알아서는 안 된다.
  • 장점:
    • 역할의 분리, 단일 책임 원칙에 따르도록 개발할 수 있다.
    • FE, BE 개발자가 각각 따로 개발할 수 있다.
  • 단점:
    • Model과 View 사이의 의존성이 높아진다. 높은 의존성은 애플리케이션이 커질수록 유지보수를 어렵게 한다.

관련 자료

2. MVP 패턴

정의

MVC 패턴에서 파생된 패턴으로, Controller 대신 Presenter로 교체된 패턴

상세

  • Presenter는 기존 MVC 패턴의 단점이었던 Model과 View 사이의 높은 의존성을 해소하기 위하여 등장
  • Controller와 유사한 역할. View에서 온 요쳥을 가공하여 Model에 전달하고, Model로부터 넘겨받은 데이터를 View에 뿌려 준다
  • Presenter와 View는 1:1 관계이다. 따라서 기존의 View-Controller보다 View-Presenter는 더 강한 결합을 가진다.
  • 장점:
    • View와 Model은 서로 의존하지 않음
  • 단점:
    • 기존의 MVC 패턴에 비해 View-Presenter의 의존성이 강해짐

관련 자료

3. MVVM 패턴

정의

MVC 패턴에서 파생된 패턴으로, Controller 대신 View Model로 교체된 패턴

상세

View Model은 기존의 View를 더욱 추상화 시킨 계층이다.
View Model과 View는 1:N 관계이며, 두 계층은 낮은 의존성을 가진다.

  • 장점:
    • View와 View Model 사이에 양방향 데이터 바인딩을 지원한다
    • UI를 별도의 코드 수정 없이 재사용할 수 있다
    • 각 부분은 독립적이기 때문에 모듈화 하여 개발할 수 있다
      • 따라서 단위테스트가 쉬워진다
  • 단점:
    • View Model의 설계가 어려움
  • 사용 예: Vue.js

3.1 MVC, MVP, MVVM의 구체적 차이

1. MVC vs. MVVM

  MVC MVVM
진입점 애플리케이션의 진입점은 Controller 애플리케이션의 진입점은 View
View와의 관계 Controller 1 : N Views View 1 : N View Models
View의 참조 관계 View에 Controller에 대한 참조가 없음 (Model, View는 다른 레이어를 모름) View에 View Model에 대한 참조가 존재
단점 단위 테스트가 어려움 데이터 바인딩이 복잡해질수록 디버깅이 복잡해짐
Model 컴포넌트를 사용자와 별도로 테스트 가능 단위 테스트가 쉽고, 코드가 이벤트 기반임  

2. MVC의 강한 의존성

  • 분명 Model, View는 다른 레이어를 모른다. 그런데 왜 서로간 의존이 강할까?
    • 다수의 Model, View가 하나의 Controller를 통해 소통하기 떄문에, 서로가 완전히 독립적이라고 할 수 없다.
    • 따라서 Controller 하나에 다수의 Model, View가 의존하여 얽혀 있는 경우가 발생할 수 있고 (Controller는 Model, View에 대한 정보를 알고 있다), 애플리케이션의 규모가 커질수록 이런 면에서 어려움이 두드러진다.
  • 출처

4. 선언형과 함수형 프로그래밍 (+ 프로그래밍 패러다임)

프로그래밍 패러다임

프로그래머에게 프로그래밍 관점을 갖게 해주는 개발 방법론.
아래와 같은 패러다임이 존재한다.

  • 객체지향 프로그래밍
  • 함수형 프로그래밍
  • 절차형 프로그래밍
  • 등등...

크게 선언형, 명령형으로 나뉘며, 선언형은 함수형 등으로 세분화된다. 명령형은 객체지향, 절차지향 등으로 세분화된다.

정의

선언형 프로그래밍 (declarative programming)이란, 무엇을 풀어내는가에 집중하는 패러다임이다. 함수형 프로그래밍은 선언형 프로그래밍의 일종이다.

상세

아래와 같은 예시 코드가 있다.

public class Calc {

    public int getMax(List<int> num) {

        int result = 0;

        for (int data : num) {
            result = Math.max(result, data);
        }

        return result;
    }
}

위 코드에서 메소드 getMax()는 "숫자 리스트" 만 받아서, 리스트 내의 정수 최댓값을 리턴한다.
함수형 프로그래밍은, 이와 같이 작은 '순수 함수'들을 블록처럼 쌓아 로직을 구현하고, '고차 함수'를 통해 재사용성을 높인 프로그래밍 패러다임이다.
1.8 이후의 Java, Kotlin, JavaScript 등이 함수형 프로그래밍을 지원한다.

순수 함수

출력이 입력에만 의존하는 함수를 의미한다.
앞서 살펴본 getMax() 메소드 역시 순수 함수이다.

고차 함수

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

5. 객체지향 프로그래밍

정의

OOP (Object-Oriented Programming)은, 애플리케이션을 객체들의 집합으로 본다.
여기서 객체란, 실제 세계에 존재하는 개념을 코드로 나타낸 것이다.
설계에 많은 시간이 소요되며, 처리 속도가 다른 프로그래밍 패러다임에 비해 상대적으로 느리다.

상세

아래와 같은 예시 코드가 있다.

public class Calc {

    private int maxVal;

    public Calc(List<int> num) {

        this.maxVal = 0;

        for (int data : num) {
            this.maxVal = Math.max(this.maxVal, data);
        }
    }

    public int getMaxVal() {
        return this.maxVal;
    }
}

위 코드에는 객체지향 프로그래밍의 여러 가지 내용이 포함되어 있다.

  • maxVal은 private이다. 따라서 외부에서는 maxVal에 직접 접근할 수 없다.
  • Calc라는 클래스가 호출될 때, 생성자는 파라미터로 받은 숫자 리스트 num 중에서 최댓값을 찾고, 그 값을 maxVal에 할당한다.
  • maxVal의 값은 메소드 getMaxVal()을 통해서만 접근할 수 있다.

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

아래와 같은 핵심 개념을 가진다.

  • 추상화 (abstraction)
  • 캡슐화 (encapsulation)
  • 상속성 (inheritance)
  • 다형성 (polymorphism)
  • 추상화란, 특정 개념을 구체화 시키지 않고 모호화 하는 것
    • 구현체를 쉽게 교체할 수 있다는 장점이 있다
    • 아래 코드를 참고하자.
    •   public interface Pet {
            public void walk();
        }
      
        public class Dog implements Pet {
      
            private String name;
      
            @Override
            public void walk() {
                // 대충 개를 산책시키는 방법
            }
        }
      
        public class Cat implements Pet {
      
            private String name;
      
            @Override
            public void walk() {
                // 대충 고양이를 산책시키는 방법
            }
        }
    • 개를 키울지, 고양이를 키울지는 못 정했지만, 애완동물은 키우기로 정했다면, 먼저 인터페이스 (애완동물을 키울 때 산책을 시킬 것)를 정의하고, 실제 구현체 (개 또는 고양이) 는 추후에 구현하면 된다.
    • 또는, 구현체를 언제든지 쉽게 교체할 수 있다 (실제 애완동물은 그러면 안 된다.)
  • 캡슐화란, 객체의 속성과 메소드를 하나의 클래스로 묶고, 필요한 부분 외에는 외부에 노출하지 않는 것을 의미한다.
    • 마치 리모콘과 같은데, 필요한 기능은 버튼으로 외부에 노출되어 있고, 그 외에 기판 등 밖에 보여질 필요가 없는 내용은 숨겨져 있다.
  • 상속성이란, 상위 (부모) 클래스의 특성을 하위 (자식) 클래스가 이어받아서 재사용하는 것이다.
    • 아래 코드를 참고하자.
    • public class Animal {
      
          private String name;
      
          public void setName(String name) {
              this.name = name;
          }
      
          public String getName() {
              return this.name;
          }
      }
      
      public class Cat extends Animal {
      
          private String color;
          private int age;
      
          // 이하 getter, setter가 있다고 가정
      }
      • 위 코드에서, '모든 동물은 이름이 있다' 라는 전제 하에, Animal 클래스는 모든 동물의 기본 사항만을 포함함
      • 그 외에 고양이에게만 있는 내용은 색깔과 나이라고 가정하고, Cat 클래스는 Animal 클래스에는 없는 변수와 메소드를 가짐
      • 그럼에도 고양이 역시 name과 set, get 메소드를 사용할 수 있음
  • 다형성이란, 같은 이름을 가진 메소드가 여러 개 존재할 수 있는 것을 의미
    • 크게 오버로딩, 오버라이딩이 있다
    • 오버로딩
      • 매개변수에 따라 같은 이름을 가진 함수가 구분되는 것
      •   public class Calculator {
        
              public int calc (int num) {
                  return num*num;
              }
        
              public int calc (int num1, int num2) {
                  return num1 * num2;
              }
          }
        
          // calc(3) = 9
          // calc(3, 4) = 12
    • 오버라이딩
      • 자식 클래스가 부모 클래스의 메소드를 재정의하는 것
      •   public class Animal {
        
              private String name;
        
              public void cry() {
                  printf(name + " 응애");
              }
          }
        
          public class Cat extends Animal {
        
              @Override
              public void cry() {
                  printf(name + " 야옹");
              }
          }
      • 오버라이딩은 런타임 중에 발생함 (동적 다형성))

객체지향 설계 원칙 SOLID

객체지향 프로그래밍을 설계할 때 지켜야 하는 5가지 원칙이 있다.

  • S: 단일 책임 원칙 (SRP; Single Responsibility Principle)
    • 모든 클래스는 반드시 단 하나의 책임만을 가져야 한다.
  • O: 개방 폐쇄 원칙 (OCP; Open-Closed Principle)
    • 모든 클래스는, 확장에는 열려 있고 수정에는 닫혀 있어야 한다.
    • 즉, 기존의 코드는 건드리지 않으며 신규 기능을 확장할 수 있어야 한다.
  • L: 리스코프 치환 원칙 (LSP; Liskov Substitution Principle)
    • 프로그램의 객체는, 프로그램의 정확성을 깨뜨리지 않으며 하위 타임의 인스턴스로 바꿀 수 있어야 한다.
    • 즉, 부모 객체 대신 자식 객체를 넣어도 기능이 동작해야 한다.
  • I: 인터페이스 분리 원칙 (ISP; Interface Segregation Principle)
    • 하나의 일반적인 (general) 인터페이스보다, 여러 개의 구체적인 (specific) 인터페이스를 만들어야 한다.
  • D: 의존 역전 원칙 (DIP; Dependency Inversion Principle)

6. 절차형 프로그래밍

정의

수행되어야 할 로직을 따라 코드가 작성되는 패러다임

상세

  • 수행해야 하는 일을 순차적으로 작성하면 되므로, 코드를 작성하기에 빠르며 실행 속도도 빠르다.
  • 따라서 계산이 많은 작업 등에 유리하다 (Fortran 등이 절차형을 사용)
  • 모듈화하기 어렵고, 유지 보수성이 떨어진다
  • 예시 코드
    •   public Class Procedure {
      
            public int proceduremax() {
                List<int> num = new LinkedList<int>(Arrays.asList(1, 2, 3, 4, 12));
      
                int maxVal = 0;
                for (int data : num) {
                    maxVal = Math.max(maxVal, data);
                }
      
                return maxVal;
            }
        }

댓글