- Published on
오브젝트 Chap7
- Authors
- Name
- ywj9811
우리는 너무 많은 정보로 인해 인지 과부하가 오는 것을 방지하기 위해 본질적인 정보만 남기고 불필요한 세부 사항을 걸러내어 문제를 단순화 시킨다.
이러한 불필요한 정보를 제거하고 현재의 문제 해결에 필요한 핵심만 남기는 작업을 추상화 라고 한다.
그리고 이렇게 추상화를 하는 가장 일반적인 방법은 큰 문제를 해결 가능한 작은 문제로 나누는 것이다.
이러한 작업을 분해 라고 한다.
프로시저 추상화와 데이터 추상화
이제는 소프트웨어 개발에서 살펴보자.
프로그래밍 언어를 통해 표현되는 추상화의 발전은 다양한 프로그래밍 패러다임의 탄생으로 이어졌다.
프로그래밍 패러다임은 프로그래밍을 구성하기 위해 사용되는 추상화의 종류와 이 추상화를 이용해 소프트웨어를 분해하는 방법의 두가지 요소로 결정된다.
따라서 모든 프로그래밍 패러다임은 추상화와 분해의 관점에서 설명할 수 있다.
- 프로시저 추상화 : 소프트웨어가 무엇을 해야하는지 추상화
- 기능 분해의 길로 들어선다. (알고리즘 분해)
- 데이터 추상화 : 소프트웨어가 무엇을 알아야 하는지 추상화
- 데이터를 중심으로 타입을 추상화 하는 길 (추상 데이터 타입)
- 데이터를 중심으로 프로시저를 추상화 하는 길 (객체지향)
이제 객체지향을 바라보면, 이는 데이터를 중심으로 데이터 추상화와 프로시저 추상화를 통합한 객체를 이용해 시스템을 분해하는 것이다. 그리고 이런 객체를 구현하기 위해 대부분의 객체지향 언어는 클래스라는 도구를 제공한다.
즉, 데이터 추상화와 프로시저 추상화를 함께 포함한 클래스를 이용해 시스템을 분해하는 것이다.
프로시저 추상화와 기능 분해
초반에는 기능과 데이터의 추상화중 기능에 초점을 맞춰서 진행을 했었다.
이와 같은 시스템 분해 방식을 알고리즘 분해 혹은 기능 분해라고 부른다.
기능 분해의 관점에서 추상화의 단위는 프로시저이며 프로시저를 단위로 분해한다.
메인 함수로서의 시스템
프로시저 중심의 기능 분해 관점에서 시스템은 입력 값을 계산해서 출력 값을 반환하는 수학의 함수와 동일한데, 시스템은 필요한 더 작은 작업으로 분해될 수 있는 하나의 커다란 메인 함수이다.
전통적인 기능 분해 방식은 하향식 접근법을 따른다.
즉, 최상위 기능을 더 작은 단계인 하위 기능으로 분해하고 이를 반복하는 것이다.
급여 관리 시스템
예를 들어
“직원의 급여를 계산한다.” 라는 메인 프로시저가 있을 때
직원의 급여를 계산한다
사용자로부터 소득세율을 입력 받는다
"세율을 입력하세요: " 라는 문장을 화면에 출력한다
키보드를 통해 세율을 입력받는다
직원의 급여를 계산한다
전역 변수에 저장된 직업의 기본급 정보를 얻는다
급여를 계산한다
양식에 맞게 결과를 출력한다
"이름: {직원명}, 급여: {계산된 금액}" 형식에 따라 출력 문자열을 생성한다
이런식으로 정리될 수 있다.
이러한 기능 분해 방식에 따라 분해된 급여 관리 시스템을 구현할 때 발생할 수 있는 문제점을 살펴보자
하향식 기능 분해 방식으로 설계한 시스템은 메인 함수를 루트로 하는 ‘트리’ 로 표현할 수 있다.
트리에서 각 노드는 시스템을 구성하는 하나의 프로시저를 의미하고 한 노드의 자식 노드는 부모 노드를 구현하는 절차중의 한 단계를 의미한다.
이와 같이 하향식 기능 분해는 논리적이고 체계적인 시스템 개발 절차를 제시한다.
이는 언뜻 보면 이상적인 방법으로 보이기도 한다.
하향식 기능 분해의 문제점
- 시스템은 하나의 메인 함수로 구성돼 있지 않다.
- 하나의 메인 함수라는 비현실적인 아이디어이다.
- 기능 추가나 요구사항 변경으로 인해 메인 함수를 빈번하게 수정해야 한다.
- 메인 함수의 빈번한 재설계
- 새로운 기능을 추가할 때마다 매번 메인 함수를 수정해야 한다. 이는 버그를 발생시킨다.
- 비즈니스 로직이 사용자 인터페이스와 강하게 결합된다.
- 초기 단계부터 입력 방법과 출력 양식을 함께 고민하도록 강요한다.
- 인터페이스를 변경하는 경우 비즈니스 로직에 영향을 끼친다.
- 하향식 분해는 너무 이른 시기에 함수들의 실행 순서를 고정시키기 때문에 유연성과 재사용성이 저하된다.
- 성급하게 결정된 실행 순서
- 설계를 시작하는 시점부터 시스템이 무엇을 하는지가 아닌 어떻게 동작하는지에 집중하도록 만든다. 이것은 함수들의 실행 순서를 정의하는 시간 제약을 강조하는데, 시간 제약은 자주 변경될 수 있다.
- 하위 함수는 상위 함수가 강요하는 문맥에 강하게 결합되기 때문에 함수는 함께 절차를 구성하는 다른 함수들과 시간적으로 강하게 결합되어 있고, 이는 변경에 취약하게 만든다.
- 데이터 형식이 변경될 경우 파급 효과를 예측할 수 없다.
- 데이터 변경으로 인한 파급효과
- 어떤 데이터가 어떤 함수에 의존하고 있는지 파악하는 것이 어렵다. 데이터의 변경으로 인한 영향이 어디로 퍼져나갈지 모르기에 스파게티처럼 얽히고 설킨 대규모 시스템에서는 변경 이후 제대로 동작하는지 테스트 하는 것도 어려워진다.
- 데이터 변경으로 인한 영향을 최소화 하기 위해서는 데이터와 함께 변경되는 부분과 그렇지 않은 부분을 명확하게 분리해야 하며 이를 위해 데이터와 변경되는 부분을 하나의 구현 단위로 묶고 외부에서는 제공되는 함수만 사용해 데이터에 접근하도록 해야한다. 즉, 잘 정의된 퍼블릭 인터페이스를 통해 접근을 통제해야 한다.
모듈
기능 분해가 가진 본질적인 문제 해결을 하기 위해 정보 은닉과 모듈이라는 개념을 제시하게 되었다.
정보 은닉과 모듈
정보 은닉은 시스템을 모듈 단위로 분해하기 위한 기본 원리로 시스템에서 자주 변경되는 부분을 상대적으로 덜 변경되는 안정적인 인터페이스 뒤로 감춰야 한다는 것이 핵심이다.
모듈과 기능 분해는 상호 배타적인 관계가 아니다. 시스템을 모듈로 분해한 후에는 각 모듈 내부를 구현하기 위해 기능 분해를 적용할 수 있다.
모듈은 다음 두가지 비밀을 감춰야 한다.
- 복잡성 : 모듈이 너무 복잡한 경우 이해하고 사용하기 어렵기에 외부에 모듈을 추상화할 수 있는 간단한 인터페이스를 제공해서 모듈의 복잡도를 낮춘다.
- 변경 가능성 : 변경 가능한 설계 결정이 외부에 노출될 경우 실제로 변경이 발생했을 때 파급효과가 커진다. 변경 발생 시 하나의 모듈만 수정하면 되도록 변경 가능한 설계 결정을 모듈 내부로 감추고 외부에는 쉽게 변경되지 않을 인터페이스를 제공한다.
모듈의 장점과 한계
모듈의 장점은 다음과 같다.
- 모듈 내부의 변수가 변경되더라도 모듈 내부에만 영향을 미친다.
- 비즈니스 로직과 사용자 인터페이스에 대한 관심사를 분리한다.
- 전역 변수와 전역 함수를 제거함으로써 네임스페이스 오염을 방지한다.
이러한 모듈은 모듈 내부의 높은 응집도를 유지시켜주고, 모듈과 모듈 사이에는 퍼블릭 인터페이스를 통해서만 통신해야 하기에 낮은 결합도가 유지된다.
모듈이 정보 은닉이라는 개념을 통해 데이터라는 존재를 설계의 중심 요소로 부각시켰다는 것이다.
모듈에 있어서 핵심은 데이터다.
하지만 큰 단점이 있다.
이는 인스턴스의 개념을 제공하지 않는다는 것이다.
데이터 추상과 추상 데이터 타입
추상 데이터 타입
사람들은 ‘직원의 급여를 계산한다’ 라는 하나의 커다란 절차를 이용해 사고하기 보다는 ‘직원’과 ‘급여’ 라는 추상적인 개념들을 머릿속에 떠올린 후 이들을 이용해 ‘계산’에 필요한 절차를 생각하는데 익숙하다.
추상 데이터 타입은 프로시저 추상화 대신 데이터 추상화를 기반으로 소프트웨어를 개발하게 한 최초의 발걸음이다.
추상 데이터 타입은 말 그대로 시스템의 상태를 저장할 데이터를 표현하는 것으로 표현된 데이터를 이용해 기능을 구현하는 핵심 로직은 추상 데이터 타입 외부에 존재한다.
추상 데이터 타입은 데이터 대한 관점을 설계의 표면으로 끌어 올리기는 하지만 여전히 데이터와 기능을 분리하는 절차적인 설계의 틀에 갇혀 있는 것이다.
클래스
이때 우리에게 혼란을 가져다 주는 것은 과연 클래스는 추상 데이터 타입인 것일까? 이다.
클래스는 추상 데이터 타입인가?
두 메커니즘 모두 외부에서는 객체의 내부 속성에 직접 접근할 수 없으며 오직 퍼블릭 인터페이스를 통해서만 외부와 의사소통할 수 있다.
하지만, 가장 핵심적인 차이는 상속과 다형성의 지원인 것이다.
이 차이가 객체지향 프로그래밍과 객체기반 프로그래밍이다.
추상 데이터 타입을 사용하는 경우
- 직원
- 정직원
- 아르바이트생
위의 타입이 직원이라는 하나의 타입으로 공존하게 된다.
하지만 클래스를 사용하게 되면 다형성과 상속을 사용할 수 있다.
- 직원
- 정직원
- 아르바이트생
위의 경우를 직원이라는 부모 클래스를 정직원과 아르바이트생이 상속받아 각각 구현하여 직원이라는 내용 하나뿐으로 처리할 수 있게 되는 것이다.
변경을 기준으로 선택하라
만약 인스턴스 변수에 저장된 값을 기반으로 메소드 내에서 타입을 명시적으로 구분하는 방식은 객체지향을 위반하는 것으로 간주된다.
이는 만약 새로운 타입을 추가하기 위해서는 클라이언트의 조건문을 찾아서 하나씩 모두 수정하게 만들 것이다.
이에 반해 객체지향은 새로운 직원 유형을 구현하는 클래스를 상속 계층에 추가하고 필요한 메소드를 오버라이딩 하기만 하면 된다.
이처럼 기존 코드에 아무런 영향도 미치지 않고 새로운 객체 유형과 행위를 추가할 수 있는 객체지향의 특성을 개방-폐쇄 원칙 (OCP) 라고 부른다.
협력이 중요하다
물론, 객체지향에서 중요한 것은 다시 말하지만 역할, 책임, 협력이다.