본문 바로가기
Study/디자인 패턴의 아름다움

[디자인 패턴의 아름다움] 2. 객체지향 프로그래밍 패러다임 - 2.5~2.7 정리

by Nahwasa 2024. 4. 5.

스터디 메인 페이지

목차

    - ☆ 표시가 붙은 부분은 스터디 중 나온 얘기 혹은 제 개인적인 생각이나 제가 이해한대로 적어놓은 것으로, 책에 나오지 않는 내용입니다. 따라서 책에서 말하고자 하는 바와 다를 수 있습니다.

    - 모든 이미지의 출처는 디자인패턴의 아름다움(왕정 저) 책 입니다.

     

     

     


     

     

    2.5 객체지향 프로그래밍처럼 보이지만 실제로는 절차적 프로그래밍

    • 오해 : 객체지향 프로그래밍 언어를 개발에 사용하고 모든 코드를 클래스에 넣기만 하면 그것이 바로 객체지향 프로그래밍이라고 생각하는 것

     

    getter, setter 메서드 남용

    • 클래스의 모든 속성에 대한 getter 메서드와 setter 메서드를 정의 -> 캡슐화 특성을 위반한다. 사실상 private로 선언된 속성들도 getter와 setter가 둘 다 있으니 public이나 마찬가지이다.
    • 객체지향 프로그래밍에서 캡슐화 특성에 대한 정의에 따르면, 내부 데이터는 접근 권한 제어를 통해 숨겨져야 하며, 외부에서는 클래스에서 제공하는 제한된 인터페이스를 통해서만 내부 데이터에 접근할 수 있어야 한다.
    • 클래스를 설계할 때 정말로 필요한 경우가 아니라면 속성에 대한 setter 메서드를 정의하지 않아야 한다.
    • 또한 비록 getter 메서드가 setter 메서드보다 안전하지만, 반환값이 List 컨테이너처럼 컬렉션인 경우 컬렉션 내부의 데이터가 수정될 가능성에도 대비해야 한다.

     

    전역 변수와 전역 메서드의 남용

    • 프로젝트에서 사용되는 모든 상수를 하나의 클래스에 통합해서 작성한 경우의 단점
      • 코드의 유지 보수성에 영향을 미친다. 새로운 상수를 추가하다보면 수백 줄의 코드가 된다.
      • 포함된 상수가 많을수록 이 클래스에 더 많은 코드가 종속되어, 코드의 컴파일 시간을 증가시킨다.
      • 코드의 재사용 가능성에 영향을 미친다. 이 프로젝트에서 개발한 클래스를 다른 프로젝트에서 재사용하려고 할 때, 상수를 모아둔 클래스에 종속되어 있어 그 중 일부만 사용하는걸 재사용 하려고 해도 모든 상수를 도입해야 한다.
    • 기능에 따라 여러 개의 상수를 모아둔 클래스로 분해하자.
    • 혹은 클래스가 자신이 사용할 상수를 직접 정의하는 것이 더 나은 방법이다.

     

    데이터와 메서드 분리로 클래스 정의하기

    • MVC 구조를 기반의 웹 프로젝트는 흔히 볼 수 있는 절차적 프로그래밍 스타일 코드이다. 데이터끼리 데이터 전용 클래스로 정의되고, 메서드는 다른 클래스에서 정의하는 형태다.
    • 기존의 MVC 구조에서 프론트엔드와 백엔드가 분리되기 시작하면서 백엔드에서는 컨트롤러 계층, 서비스 계층, 저장소 계층으로 다시 나누는 경향이 생겼다. 일반적으로 VO(view object), BO(business object), Entity에서는 데이터만 정의하고, Controller, Service, Repository 클래스에서 메서드를 정의한다. 이러한 방식은 전형적인 절차적 프로그래밍 스타일이다.
    • 위와 같은 개발 방식을 빈약한 도메인 모델 기반의 개발 방식이라고 하며, 현재 일반적으로 사용하고 있는 웹 프로젝트의 백엔드 개발 방식이기도 하다.
    • 객체지향 프로그래밍에서 절차적 프로그래밍 스타일 코드를 쉽게 작성할 수 있는 이유 : 절차적 프로그래밍 스타일은 프로세스 지향적인 사람의 사고방식과 정확히 일치하지만, 객체지향 프로그래밍은 복잡한 개발에 적합하지만 인간의 사고 습관과는 완전히 일치하지 않는다. 또한 객체지향 프로그래밍이 상대적으로 어렵다.
    • 간단한 프로그램을 작성하거나 데이터 처리 관련 프로그램을 개발할 때 알고리즘 기반으로 데이터를 보완한다면 스크립트 스타일인 절차적 프로그래밍 스타일이 더 적합하다.
    • 객체지향 프로그래밍 스타일과 절차적 프로그래밍 스타일은 양자택일의 대상이 아니다. 객체지향 프로그래밍 언어로 프로젝트를 개발할 때도 절차적 프로그래밍 스타일 코드로 작성하는 일이 드물지 않으며, JDK, Apache Commons, Google Guava와 같은 일부 표준 개발 라이브로리에도 이러한 코드가 많이 포함되어 있다.
    • 어떤 스타일을 사용하든 궁극적인 목표는 유지하기 쉽고, 읽기 쉽고, 재사용이 쉬우며, 확장하기 쉬운 고품질 코드를 작성하는 것이다.

     


     

    2.6 빈약한 도메인 모델에 기반한 전통적인 개발 방식은 OOP를 위반하는가?

    풍성한 도메인 모델에 기반한 DDD 개발 방식 & 두 가지 개발 방식의 비교

    • 빈약한 도메인 모델에서 데이터와 비즈니스 논리는 별도의 클래스로 나뉘는 반면, 풍성한 도메인 모델은 정반대로 데이터와 비즈니스 논리가 하나의 클래스에 포함된다.
    • 따라서 풍성한 도메인 모델은 객체지향 프로그래밍의 캡슐화 특성을 만족하며 전형적인 객체지향 프로그래밍 스타일에 속한다.
    • DDD : 비즈니스 시스템을 분리하고, 비즈니스 모듈을 분할하고, 비즈니스 도메인 모델과 상호 작용을 정의하는 방법을 설계할 때 사용된다.
      • DDD를 잘 하기 위한 핵심은 DDD의 개념에 대한 이해가 아니라 비즈니스에 친숙해지는 것이다.
      • DDD의 개념을 잘 알고 있어도 비즈니스에 익숙하지 않으면 합리적인 도메인 설계를 얻을 수 없다. 따라서 DDD를 왕도라고 생각하고 지나치게 많은 시간을 할애하는 것은 옳지 않다.
    • 풍성한 도메인 모델을 기반으로 DDD 개발 방식으로 구현된 코드는 일반적으로 MVC 아키텍처에 따라 계층화된다. 빈약한 도메인 모델과 차이점은, DDD 개발 방식에서 서비스 계층은 Service 클래스와 Domain 클래스 두 부분으로 구성된다.
      • Domain 클래스는 데이터와 비즈니스 논리를 모두 포함하는 풍성한 도메인 모델 기반으로 개발된다.
      • 상대적으로 Service 클래스는 매우 가볍게 구성된다.
    • 풍성한 도메인 모델 기반 DDD 개발 방식에서 비즈니스 논리를 Domain 클래스로 옮기고 나서 Service 클래스가 가벼워졌음에도 여전히 Service 클래스가 남아 있는 이유
      • Service 클래스의 책임은 저장소 계층과의 통신 담당이다. 도메인 모델에서 저장소 계층과 상호작용하는 대신 서비 클래스가 저장소 계층과 상호작용하는 이유는 도메인 모델을 저장소 계층과 같은 다른 계층 또는 Spring, Mybatis와 같은 개발 프레임워크와 분리하기 위한 것이다.
      • Service 클래스는 여러 도메인 모델의 비즈니스 논리를 결합한다. 물론 기능의 진화와 비즈니스의 복잡성에 따라 비즈니스 일부를 추출하여 독립적인 도메인 모델로 설계할 수도 있다.
      • 또한 Service 클래스는 기능과 무관한 타 시스템과의 상호 작용을 담당한다. 트랜잭션, 이메일 보내기, 메시지 보내기, 로깅, 다른 시스템의 RPC 인터페이스 호출 등이 이에 해당한다.
    • 풍성한 도메인 모델을 기반으로 하는 DDD 개발 방식에서 서비스 계층은 풍성한 도메인 모델로 바뀌었지만, 컨트롤러 계층과 저장소 계층은 여전히 빈약한 도메인 모델로 작성되어 있다. 이 계층들도 풍성한 도메인 모델로 변환해야 할까?
      • 변환이 필요하지 않다. 인터페이스를 노출시키는 컨트롤러와 데이터베이스를 처리하는 저장소 계층은 그다지 많은 비즈니스 논리가 포함되어 있지 않다.
      • 비즈니스 논리가 비교적 단순하다면 굳이 풍성한 도메인 모델로 설계할 필요가 없다.
    • 절차적 프로그래밍 스타일의 부작용을 제어하는 방법
      • 저장소 계층의 Entity의 경우 빈약한 도메인 모델로 설계되면 객체지향 프로그램밍의 캡슐화 특성을 위반하고 임의로 수정될 위험이 있는 것은 확실하다. 하지만 일반적으로 Entity를 서비스 계층에 전달하자마자 Domain으로 변환되어 계속 처리되므로 Entity의 수명 주기는 여기서 끝나므로 임의로 수정될 여지가 없다.
      • dto는 주로 다른 시스템으로 데이터를 보내기 위한 인터페이스의 데이터 전송 캐리어로 사용된다. 기능적으로 보면 비즈니스 논리를 포함하지 않고 데이터만 포함해야 하기 때문에 빈약한 도메인 모델로 설계하는 것이 합리적이다.
    • ☆ 개인적으로 테스트 관련 얘기가 안나와서 약간 아쉬웠다. 예를들어 책 85p의 빈약한 도메인 모델의 debit()을 테스트하려면 WalletEntity, WalletRepository가 이미 만들어져 있어야 한다. 반면 88p의 풍성한 도메인 모델로 만들어진 debit()을 테스트 하려면 순수 자바 클래스로 비즈니스 로직만을 테스트 가능하다.

     

    빈약한 도메인 모델에 기반한 전통적인 개발 방식이 널리 사용되는 이유

    • 대부분의 경우 우리가 개발하는 시스템의 비즈니스는 비교적 단순하며 SQL 기반의 CRUD 작업에 기반한다. 이에 따라 풍성한 도메인 모델을 사용하더라도 모델 자체에 비즈니스 논리가 많이 포함되지 않으며, 결과적으로 모델 자체가 얇아지게 될 것이며, 이는 큰 의미가 없다.
    • 풍성한 도메인 모델은 객체지향 프로그래밍 스타일이기 때문에 설계하기가 훨씬 까다롭다. 처음부터 어떤 데이터가 어떻게 사용되며, 해당 데이터를 다루는 비즈니스 논리가 정리되어 있어야 하기 때문이다.
    • 일반적으로 사고방식은 한 번 자리 잡으면 변하기 어렵고, 변화에는 그만한 대가가 필요하다. 풍성한 도메인 모델과 DDD로 전환하려면 필연적으로 비용이 증가할 수밖에 없다.

     

    풍성한 도메인 모델에 기반한 DDD 개발 방식의 응용 시나리오

    • 풍성한 도메인 모델을 기반으로 하는 DDD 개발 방식은 이자 계산과 상환 모델 같은 복잡한 비즈니스 모델을 포함하는 금융 시스템처럼 복잡한 비즈니스 시스템을 개발하는 데 적합하다.
    • 일반적인 개발 작업은 대부분 SQL 기반이라 해도 과언이 아니다.
      • 백엔드 인터페이스에 대한 개발 요청을 받는다.
      • 인터페이스에 필요한 데이터를 위해 어떤 테이블이 필요한지 살펴본다.
      • 데이터를 얻기 위해 SQL문을 작성하는 방법에 대해 생각한다.
      • Entity, BO, VO(view object)를 정의하고, 해당하는 Repository 클래스, Service 클래스, Controller 클래스에 코드를 추가한다.
    • 위의 과정을 통해 비즈니스 논리는 하나의 큰 SQL 문으로 묶이며, 이 큰 SQL 문은 대부분의 작업을 수행한다. 여기에서 서비스 계층이 할 수 있는 일은 거의 없으며 업무마다 별도의 SQL 문이 필요하기 때문에 코드의 재사용성도 극히 낮다. 또 다른 유사한 비즈니스 기능을 개발하려면 별도의 SQL 문을 하나 더 작성할 수밖에 없으므로, 서로 거의 차이가 없는 수많은 SQL 문이 코드에 포함될 수 있다.
    • ☆ SQL 문 혹은 프로시져에 비즈니스 로직을 넣으면 생기는 문제점 -> 이해하기 힘들어지고, 테스트하기가 어렵다. 또한 형상 관리가 힘들고, 유지보수도 힘들어진다. 또 백엔드 서버야 스케일 아웃이 용이한 편이지만, DB에 프로시져를 넣어놓고 쓸 경우 스케일아웃이 힘들고 스케일업을 해야한다.
    • 복잡한 비즈니스 시스템 개발의 경우 위와 같은 개발 방식으로 진행하면 코드를 점점 더 혼란스럽게 만들고 결국에는 유지 관리를 할 수 없을 지경에 이르게 된다.
    • 시스템이 복잡할수록 코드 재사용성과 유지 관리 용이성에 대한 요구 사항이 높아지기 때문에 초기 설계에 더 많은 시간과 에너지를 토자해야 한다.
    • 풍성한 도메인 모델에 기반한 DDD 개발 방식은 초기에 많은 비즈니스 연구와 도메인 모델 설계가 필요하므로 복잡한 비즈니스 시스템 개발에 더 적합하다.

     


     

     

    2.7 추상 클래스와 인터페이스

    • 추상 클래스와 인터페이스는 많은 디자인 패턴과 설계 원칙의 프로그래밍 구현을 위한 기반이기도 하다.
      • 인터페이스를 사용하여 객체지향 추상화, 다형성, 설계 원칙 구현 등
      • 추상 클래스를 사용하여 객체지향 상속과 템플릿 디자인 패턴 구현 등

     

    추상 클래스와 인터페이스의 정의와 차이점 & 추상 클래스와 인터페이스의 의미

    • 추상 클래스
      • 인스턴스화 할 수 없으며 상속만 가능하다
      • 속성과 메서드를 포함할 수 있다. 메서드는 코드 구현을 포함할 수도 있고, 포함하지 않을 수도 있다. 후자는 추상 메서드라고 한다.
      • 하위 클래스는 추상 클래스를 상속할 때 추상 클래스의 모든 추상 메서드를 실제로 구현해야 한다.
      • 코드 재사용에 초점을 맞추고 있다.
    • 인터페이스
      • 인터페이스는 속성을 포함할 수 없다. (정적 멤버 변수는 포함 가능)
      • 인터페이스는 메서드를 선언할 수 있으나, 실제 코드 구현을 포함할 수 없다. (default 메서드는 가능)
      • 클래스가 인터페이스를 구현할 때는 인터페이스에 선언된 모든 메서드를 구현해야 한다.
      • 디커플링에 초점을 맞추고 있다.

     

     

    댓글