안녕하세요. 쉽게 이해하고 싶은 객체지향이라는 주제로 세미나를 진행한 크루 김찬규입니다.

“쉽게 이해하고 싶은” 이라는 키워드를 보면 아시겠지만 제가 객체지향설계를 너무나도 잘 알아서 발표를 하는 것이 아니라 객체지향을 설명하고자 할 때 어려움을 겪는 제 모습을 발견한 것이 주제 선정의 계기가 되었습니다. 객체지향을 잘 이해하고픈 마음으로 공부하는 과정에서 얻은 내용들을 크루 여러분께 공유해 드리고자 합니다.

객체지향 패러다임

패러다임

먼저, 객체지향프로그래밍을 이해하려면 패러다임 이 무엇인지 알아야합니다. 패러다임이란 한 시대의 사회 전체가 공유하는 이론, 방법과 문제의식 등의 체계 를 의미하는데요, 이는 ‘패턴’, ‘전형적인 예’를 의미하는 단어인 paradeigma라는 그리스어에서 유래한 단어입니다.

크루분들 중 몇몇 분은 방금 드린 두 설명이 무언가 비슷하면서도 어긋나 있다는 느낌을 받으셨을 겁니다. ‘사회 전체가 공유하는 이론’과 ‘전형적인 예’는 같은 의미라고 보기엔 너무 비약이 크지 않을까요?

사실, 과거에는 표준적인 모델을 따르거나 모방하는 상황을 가리키는 매우 제한적인 상황에서만 패러다임이라는 단어를 사용했다고 합니다. 그러다 바야흐로 1962년, 토마스 쿤의 과학혁명의 구조 의 출간이 도화선이 되어 패러다임이 지금과 같은 의미로 사용되게 되었습니다.

쿤은 과학의 발전이 진리를 향해 점진적으로, 그리고 연속적으로 접근한다는 기존의 보편적인 시각을 부정하였습니다. 과학은 계단식 발전으로 이루어진 것이 아니라 새로운 발견이 기존의 과학적 견해를 완전히 붕괴시키는 혁명을 통해 발전해왔다고 주장했지요. 그리고 이를 과학혁명이라고 일컬었습니다.

과학혁명 이란 과거의 패러다임이 새로운 패러다임에 의해 대체됨으로써 기존의 방향성이 변하는 것을 의미하고, 이를 패러다임 전환이라고 부릅니다. 예를 들면 천동설에서 지동설로의 변화가 있겠네요. 아, 양자역학의 등장 또한 패러다임의 전환이라고 볼 수 있습니다. 그리고 무엇보다 우리, 소프트웨어 개발자들에게 중요한 패러다임 전환은 절차형 패러다임에서 객체지향 패러다임으로의 변환이 있습니다.

프로그래밍 패러다임

프로그래밍에서 패러다임은 무엇을 의미할까요? 정답을 빨리 말씀드리자면 특정 시대의 개발자 공동체에 의해 수용된 프로그래밍 방법과 문제해결 방법, 코드 스타일 등을 의미합니다. 객체지향은 개중 하나에 불과하죠.

우리 Kernel360 크루들은 동일한 프로그래밍 스타일을 공유한다고 말할 수 있을 것입니다. Java와 Spring을 일반적으로 사용하시죠? Java를 사용한다고 함은 곧 객체지향 패러다임의 일원이라고 할 수 있습니다. 이렇게 비슷한 스타일을 서로 공유함으로써 불필요한 충돌을 방지하고, 동일한 규칙과 방법을 사용하는 개발자로 성장할 수 있다는 장점이 있습니다.

각 프로그래밍 언어가 제공하는 프로그래밍 스타일은 해당 언어가 가진 철학과 채택한 프로그래밍 패러다임에 따라 달라지기 때문에 프로그래밍 언어와 프로그래밍 패러다임은 서로 분리불가결합니다.

주의할 점을 두가지 말씀드리자면, 먼저 이 패러다임이라는 것은 절대불변이 아니라는 점입니다. 우리 Kernel360 크루원분들이 과정중에 무언가 획기적인 프로그래밍 체계를 선도해나가게 된다면 새로운 프로그래밍 패러다임이 등장할지도 모르는 일입니다.

두번째로는 프로그래밍 패러다임들은 서로 배타적인 것이 아니라 하나의 언어 안에서 공존함으로써 서로의 장단점을 보완하는 경향을 보인다는 점입니다. 예를 들어, 절차형 패러다임과 객체지향 패러다임을 접목시킨 C++, 함수형 패러다임과 객체지향 패러다임을 접목시킨 Scala를 예로 들 수 있겠네요. 이처럼 하나 이상의 패러다임을 수용하는 언어를 다중 패러다임 언어(Multiparadigm Language) 라고 부릅니다. 이를 보면 알 수 있듯이 프로그래밍 패러다임은 혁명적이기보다는 발전적이라고 이야기 할 수 있겠네요.

객체

그렇다면 객체지향 패러다임에서 객체(Object)란 무엇일까요? 객체가 무엇인지 쉽게 설명해 주실 수 있는 분이 계실까요?

넓은 의미에서 객체는 실세계에 존재하거나 생각할 수 있는 모든 것입니다. ㅎㅎ 좀 너무 두루뭉술한 거 같죠? 그렇다면 객체지향 패러다임에서 객체는 무엇을 의미할까요? 객체지향에서 객체는 정보와 행위를 가지는 프로그램의 단위 라고 정의할 수 있습니다.

그렇다면 객체는 알겠는데, 클래스와 인스턴스는 무엇이냐고 물을 수 있겠죠.

클래스 : 클래스는 객체 지향 프로그래밍에서 설계의 청사진(blueprint)이며, 데이터와 동작을 정의하는 틀입니다. 클래스는 객체를 생성하기 위한 멤버 변수(속성)와 메서드(동작)를 정의하며, 객체가 가져야 하는 속성과 행동을 결정합니다.

인스턴스 : 클래스를 기반으로 생성된 실체화된 (메모리에 할당된) 객체가 인스턴스 입니다.

객체 : 객체는 ‘설계도로 구현한 모든 대상’을 의미합니다. 클래스의 타입으로 선언되었을 때 객체라고 부르고, 그 객체가 메모리에 할당되어 실제 사용될 때 인스턴스라고 부릅니다.

하지만 이런 설명으론 쉽게 이해하기 말하기는 어렵죠. 간단한 비유를 통해 설명해보겠습니다.

클래스는 ‘붕어빵 틀’과 비슷합니다. 붕어빵 틀에는 붕어빵을 만들기 위한 형태(모양)와 내용물(팥 등)이 정의되어 있습니다. 이 틀을 기반으로 붕어빵(인스턴스)을 찍어낼 수 있습니다.

여기서 만들어진 붕어빵(인스턴스)은 각각 다른 모양과 내용물을 가질 수 있지만, 모두 같은 붕어빵 틀(클래스)을 기반으로 만들어졌습니다. 이렇게 클래스는 객체를 만들기 위한 틀이라고 할 수 있습니다.

음.. 그래도 비유가 부족할 수 있으니 설명을 조금 더 드릴게요.

설계도

주변을 한 번 둘러보시겠어요? 무엇이 보이시나요? 먼저 동료 크루원분들의 얼굴이 제일 먼저 눈에 띄시겠네요. 하지만 예시로 들기엔 너무 입체적이니 쉽게 설명할 수 있도록 모니터를 예로 들도록 하겠습니다.

모니터를 정면에서 보시면 어떻게 보이시나요? 이렇게 직사각형 화면이 보이실 겁니다. 이미지 교체 필요

반대로 옆에서 보면 어떻게 보일까요? 이렇게 적당히 긴 평행사변형 꼴로 보이시겠죠.

이미지 교체 필요

하지만 실제 모니터는 이렇게 직사각형이나 평행사변형꼴이 아니죠. 어느 방향에서 바라본 사물은 실제 사물을 평면에 투영한 것과 같습니다. 아래 그림과 같이 3차원, 혹은 그 이상의 고차원을 저차원에 투영한 것과 마찬가지입니다.

객체가 바로 이것과 같습니다. 실제 세계에 존재하는 사물이나 문제를 어떤 관점에서 바라보느냐에 따라서 서로 다른 객체가 만들어집니다. 객체는 실세계의 객체를 특정한 관점에서 바라보며 정의한 설계도 입니다.

그렇기에 무엇을 중점에 두느냐에 따라서 똑같은 문제를 정의하더라도 설계자의 생각에 따라 객체는 완전히 달라질 수 있습니다.

게다가 객체는 실세계의 객체를 100% 동일하게 표현할 필요가 없습니다. User 객체를 만들기 위해 해당 사람의 모든 정보(나이, 머리카락 색, 오늘 먹은 점심 메뉴 …) 를 필요로 할 이유가 있을까요?

소프트웨어를 만들기 위해 실재를 완전히 동일하게 옮겨다 놓을 필요는 없습니다. 우리는 적절한 정보와 적절한 기능을 적절한 객체가 잘 가지고 있기만 하면 됩니다.

정리

프로그래밍 패러다임은 우리의 눈과 같은 역할을 합니다. 눈이 하나라도 물체를 보는 것에는 지장이 없지만 두 개의 눈으로 물체의 깊이를 파악할 수 있듯이, 다중 패러다임을 통해서 정의하고자 하는 문제를 좀 더 잘 표현할 수 있습니다.

또한 바라보는 방향에 따라 사물의 모습이 달리 보이는 것처럼 객체는 프로그래머가 어디에 중점을 두느냐에 따라 다른 모습을 띌 수 있습니다.


객체지향 프로그래밍

절차지향

객체지향을 잘 알기위해선 절차지향에 대해서도 알아야겠죠. 절차지향은 무엇일까요? 절차지향 언어는 시간의 흐름에 따라서 구현하는 방식, 더 자세히 말하자면 프로그램의 순서와 흐름을 먼저 세우고 설계하는 방식입니다.

저작권이미지-교체필요

이미지를 보시면 Theater 안에서 모든 처리가 이루어지고 있구요, 이 관점에서 씨어터에서는 프로세스(Process)를 담당하고 Audience, TicketSeller, Bag, TicketOffice는 데이터(Data)입니다.

일반적으로 이처럼 프로세스와 데이터를 별도의 모듈에 위치시키는 방식을 절차지향적 프로그래밍이라고 부릅니다.

객체지향

그에 반해 객체지향은 먼저 자료구조와 이를 중심으로 한 모듈들의 관계를 먼저 설계한 다음 실행 순서와 흐름을 짜는 방식입니다. 객체와 객체사이의 메시지 교환을 통해 프로그램을 실행시킵니다.

저작권이미지-교체필요

수정한 설계는 자신의 데이터를 스스로 처리하도록 씨어터가 맡고 있던 프로세스의 적절한 단계를 Audience와 TicketSeller로 옮겼습니다. 판매원이 티켓을 팔고, 관객이 직접 티켓을 사는 거죠. 구조와 순서가 다르죠? 그 덕분에 데이터와 프로세스가 동일한 모듈 내부에 위치하게 되고, 그렇게 프로그래밍 하는 방식을 객체지향 프로그래밍이라고 부릅니다.

착각하지 말아야 할 점은 객체지향은 절차적이지 않고, 절차지향은 구조적이지 않다 라고 이해하시면 안됩니다. 객체지향은 절차를 간소화 해주는 프로그래밍 방식일 뿐, 절차를 무시하는 것이 아니에요. 객체 지향과 절차지향은 서로 상호 보완적으로 공존하고 있습니다.

왜 객체지향일까?

그럼에도 불구하고 객체지향 패러다임의 세계에 우리가 살고있는 이유가 있겠죠?

그 이유를 로버트 마틴 아저씨의 어록에서 찾을 수 있습니다. 마틴 아저씨는 소프트웨어 모듈이 만족해야할 세 가지 조건을 이야기 했는데요, 다음과 같습니다.

첫 번째, 모든 모듈은 실행 중 제대로 동작해야 한다.

두 번째, 변경을 위해 존재하므로 간단한 작업만으로도 변경이 가능해야 한다.

세 번째, 개발자가 쉽게 읽고 이해할 수 있어야 한다.

이 중에서도 두 번째 조건을 만족하기가 비교적 쉽기 때문에 객체지향 패러다임이 지배적이게 되었습니다. 객체지향 설계 핵심은 캡슐화를 이용해 의존성을 적절히 관리함하는 것이고 그를 통해 객체 사이의 결합도를 낮출 수 있습니다.

지갑, 컴퓨터, 등 자율의지가 없어보이는 객체마저 사람인 것마냥 의인화를 하여 각각의 객체가 자신의 기능, 자신에게 더 알맞는 책임을 그 자신이 직접 처리함으로써 코드의 응집도는 높아지고 기능 처리를 위해 다른 객체에 의존하는 것을 줄일 수 있습니다.

이것이 절차지향보다 객체지향이 좀 더 유연하다고 말하는 이유입니다.


객체지향프로그래밍의 네가지 특징

객체지향의 특징 모두 아시죠? 저만 해도 Kernel360 과정의 면접을 보기전에 한 번 더 외워서 왔는데… 모두 아셔셔 지겹겠지만 그래도 설명하도록 하겠습니다.

다형성

어떤 객체의 속성이나 기능이 상황에 따라 여러 가지 형태를 가질 수 있음을 의미

다시 말해, 동일 메시지를 수신했을 때 객체의 타입에 따라 다르게 응답할 수 있는 능력이 곧 다형성 입니다.

일반적으로 다형성은 지연 바인딩(동적 바인딩)을 통해 런타인에 바인딩함으로써 실현할 수 있습니다.

상속성

어떤 객체에서 소유한 자원을 다른 객체에서도 재사용할 수 있도록 하는 것

상속성은 객체지향 프로그래밍에서 코드 재사용과 계층 구조를 통한 효율적인 설계를 지원합니다. 어떤 클래스가 다른 클래스의 특성과 행위를 상속받으면, 상속받은 클래스는 기존 클래스의 모든 멤버를 포함하게 됩니다. 이로써 중복된 코드를 방지하고, 수정이 필요할 때 부모 클래스만 수정하면 자식 클래스들도 자동으로 업데이트되는 장점이 있습니다.

상속성은 코드의 재사용성을 높여주며, 계층 구조를 통해 객체 간의 관계를 명확하게 표현할 수 있습니다. 이는 유지보수성코드의 확장성에 긍정적인 영향을 미칩니다.

캡슐화

객체를 껍질로 감싸 외부에서 객체의 기능과 데이터에 함부로 접근하지 못하도록 하는 것

캡슐화가 좀 중요한데요, 결합도를 낮추기 위한 방법으로 캡슐화를 사용합니다.

모든 자원이 외부에서 접근 가능한 상태라면, 내가 자원을 변경하게 되면 해당 자원이 외부에도 영향을 끼치게 되니까, 그 영향을 생각하지 않고서는 코드를 변경하기가 어렵겠죠?

접근을 제한하면 객체와 객체사이의 결합도를 낮출 수 있기 때문에 부담없이 변경할 수 있다 는 것입니다.

인스턴스 변수는 private, 메서드는 public 으로 설정하는 식으로 클래스의 내부와 외부를 구분하여 경계의 명확성이 객체의 자율성을 보장하게 하여 프로그래머에게 구현의 자유를 보장할 수 있습니다. (클라이언트 프로그래머의 영향을 걱정하지 않고 내부구현을 마음대로 바꿀 수 있다 == 자유)

추상화

객체의 공통적인 속성과 기능을 추출하여 정의하는 것

추상화를 사용하면 세부적인 내용을 무시한 채 상위 정책을 쉽고 간단하게 표현할 수 있습니다.

새로운 자식클래스들은 추상화를 이용한 상위의 협력 흐름을 그대로 따르게 됩니다.

따라서, 기존 구조를 변경하지 않고도 새로운 기능을 쉽게 추가하고 확장할 수 있게 되죠. (설계가 구체적인 상황에 결합되는 것을 방지하기 때문!!)

정리

이러한 특성들은 객체지향 프로그래밍이 유연하고 효율적인 소프트웨어 설계를 가능하게 하는 핵심 원리들입니다.

그렇기 때문에 객체 지향이 왜 이런 특성들을 필요로 하게 되었는지 생각하는 것이 중요합니다. 객체지향은 왜 이런 특성을 필요로 하게 되었을까요? 이 부분을 공부하는 것은 크루분들께 숙제로 내드리도록 할게요.


추상클래스와 인터페이스

왜 하늘은 추상클래스를 낳으시고 인터페이스를 또 낳으셨는가…?

그렇다면 여기서 궁금증이 하나 드는데요, Java에는 추상클래스와 인터페이스가 동시에 존재합니다.

하지만 추상 클래스 하나만 존재하면서 추상 클래스 안에 추상 메서드를 여러개 두면 될 거 같은데 왜 인터페이스를 따로 만들어둔 것일까요? 결론부터 말씀드리자면 이 또한 관점의 차이 라 말할 수 있습니다.

추상클래스

최소 하나 이상의 추상 메소드를 포함하는 클래스

추상 클래스는 똑같은 일을 하는 녀석들이 여러 개 생겨야 할 때 사용합니다. 다음과 같은 특징을 가지죠.

  • 인스턴스를 생성할 수 없다
  • 다중 상속이 불가능
  • 일반 변수, 상수 모두 가능
  • 접근 지정자 제한 없음
  • extends 키워드 사용
  • 최소 하나 이상의 추상 메서드를 가지는 클래스 ( 추상 메서드가 아닌 미리 구현된 메서드도 가질 수 있다!!)

추상 클래스는 동작이 정의되어 있지 않은 추상 메소드를 포함하고 있으므로,  추상메서드가 구현이 되어야 인스턴스를 만들 수 있기에 직접 객체를 구현할 수 없습니다.

또한 다중 상속이 불가능한 이유는 동일한 시그니쳐를 가진 두 부모 클래스를 상속받을 때, 해당 메서드가 이미 구현된 메서드라면 어떤 클래스의 메서드를 상속받아야하는지 파악할 수 없기 때문입니다.

추상 클래스는 먼저 상속을 통해 자식 클래스를 만들고, 자식 클래스에서 추상 클래스의 모든 추상 메소드를 오버라이딩하고 나서야 비로소 자식 클래스의 인스턴스를 생성할 수 있게 됩니다.

인터페이스

추상 메서드와 상수 필드로 구성된 프로그래밍 요소

인터페이스는 다음과 같은 특징을 가지고 있습니다.

  • 인스턴스를 생성할 수 없다
  • 다중 상속 가능
  • 상수(static final)만 사용 가능
  • 접근지정자는 public
  • implements 키워드를 사용

인터페이스의 주요 목적은 다양한 클래스들이 특정한 동작을 공유하도록 강제하는 것입니다. 인터페이스를 구현하는 클래스들은 동일한 메서드를 가지며, 각 클래스는 자신의 방식에 맞게 메서드를 구현합니다.

이를 통해 코드의 일관성을 유지하면서도 다양한 객체의 동작을 보장할 수 있습니다. 따라서, 추상 클래스와 인터페이스는 서로 보완적으로 사용되며, 상황에 따라 적절한 구조를 선택하여 프로그래밍의 유연성을 높이게 됩니다.


마무리

마무리하며 정리하자면, 두가지만 기억하시면 될 것 같습니다.

하나, 객체지향 설계는 일반적으로 객체가 자기 자신을 스스로 책임지지만, 절차지향 설계는 책임을 지는 놈이 따로 있다.

, 추상클래스는 상속, 인터페이스는 선언을 위해 사용한다.

지금까지 객체지향에 대해서 간략히 알아보았습니다. 도움이 되셨기를 바라면서, 기회가 된다면 객체지향의 SOLID 원칙에 대해서도 설명드리도록 하겠습니다. SOLID 가 궁금하신 분들은 먼저 찾아보셔도 좋을 것 같아요.