객체 지향 프로그래밍은 객체를 중심으로 프로그램을 구성하는 방법으로, 절차 지향 프로그래밍의 한계를 극복하기 위해 등장하였습니다.
절차 지향 프로그래밍의 한계
데이터와 데이터를 다루는 함수가 분리되어 있다.
이로 인해 함수가 항상 첫 번째 인자로 주소값을 받아야 했고, 이는 쓸데없이 함수가 길어지는 요인이 되었습니다.
마찬가지로 함수 내부에서도 포인터로 데이터를 다뤄야 하는 불편함이 있었습니다.
함수의 이름을 항상 다르게 작성해야 한다.
C언어를 생각해보면 함수의 이름은 전역 이름공간을 사용했기에, 모든 함수가 절대로 이름이 겹쳐서는 안 되었습니다.
이는 프로그램이 복잡해지면 함수명의 결정에 큰 어려움을 주었지요.
프로그램을 확장하기 불편하다.
항상 어떤 함수가 필요한지 명확히 호출해야 했기에, 프로그램에 수정 사항이 생기면 그에 따른 변경 사항을 관련된 모든 함수에 적용해야 했습니다.
상속의 개념이 없었기에, 비슷하게 코드를 중복 사용하고자 하려면 분기문을 많이 사용해야 했습니다.
이를 최대한 줄이기 위해 함수 포인터를 사용하는 등의 대책이 나왔지만, 이 또한 추가적인 데이터를 사용했죠.
결국, 유지 보수가 힘들다.는 것이 절차 지향 프로그래밍의 한계였습니다.
이러한 불편함을 줄이기 위한 고민의 결과가 객체 지향 프로그래밍입니다.
객체 지향 프로그래밍에는 4가지 주요 개념이 있습니다.
1.캡슐화(Encapsulation)
데이터와 데이터를 다루는 함수를 같이 작성하는 것입니다.
이로 인해 더 이상 포인터 변수를 작성할 필요도 없어질 뿐더러, 같이 있는 데이터와 함수는 서로 접근이 쉬워지기에 객체를 만들 때 비교적 직관적인 초기화가 가능해지는 것입니다.
또한, 객체에서 함수를 호출하는 것과 접근 한정자를 이용해 객체의 내외부를 구분 짓는 것이 가능해졌습니다.
2.상속(Inheritance)
코드를 물려받아 재사용할 수 있는 것을 의미합니다.
각 타입을 상위와 하위로 나누고, 상위에 중복되는 코드를 모으고 하위 타입이 이를 '상속'하게 하는 것으로 코드의 중복을 줄이고, 이전에 비해 더 작은 단위로 모듈화가 가능해 집니다.
3.추상화(Abstraction)
구현 세부 정보를 숨기는 일반적인 인터페이스를 정의하는 행위입니다.
여기서 인터페이스란, 서로 다른 부분이 서로에게 영향을 미치는
영역을 의미합니다. 스마트폰이라면 액정에 해당하겠지요.
'추상화'가 잘 이루어지면 잘 이루어질 수록 데이터를 다루기 쉬워지고, 이는 프로그램을 작성하는 입장에서 더 빠르고 편리하고 안전하게 작업을 할 수 있다는 것입니다.
또한, 구현 세부 정보가 잘 숨겨져 있다면 내부에 변화가 있더라도 사용하는 입장에서는 알 수 없으니, 그러한 변화를 더 잘 만들 수 있으니 확장성 또한 좋아진다고 볼 수 있습니다.
4.다형성(Polymorphism)
다형성은 문자 그대로 이해하자면 '여러가지 형태를 가지는 것'을 의미합니다.
다만 OOP에서는 형태란 '서로 다른 동작'으로 이해할 수 있습니다.
그 사용 되는 모습, '인터페이스'는 유지한 채로 내부의 동작이
바꾸어지는 것입니다. 이는 C++에서는 '가상 함수'를 통해 이용할 수 있으며, 이런 변경을 '오버라이딩'이라고 합니다.
이러한 다형성은 객체 지향 프로그래밍의 핵심입니다.
클래스란 사용자 정의 타입으로, 멤버의 집합체입니다.
인스턴스를 만들기 위한 틀의 역할을 합니다.
멤버에는 데이터와 함수가 들어갈 수 있으며, 각각 필드와 메소드라고 부릅니다.
그 외에도 내부 타입, 멤버 템플릿 등을 작성할 수 있습니다.
한 소프트웨어에는 복수의 객체가 있습니다. 이 객체들은 소프트웨어의
기능을 구현하기 위해 서로 요청하고 처리하며 협력합니다.
여기서 좋은 협력이란, 요청이 그 요청을 처리할 수 있는 객체에게 갔을 때를 말하는 것입니다.
따라서 객체 지향 설계는 책임을 그 책임을 수행하기에 적절한 객체에게 부여하는 것입니다.
책임은 한 객체가 다른 객체로부터 요청을 수신하고, 이를 처리해야 할 때 발생합니다.
즉, 책임이란 객체가 수행해야 하는 행동이라 할 수 있습니다.
그러므로 객체 지향 설계에서는 이 책임의 결과와 조건, 즉
소프트웨어의 기능과
기능을 수행하기 위한 협력관계
를 명백히 서술할 필요가 있습니다.
여기서 오해해서는 안되는 부분은, 책임은 객체가 '무엇을' 해야 하는가를 설명할 뿐, '어떻게' 해야 하는가는 설명하지 않는다는 점입니다.
책임을 어떻게 수행할지는 객체가 스스로 결정해야 할 부분입니다.
책임은 수행 방법을 제한할 정도로 구체적이어도,
협력의 의도를 표현 못할 정도로 추상적이어도 안되는 것입니다.
객체 지향 프로그래밍에서, 객체 간의 의사소통 수단입니다.
객체 외부에서의 요청을 표현하는 것을 메시지,
그 요청을 내부에서 처리하기 위한 방법이 멤버 함수인 메소드라고 할 수 있습니다.
책임 주도 설계의 시점으로 다형성을 재해석하자면, 다형성이란 서로 다른 객체들이 동일한 책임을 공유하는 것이라고도 할 수 있습니다.
즉, 메시지 송신자의 관점에서 책임이 어떤 객체가 어떤 방법으로 처리하는가와 무관하게, 그저 수신자가 메시지를 받고 책임이 수행된다는 결과만이 중요한 것입니다.
그렇기 때문에 객체 지향적 설계에서 다형성을 이용해 협력이 유연해지고, 송신자와 수신자 사이의 결합도가 낮아짐으로써
유연하고, 확장 가능하고, 재사용 가능한, 즉 강력한 설계가 완성되는 것입니다.
소프트웨어의 각 모듈은 그 변경의 이유가 하나여야 합니다.
이는 객체마다 단 하나의 책임이어야 객체가 간결하고 명확해지며, 유지 보수가 간편하기 때문입니다.
여러가지 책임을 가진 전지전능한 객체(God object)는 그 복잡도로 인해 자멸하기 마련입니다.
객체는 확장에는 열려 있어야 하고, 변경에는 닫혀 있어야 한다는 원칙입니다.
기존 코드를 수정하기보다 새로운 코드를 추가하는 쪽으로 시스템을 변경할 수 있게 설계해야 후에 유지 보수를 할 때도 쉽게 시스템을 변경할 수 있다는 것입니다.
이는 캡슐화와도 관련이 깊은 원칙입니다.
하위 타입에 관한 원칙으로, 한 T 타입 객체의 자리를 S 타입의 객체로 모두 치환했을 때 프로그램의 행위가 변하지 않는다면 S가 T의 하위 타입이라는 것입니다.
이는 is-a 관계로 설명하면 이해하기 쉽습니다.
S is a T라는 문장에 넣었을 때 말이 된다면, S는 T의 하위 타입인 것입니다.
예를 들어, Cat과 Pet이라는 타입들로 생각해봅시다. Cat is a Pet 은 말이 되는 문장입니다. 따라서 우리는 Cat이 Pet의 하위 타입이라고 이해할 수 있습니다.
고수준 정책을 구현하는 코드는 저수준 세부사항을 구현하는 코드에 절대로 의존해서는 안되며, 세부사항이 정책에 의존해야 한다는 원칙입니다.
이는 인터페이스와 같은 전체적이고 추상적인 개념에 객체와 같은 구체적인 개념이 의존해야 한다는 것이며, 이로써 모듈 사이를 구분지어 확장성을 높입니다.
즉, 다형성을 적극 활용하라는 것입니다.