Post

객체 지향 프로그래밍이란?

요즘 주로 스프링 프레임워크를 다루다보니 자연스럽게 객체 지향 프로그래밍에 대한 흥미와 관심이 생기게 되었습니다. 그래서 이번에는 객체 지향 프로그래밍에 대한 간단한 소개와 객체 지향 프로그래밍에 대한 개인적인 이해를 담은 글을 작성해봤습니다.

객체 지향 프로그래밍이란?

객체 지향 프로그래밍(Object Oriented Programming, OOP)의 정의를 위키피디아에서 찾아보면 다음과 같습니다.

객체 지향 프로그래밍은 컴퓨터 프로그램을 명령어의 목록으로 보는 시각에서 벗어나 여러 개의 독립된 객체들의 모임으로 파악하고자 하는 것이다.

각각의 객체끼리 서로 메시지를 주고 받으며 데이터를 처리할 수 있다. 이러한 객체 지향 프로그래밍은 프로그램에 유연성을 주며 이는 변경을 용이하게 만들기 때문에 큰 규모의 소프트웨어 프로젝트에서 많이 사용된다.

위 정의에서 알 수 있는 객체 지향 프로그래밍의 장점으로 프로그램을 더욱 유연하게 만들어 변경 가능성을 높이고 이를 통해 대규모 소프트웨어 프로젝트에서도 조금 더 쉽게 유지보수 할 수 있도록 도움을 준다는 점을 들 수 있습니다.

이렇게 유연한 대처가 가능한 이유는, 여러 개의 독립된 객체들로 잘개 쪼개서 변경이 필요할 때마다 일부 객체만을 부품 갈아 끼우듯이 변경하면 되기 때문입니다. 이걸 프로그래밍적으로 구현하기 위해서는 다형성(Polymorphism)이라는 개념을 알아야 합니다. 그래서 지금부터는 다형성에 대해 알아보겠습니다.

다형성이란?

다음은 위키피디아에 나와있는 다형성(Polymorphism)에 대한 정의입니다.

프로그래밍 언어의 다형성은 그 프로그래밍 언어의 자료형 체계의 성질을 나타내는 것으로, 프로그래밍 언어의 각 요소들(상수, 변수, 식, 오브젝트, 함수, 메소드 등)이 다양한 자료형(type)에 속하는 것이 허가되는 성질을 가리킨다.

이와 반대되는 말은 단형성(monomorphism)으로, 프로그램 언어의 각 요소가 한 가지 형태만을 가지는 성질을 가리킨다.

여기서 핵심은 프로그래밍 언어의 한 요소가 다양한 자료형에 동시에 속할 수 있다는 점입니다. 즉 예를들면 하나의 객체가 여러 가지의 타입을 가질 수 있는 것을 들 수 있습니다.

자바에서는 다형성을 위해 부모 클래스 타입의 참조 변수로 자식 클래스 타입의 인스턴스를 참조할 수 있게 하고 있습니다. 이를 통해 다형성을 실제 프로그래밍적으로 구현할 수 있습니다. 이 말만 들어서는 크게 와닿지 않을 수 있어서 간단한 동물 예시를 하나 가져왔습니다.

polymorphism

위 그림을 보시면 Animal이라는 부모 클래스가 있고 이에 대한 자식 클래스로 Dog, Cat, Duck이 있습니다. 즉 Animal 타입의 부모 클래스는 Dog 타입을 가질 수도 있고 Cat, Duck 타입 등을 가질 수 있습니다. 이를 자바에서는 다형성을 위해 허용하고 있습니다.

이를 통해 저희는 조금 더 추상적인 부모 클래스 혹은 인터페이스를 만들어 놓고 이를 필요한 기능에 맞춰 상속받거나 구현하도록 프로그래밍을 해 놓으면 구현체를 부품 갈아 끼우듯이 요구 사항에 맞춰 변경이 필요한 시점에 수월하게 교체할 수 있게 됩니다.

더욱 깊이있게 다형성을 이해하기 위해서는 역할구현으로 나눠 생각해봐야 합니다. 위 예시를 보면 Animal은 역할이고 이를 구현하는 Dog, Cat, Duck 등의 구체적인 구현체들이 있는 것입니다. 이때 필요에 따라 역할은 그대로 두고 구현체만을 바꿔가며 변경을 손쉽게 할 수 있게 됩니다.

지금은 간단히 부모 타입과 자식 타입만을 두고 예시를 들었는데 이런 식으로 추상화시킨 역할 타입과 구현 타입으로 잘게 객체들을 나누고 시스템 자체를 이런 역할과 구현의 관계의 객체들로 쪼개어 설계했을 때 객체 지향적으로 설계했다고 말할 수 있습니다. 이를 통해 얻고자하는 궁극적인 목표는 유연성과 변경의 용이성입니다.

역할과 구현을 분리하면?

앞서 살펴봤듯이 역할과 구현을 분리하여 설계하는 방식이 객체 지향 설계의 핵심이라고 말할 수 있습니다. 이렇게 역할과 구현으로 구분하면 세상이 단순해지고, 더욱 유연해지며 변경이 쉬워지게 됩니다.

왜냐하면, 어떤 역할(부모 타입 혹은 인터페이스 등)을 이용하는 다른 객체(클라이언트)가 있을 때 클라이언트는 내가 의존 관계를 맺고 있는 역할만 알고 있으면 되고 이 역할이 어떤 구현체(자식 타입 혹은 인터페이스의 구현 등)를 가지고 있는지는 전혀 몰라도 되기 때문입니다.

더 나아가서 클라이언트는 역할에만 의존적이기 때문에 구현 대상의 내부 구조가 변경되더라도 영향을 받지 않습니다. 즉, 역할과 구현의 관계로 나눠 설계하게 되면 클라이언트에 영향을 주지 않고 쉬운 변경이 가능합니다. 따라서, 객체 지향적인 설계를 할 때는 역할(인터페이스)를 먼저 생각하고 나중에 그 역할을 수행하는 구현 객체에 대한 고민을 하는 것이 좋습니다.

이렇게 역할과 구분의 관계를 가지는 객체들로 시스템을 쪼개었을 때, 이 시스템이 원활하게 운영되는 기반은 모두 객체의 협력으로부터 나오게 됩니다. 혼자 있는 객체는 없으며, 어떤 객체에게 요청하는 객체는 클라이언트, 이에 대해 응답하는 객체는 서버로 생각할 수 있습니다. 이러한 요청과 응답의 협력 관계로 시스템 내 모든 기능들이 구현되게 됩니다.

마무리

이번 포스팅의 내용은 짧다면 짧다할 수 있지만, 객체 지향 프로그램의 정수인 역할과 구현을 나누는 이유에 대해 알아볼 수 있는 중요한 기록이었다고 생각합니다. 더 쉬운 변경을 가능하게 하기 위해 역할과 구현으로 잘게 나누고 이들의 협력을 기반으로 작은 객체들을 모아 더 큰 시스템을 설계하는 습관을 더욱 더 들여나가는 좋은 기회가 되었으면 좋겠습니다.

References

  • 인프런 내 김영한 강사님의 스프링 핵심 원리 - 기본편
This post is licensed under CC BY 4.0 by the author.