본문 바로가기

BackEnd

7장 객체 분해 [WIP]

반응형

프로시저 추상화와 데이터 추상화

프로시저 추상화

소프트웨어가 무엇을 해야 하는지 추상화한다.

프로시저는 리턴값이 없는 로직 묶음

데이터 조작

기능 분해(알고리즘 분해)

 

 

데이터 추상화

소프트웨어가 무엇을 알아야 하는지 추상화

정보 표현

데이터 중심 타입 추상화(Abstract Data Type)

데이터 중심 프로시저 추상화(Object-Oriented)

 

역할과 책임을 수행하는 객체가 객체지향 패러다임이 이용하는 추상화다.

기능을 협력하는 공동체를 구성하도록 객체들로 나누는 과정

 

프로그래밍 언어 관점에서 객체지향이란

데이터를 중심으로 데이터 추상화와 프로시저 추상화를 함께 포함한 객체를 이용해 시스템을 분해하는 방법이다.

 

프로시저 추상화와 기능 분해

기능은 오랜 시간동안 시스템을 분해하기 위한 기준으로 사용되었다.

이를 알고리즘 분해 또는 기능 분해라고 부른다.

기능 분해의 관점에서 추상화의 단위는 프로시저이며, 시스템은 프로시저를 단위로 분해된다.

 

프로시저는 반복적으로 실행되거나 거의 유사하게 실행되는 작업들을 하나의 장소에 모아놓음으로써 로직을 재사용하고 중복을 방지할 수 있는 추상화 방법이다.

 

프로시저를 추상화라고 부르는 이유는 내부 상세 구현을 모르더라도 인터페이스만 알면 프로세저를 사용할 수 있기 때문이다.

전통적인 기능 분해 방법은 하향식 접근법(Top-Down Approach)를 따른다.

하향식 접근법이란 시스템을 구성하는 가장 최상위(topmost)기능을 정의하고,

이 최상위 기능을 좀 더 작은 단계의 하위 기능 집합으로 분해해 나가는 방법을 말한다.

각 세분화 단계는 바로 위 단계보다 더 구체적이어야 한다.

분해는 세분화된 마지막 하위 기능이 프로그래밍 언어로 구현 가능할 수준이 될 때까지 계속한다.

하향식 기능 분해 예시

기능 분해 결과는 최상위 기능들 수행에 필요한 절차들을 실행되는 시간 순서에 따라 나열한 것이다
기능 분해는 책의 목차를 정리하고 내용을 채워넣는 것과 유사하다.

책을 쓰는 것처럼, 입력, 계산, 출력으로 목차를 정하고 내용을 끼워넣는다.

기능이 우선이고 데이터는 조연이다.

기능 분해를 위한 하향식 접근 방법은 먼저 필요한 기능을 생각하고, 기능을 분해, 정제하는 과정에서 필요한 데이터의 종류와 저장 방식을 식별한다.

이는 유지보수에 다양한 문제를 야기한다.

 

 

하향식 기능 분해는 시스템을 최상위의 가장 추상적인 메인 함수로 정의하고, 메인 함수를 구현 가능한 수준까지 세부적인 단계로 분해하는 방법이다.

시스템은 메인을 루트 노드로 하는 트리, 각 프로시저는 노드, 자식 노드는 부모 노드 구현 절차 중 한 단계를 의미한다.

위 그림만 보면 논리적, 구조적, 체계적인 동시에 이상적으로 보인다.

체계적이고 이상적인 방법이 불규칙하고 불완전한 인간과 만나는 지점에서 혼란과 동요가 발생한다.

 

하향식 기능 분해의 문제점

변경에 취약한 설계를 낳는다는 것이다.

하향식 접근 방식은 하나의 알고리즘 구현이나 배치 처리에나 적합하다.

실제 시스템에 정상(top-down : 하향식 분해를 위한 정상)은 존재하지 않는다.

  • 시스템은 하나의 메인 함수로 구성돼 있지 않다.
    • 모든 기능을 자식 노드로 가지는 하나의 메인 기능을 선택할 수 있는가?
      • 대부분의 시스템에서 논재하지 않는다.
      • 기능성 측면에서 동등하게 독립적이고 완결된 하나의 기능을 표현
  • 기능 추가나 요구사항 변경으로 메인 함수를 빈번하게 수정해야 한다.
    • 기존 로직과 상관 없는 함수가 들어오면 메인 함수를 수정해야 한다. (if)
    • 지금까지 우리는 조건 추가 시 원래 코드를 수정하지 않는 방법들을 배워왔다.
  • 비즈니스 로직이 사용자 인터페이스와 강하게 결합된다.
    • 설계 초기부터 입출력을 함께 고민하도록 한다.
      • 사용자 인터페이스의 관심사와 비즈니스 로직의 관심사를 동시에 고려하도록 강요한다
      • main에 비즈니스 로직(계산과) 입출력 로직(사용자 입력 받기)가 섞여엤어 인터페이스를 변경하려면 전체 구조를 재설계해야 한다.
  • 하향식 분해는 너무 이른 시기에 함수들의 실행 순서를 고정시키기 때문에 유연성과 재사용성이 저하된다.
    • 하향식 분해는 하나의 함수를 더 작게 분해하며, 실행 순서를 결정한다.
      • 실행 순서를 정의하는 시간 제약
    • 이것은 what이 아니라 how에 집중하는 것이다.
      • 직원 급여를 계산하기 위한 작업을 순서대로 해야 함
        • 소득세율을 입력 받는 작업
        • 급여를 계산하는 작업
        • 계산된 결과를 화면에 츨력하는 작업
      • 위에 모든것은 what이 아니라 how다.
  • 상위 함수의 문맥 안에서만 의미를 갖기 때문에 재사용이 어렵다.
    • 함수는 상위 함수가 강요하는 문맥에 시간(순서)적으로 강하게 결합된다.
      • 메인 함수가작은 함수로 분해되려면 우선 순서를 정해야 한다.
        • 실행 순서, 반복, 조건과 같은 제어 구조를 정해야 해서 중앙 집중 스타일이 될 수밖에 없다.
          • 중요한 제어 흐름을 객체의 책임별로 분산시켜야 한다.
            • 즉 논리적 제약을 설계의 기준으로 삼는다
            • 어떤 객체의 책임인가! 
      • 강한 결합도는 시스템을 변경에 취약하게 만들고 이해하기 어렵게 만든다.
      • 시간 순서 가 아닌 논리적 제약을 설계의 기준으로 삼아야 한다.
    • 함수가 재사용 되려면 일반적이어야 한다.
      • 하위 함수는 항상 상위 함수 문맥에 종속정이다. 이는 재사용성에 반대된다.
      • 이는 결합도를 높이고, 강한 결합도는 시스템을 변경에 취약하게 만들면서, 이해하기 어렵게 만든다.
  • 데이터 형식이 변경될 경우 파급 효과를 예측할 수 없다.
    • 하향식 기능 분해의 가장 큰 문제점
    • 어떤 데이터를 어떤 함수가 사용하고 있는지를 추적하기 어려움
      • 어떤 데이터가 어떤 함수에 의존하고 있는지는 모든 함수를 열어 데이터 사용 여부를 찾아봐야 함 (전역변수...)
      • 전역변수가 수정되었다고 기존 함수를 수정해야 할 것이라는 생각을 하기가 어려움 (전역변수 이용 if 를 쓰고있다면...)
      • 정보은닉, 모듈이 등장한 이유

시스템은 여러개의 정상으로 구성되기 때문에

sumOfBasePays 함수 같은 새로운 정상을 추가할 때마다 하나의 정상이라고 간주했던 main 함수의 내부 구현을 수정할 수밖에 없다.

기존 코드의 빈번한 수정으로 인한 버그 발생 확률이 높아져 시스템은 변경에 취약해진다.

즉 하나의 함수에 제어가 집중되어 확장이 어렵다.

과도하게 함수에 집중하여 데이터에 대한 영향도 파악이 어렵다.

설계를 재사용하기 힘들다.

하향식 분해가 유용한 경우

문서화.

이미 개발된 개별 알고리즘.

작은 프로그램.

하향식 서술은 사람들이 설계나 개발을 이와 같이 성공할 수 있다고 착각하게 한다.

모듈

정보 은닉과 모듈

시스템 변경 관리 기본 전략은 함께 변경되는 방향으로 기능과 데이터를 묶어 인터페이스로만 접근할 수 있게 하는 것이다.

정보 은닉 :

모듈 단위 분리 기본 원리

자주 변경되는 부분을, 안정적인 인터페이스 뒤로 감춰야 한다

즉 모듈 단위 분리는, 비밀을 감추는 것부터 시작한다.

기능 분해가 기능 도출 과정이라면

모듈 분해는 감춰야 할 비밀을 선택하고, 비밀 주변에 안정적인 보호막을 설치하는 보존의 과정이다.

시스템을 모듈 단위로 어떻게 분해할 것인가? 시스템이 감춰야 하는 비밀을 찾아라.

외부에서 내부 접근을 못하도록 방어막(퍼블릭 인터페이스)을 쳐서 에워싸라.

 

모듈은 다음 두 가지 비밀을 감춰야 한다.

  • 복잡성 : 모듈을 추상화하는 간단한 인터페이스를 제공하여 내부 로직의 복잡도를 낮춘다.
  • 변경 가능성 : 변경 발생 시 하나의 모듈만 수정하면 되도록 변경 가능한 설계 결정을 모듈 내부로 감추고 외부에는 쉽게 변경되지 않을 인터페이스를 제공한다.

가장 큰 비밀은 데이터지만, 복잡한 로직이나 변경 가능성 큰 자료구조일 수 있다.

데이터 캡슐은 정보 은닉의 한 종류일 뿐임.

 

자바의 모듈 개념은 패키지다.

C++, C# 은 네임스페이스다.

javascript는 파일이다.

 

모듈의 장점

모듈은 기능이 아니라 변경의 정도에 따라 시스템을 분해하게 한다.

각 모듈은 외부에 감춰야 하는 비밀과 관련성 높은 데이터,함수 집합니다.

따라서 모듈 내부는 응집도가 높다.

모듈과 모듈 사이에는 퍼블릭 인터페이스를 통해서만 통신할 수 있다.

따라서 낮은 결합도를 유지한다.

 

 

모듈 내부의 변수가 변경되더라도 모듈 내부에만 영향을 미친다

 

 

모듈을 사용하면 모듈 내부에 정의된 변수를 직접 참조하는 코드의 위치를 모듈 내부로 제한할 수 있다.

이제 어떤 데이터가 변경되었을 때 영향을 받는 함수는 해당 데이터를 정의한 모듈만 검색하면 된다.

즉, 전체 함수들을 뒤져볼 필요가 없다. 데이터 변경으로 인한 파급효과를 제어한다,

 

비즈니스 로직과 사용자 인터페이스에 대한 관심사를 분리한다

 

사용자 인터페이스는 외부에서 담당한다.

모듈은 비즈니스 로직을 담당하는 인터페이스만 제공한다

즉, 다른 사용자 인터페이스가 추가되더라도 사용되는 모듈의 비즈니스 로직은 변하지 않는다.

 

 

전역 변수와 전역 함수를 제거함으로써 네임스페이스 오염(namespace pollution)을 방지한다

 

모듈의 한 가지 용도는 네임스페이스를 제공하는 것이다.

변수와 함수를 모듈 내부에 포함시키기 때문에 다른 모듈에서도 동일한 이름을 사용할수 있다.

즉 전역 네임 스페이스를 오염시키지 않고, 이름 충돌(name collision)의 위험을 완화한다.

 

 

모듈의 단점

정보 은닉이라는 개념을 통해 데이터를 설계의 중심 요소로 부각시킨다.

인스턴스 개념을 제공하지 않는다.

좀 더 높은 수준의 추상화를 위해선, Employees 모듈이 전체 직원들을 다루는게 아니라

개별 직원을 독립적인 단위로 다룰 수 있도록 해야 한다.

 

즉 다수의 인스턴스가 필요하다.

이를 위해 등장한 개념이 추상 데이터 타입이다.

 

데이터 추상화와 추상 데이터 타입

프로그래밍 언어에서 타입(type)이란 변수에 저장할 수 있는 내용물의 종류와 변수에 적용될 수 있는 연산의 가짓수를 의미한다.

타입을 통해 저장된 값에 대해 수행될 수 있는 연산이 정해진다.

따라서 변수의 값의 행동을 예측할 수 있다.

(문자열 연결, 정수 덧셈)

절차지향 언어는 적은 수의 내장 타입만을 제공했으며, 새로운 타입 추가가 불가능하거나 제한적이었다.

리스코프는 소프트웨어를 이용해 표현할 수 있는 추상화의 수준을 한단계 높였다.

 

추상 데이터 타입 구현을 위한 프로그래밍 언어의 기능

타입 정의 선언

타입 인스턴스를 다루기 위한 오퍼레이션의 집합

제공된 오퍼레이션을 통해서만 조작할 수 있도록 데이터 캡슐화(접근제어자)

타입에 대해 여러개의 인스턴스 생성 가능해야 한다.

 

추상 데이터 타입은 구조체랑 비슷함.

여ㅓㄴ히 데이터와 기능을 분리

말 그대로 시스템의 상태를 저장할 데이터를 표현한다.

핵심 로직은 함수(추상 데이터 타입 외부에 존재)

 

기본 의도는 프로그래밍 언어가 제공하는 타입처럼, 사용자 정의타입을 추가할 수 있게 하는것이다.

클래스와 추상데이터 타입 둘 다 데이터 추상화를 기반으로 시스템을 분해하지만,

상속과 다형성 지원여부가 다르다.

클래스와 추상 데이;터 타입 구분 위해 객체기반 프로그래밍이라고도 한다 

 

 

클래스

클래스는 추상 데이터 타입인가?

클래스와 추상 데이터 타입 모두 데이터 추상화를 기반으로 시스템을 분해한다.

클래스는 상속과 다형성을 지원하지만

추상 데이터 타입은 지원하지 못한다.

객체지향 프로그래밍 vs 객체기반 프로그래밍

클래스는 절차를 추상화한 것

추상데이터타입은 타입을 추상화한 것

 

추상 데이터 타입은 물리적으로는 하나의 타입이고, 개념적으로는 두 개의 개별적인 개념을 포괄하는 복합 개념이다.

추상 데이터 타입은 물리적으로 하나의 타입처럼 보이는 Employee 타입 내에 개념 2개가 공존함.

구체적인 직원 타입을 캡슐화함.

이를 타입 추상화라 함.

클라이언트는 정규직원이나 아르바이트직원이 있다는 사실은 알 수 없다.

객체지향은 타입을 기준으로 오퍼레이션을 묶는다.

즉, 정규 직원과 아르바이트 직원이라는 두 개의 타입을 명시적으로 정의하고

두 직원 유형과 관련된 오퍼레이션의 실행 절차를 두 타입에 분배한다.

즉 아르바이트, 직원 각각에 클래스를 정의하고

각 클래스들이 calculatePay와 monthlyBasePay 오퍼레이션을 적절하게 구현하게 될 것이다.

공통로직은 부모 클래스에 위치한다.

 

클라이언트 관점에서 두 클래스의 인스턴스는 동일하게 보인다. 실제로 내부에서 수행되는 절차는 다르다.

내부 수행 절차는 다르지만 클래스를 이용한 다형성은 절차에 대한 차이점을 감춘다.

즉, 객체지향은 절차 추상화다.

 

추상 데이터 타입은 오퍼레이션을 기준으로 타입들을 추상화한다.

클래스는 타입을 기준으로 절차들을 추상화한다.

변경을 기준으로 선택하라

클래스를 구현 단위로 사용한다는 것이 객체지향 프로그래밍을 한다는 의미는 아니다.

타입을 기준으로 추상화하지 않았다면 그것은 객체지향 분해가 아니다.

비록 클래스를 사용하고 있다고 하더라도 말이다.

내부 인스턴스 변수 기반으로 메서드 내에서 타입을 명시적으로 구분하는 방식은 객체지향 위반이다.

타입 변수를 이용한 조건문을 다형성으로 대체한다.

객체가 메세지를 처리할 적절한 메서드를 선택한다.

객체지향이란 조건문을 제거하는 것이라고 어느정도 할 수 있다.

조건문 체크 로직을 하나하나 다 찾아서 수정해야하기 때문이다.

새로운 직원 유형 구현 클래스를 Employee 상속 계층에 추가하고, 필요한 메소드를 오버라이딩 하기만 하면 된다. 다른 곳은 건드릴 필요가 없다.

Open-Closed Principle : 기존 코드에 아무런 영향도 미치지 않고 새로운 객체 유형과 행위를 추가할 수 있는 객체지향의 특성

 

추상 데이터 타입 vs 클래스

타입 추가의 경우 객체지향

오퍼레이션 추가의 경우 추상 데이터 타입

객체지향의 경우 새로운 오퍼레이션을 추가하기 위해서는 상속 계층에 속하는 모든 클래스를 한번에 수정해야 한다.

추상 데이터 타입의 경우에는 전체 타입에 대한 구현 코드가 하나의 구현체 내에 포함돼 있기 때문에 새로운 오퍼레이션을 추가하는 작업이 상대적으로 간단하다.

서비스 중심적인 관점 vs 데이터 중심적인 관점

가장 중요한 것은 역할, 책임, 협력이다.

객체 설계는 책임 주도 설계의 흐름을 따른다.

반응형