프로그래밍에서 문제를 해결하기 위해 사용하는 저장소는 단기 기억 방식이다. 단기 기억의 경우 용량이 상대적으로 작고 이를 초과하는 순간 해결 능력이 급격히 떨어진다.
이러한 현상을 인지 과부화라고 하며 이를 해결하기 위해 정보마다 불필요한 부분을 제거하고 현재 문제 해결의 핵심만 남기는 추상화라는 작업이 필요하다.
현대적인 프로그래밍 언어에서 추상화는 크게 2가지로 나뉜다.
프로시저 추상화
반복적으로 실행되거나 거의 유사하게 실행되는 작업들을 하나의 장소에 모아놓는 기법이다. 이를 통해 중복 코드를 방지할 수 있으며 재사용성이 높아진다.
내부의 상세한 구현 내용을 모르더라도 인터페이스만 알면 프로시저를 사용할 수 있기 때문에 추상화라 볼 수 있다.
데이터 추상화
프로시저 추상화의 한계를 극복하기 위해 나온 기법으로 과정(프로시저)를 오퍼레이션을 기준으로 세분화하여 추상화하는 기법
효과적인 추상화를 통해 문제의 복잡성을 극복해야 한다. 추상화들이 어떤 식으로 적용되며 점진적으로 개선하면서 장/단점을 구체적으로 살펴보자.
참고
추상화에 따라 프로그래밍 패러다임이 결정된다.
프로시저를 중심으로 시스템을 분해해보자. 전통적인 방법으로 하향식 접근법이 있다.
시스템을 구성하는 가장 최상위 기능을 정의하고 이를 여러 개의 작은 단계의 하위 기능들로 분해하면서 나가는 방법을 뜻한다. 이 때, 하위 기능들은 상위 기능보다 더 구체적이여야 한다.
시스템은 필요한 더 작은 작업으로 분해될 수 있는 하나의 메인 함수이다. 만약, 급여 계산을 한다면 메인 함수내에서 실행되야 하는 로직들은 아래와 같다.
기본적으로 기능 분해는 최상위 기능을 수행하는데 필요한 절차들을 실행 순서에 따라 나열한 것이라 볼 수 있다. 이 방법은 기능을 중심으로 필요한 데이터를 결정한다.
하지만, 이는 다양항 문제점들을 야기한다. 이를 통해 유지보수 관점에서 객체지향의 장점을 이해할 수 있다.
하향석 접근법은 설계가 어느정도 안정화 된 경우 설계의 다양한 측면을 논리적으로 설명 및 문서화하기 용이하다.
"이미 완전 이해한 사실을 서술하기에 적합한 방법"
특히, 이미 해결된 알고리즘을 문서화 및 서술하는데 용이하다.
모듈이란 데이터와 함수가 통합된 한 차원 더 높은 추상화를 제공하는 설계 단위이다. Java에서 패키지의 개념과 동일하다.
시스템 변경을 관리하기 위한 기본 전략은 함께 변경되는 부분을 하나로 묶고 외부에 퍼블릭 인터페이스를 제공하도록 만드는 것이다. 여기서
정보 은닉이란 모듈을 만들 때 사용되는 기본 원리이다. 시스템에서 자주 변경되는 부분을 상대적으로 변경이 적은 인터페이스 뒤로 숨기는 것이 핵심이다. 핵심은 기능을 초점에 두지 않고, 변경의 방향성을 기반으로 시스템을 분해하는 것이다.
이를 기반으로 모듈은 시스템이 감춰야 하는 비밀은 내부에 감추고, 퍼블릭 인터페이스를 외부에 제공한다.
앞서 언급한 시스템이 감춰야 하는 비밀은 다음 2가지이다.
복잡성
모듈이 너무 복잡하다면 사용하기 어렵다. 따라서, 이를 간단히 하는 인터페이스를 제공하여 복잡도를 낮추자.
변경 가능성
변경 가능성이 높은 부분을 외부에 노출하면 변경의 파급효과가 커지게 된다. 따라서, 변경 가능성이 높은 부분은 내부에 감추고 가능성이 적은 부분을 인터페이스로 제공하자.
모듈은 변경 가능성이 같은 것끼리 묶는 반면, 기능 분해는 하나의 기능을 구현하기 위해 필요한 기능을 탐색하는 과정이다. 이 둘은 상호베타적이라 볼 수 없다.
변경을 우선으로 고려하여 모듈화를 진행하고, 이후 제공할 인터페이스를 기능 분해를 통해 결정하는 방식이다.
위의 정보 은닉을 기반으로 모듈을 설계했을 때는 여러 장점이 존재한다.
우선, 변경의 여파가 모듈 내부에만 한정되어 데이터 변경으로 인한 파급효과를 제어할 수 있다.
모듈 내부는 변경 포인트가 비슷한 데이터와 함수로 구성되므로 응집도가 향상되며, 모듈끼리 퍼블릭 인터페이스를 통해서만 통신하므로 모듈끼리 낮은 결합도를 유지한다.
모듈은 데이터를 중심으로 시스템을 분해한다. 이는 프로시저 추상화보다 더 높은 추상화 개념을 제공하지만, 변경을 관리하기 위한 구현기법이므로 한계가 명확하다.
참고) Type
변수에 저장할 수 있는 내용물의 종류와 변수에 적용될 수 있는 연산의 가짓수
프로시저만으로는 풍부한 추상화가 불가능하다. 이를 보완하기 위해 데이터 추상화(Data abstraction)이라는 개념이 등장했다.
예를 들어, "직원의 급여를 계산한다." 라는 커다란 절차(프로시저)를 고민하는 것 보단 "직원"과 "급여"라는 추상적인 개념들을 통해 "계산"에 필요한 절차를 생각하는게 더 일반적일 것이다.
ADT란 위에서 언급한 "직원", "급여"와 같은 추상적인 타입을 의미한다. 이들은 상태와 행위를 가지는 독립적인 객체이다. 따라서, 이전 전체 직원을 캡슐화했던 Employees
모듈보다는 개념적으로 이질감이 덜하다.
그러나, ADT 또한 데이터와 기능을 분리해서 바라보기에 절차지향적인 성격이 강하다.
클래스는 ADT와 동일하게 외부에서 객체 내부 속성에 직접적으로 접근이 불가능하며 오직 퍼블릭 인터페이스를 통해 소통이 가능하다. ADT에 다형성 + 상속이 추가된 매커니즘이라 보면 된다.
참고
상상속 + 다형성을 지원하지 않는 프로그래밍 패러다임을 객체 기반 프로그래밍(Object-Based Programming)이라 한다.
클래스는 다형성 패턴을 통해 OCP(개방 폐쇄 원칙)를 구현할 수 있다. 그러나, 오퍼레이션이 자주 변경되는 상황이라면 ADT가 더 유리하다.
위와 같은 추상화 기법들은 결국 협력 내에서 책임을 수행할 때 사용된다. 책임을 다양한 방식으로 수용해야 하는 경우 각 절차를 추상화할 수 있다.
타입 계층과 다형성은 협력 내에서 책임을 수행하는 방법을 유연하게 할 수 있게 해주는 역할일뿐이다. 협력과 책임을 중요시해야지 이들을 우선시하여 설계를 하면 안된다.