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

JBB·2022년 12월 26일
0

Paradigm

목록 보기
1/1

객체 지향…? 객체를 지향하다

💡 우리는 개발을 배우면서 한번쯤은 OOP 즉, 객체지향 프로그래밍이라는 말을 들어볼겁니다.
검색도 해보고, Object 같은 관련 서적을 찾아보기도 하고 말이죠. 하지만 객체지향을 설명해보라는 질문을 받으면 당황하곤 합니다.
객체지향 프로그래밍의 개념을 이해하려면 무작정 OOP와 관련 내용을 찾아보기 전에 OOP가 왜 등장하기 시작했는지를 알아볼 필요가 있습니다.

객체지향 이전의 프로그래밍 패러다임

순차적 (비구조적) 프로그래밍

작성한 코드의 흐름에 따라 순서대로 동작하며 프로그램을 완성하는 방식입니다.
간단한 프로그램의 경우, 이렇게 코드를 작성하면 흐름이 눈으로 보이기 때문에 매우 직관적입니다.
그러나 프로그램의 규모가 커지면 어떻게 될까요?
A → B → C 순서로 동작을 구현하다가 C에서 다시 A로 돌아가야한다고 가정한다면 goto라는 방식을 사용해야합니다.

다만 이 때 goto문을 무분별하게 활용하게 되면, 더이상 순차적이라고 볼 수 없고, 동작이 직관적이지 못하게되어 유일한 장점이 사라지게 됩니다.

그래서 등장하는 것이 절차적, 구조적 프로그래밍입니다.

절차적 (구조적) 프로그래밍

절차적 프로그래밍에서 ‘절차’란 함수를 의미합니다.
따라서 절차적 프로그래밍이란? 반복되는 동작을 함수 및 프로시저 형태로 모듈화하여 사용하는 방식입니다.

💡 Function (함수) vs Procedure (절차) ?
언어마다 다를 수 있지만 일반적으로 함수는 값을 반환하는 형태이고, 프로시저는 명령을 실행합니다.
함수는 입력(input)을 기반으로 값을 계산하는데 사용되고, 프로시저는 순서대로 실행할 수 있는 명령집합이라고 볼 수 있습니다.
현재 대부분의 프로그래밍 언어에서는 함수에서도 명령집합을 가질 수 있어 프로시저가 어떤 친구인지만 알면 됩니다 🙂

절차적 프로그래밍은 반복 동작을 모듈화하여 코드를 많이 줄일 수 있습니다. 하지만 프로시저 자체가 너무 추상적이라는 단점이 있었습니다.

예시를 통해 알아봅시다.

도서관의 도서 관리 프로그램을 개발한다고 가정할때,

  • “책”이라는 자료형을 구현
  • 책에 대한 함수를 구현

절차적 프로그래밍에서는 이 두가지를 따로 생각할 수 밖에 없습니다. 책은 책이고, 책이 동작하는 함수는 따로 있기 때문에 같은 소스코드 파일 내에 있더라도 이 둘의 연관 여부를 한번에 알아차리기 쉽지 않습니다.
즉, 논리적으로 묶여있을 수 없는 구조이기 때문에 동작이 추상적입니다.

따라서, 이를 묶기 위한 패러다임으로 “객체지향 프로그래밍”이 등장하게 됩니다.

객체지향 프로그래밍의 등장

우리가 개발해야할 어떤 개념의 자료형, 함수를 “객체”형태로 묶어서 관리하기 위한 프로그래밍 방법론이 등장했습니다. 핵심 포인트는 객체 내부에 자료형 필드와 함수가 함께 존재한다는 것이였습니다. 가능한 모든 요소를 객체로 만들어서 관리하는 것이 객체지향 프로그램이라고 생각하시면 됩니다.

좀전에 봤던 도서 관리 프로그램을 이번에는 객체지향으로 구현한다고 가정해봅시다.
우리는 책이라는 객체를 만들어서 책의 제목, 저자, 페이지 수와 같은 자료형 필드와 대출하기, 반납하기등의 메소드를 만들어서 책이라는 객체를 관리할 수 있습니다.
이렇게 되면, 절차적 프로그래밍으로 구현했던 것보다 직관적이고 가독성이 증가합니다.

결론적으로 객체지향 프로그래밍의 도입으로 객체간의 독립성이 생기고, 중복되는 코드의 양이 줄어듭니다.
이말은 곧 유지보수가 용이해집니다.

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

1. 추상화 (Abstraction)

💡 객체들이 공통적으로 필요로 하는 속성 혹은 동작을 하나로 추출해내는 작업

추상적인 개념에 의존하여 설계해야 유연함을 갖출 수 있습니다.
즉, 세부적인 사물들의 공통적인 특징을 파악한 후, 하나의 묶음으로 만들어내는 것이 추상화입니다.

그림의 동물들은 생김새는 다 다르지만, 한가지 공통점이 있습니다.
벌써 알아차리신 분도 계시겠지만 이 동물들은 모두 포유류입니다.

바다속을 헤엄치는 돌고래도 땅위의 고양이도 다른 모든 포유류도 모두 공통적으로 ‘새끼를 낳는다.’라는 공통점이 있습니다.

이러한 공통점을 추상화로 구현해두면 다른 포유류 객체를 생성할때,
우리는 번식방법에 대한 코드를 다시 작성할 일이 줄어듭니다.

2. 캡슐화 (Encapsulation)

💡 정보 은닉화를 통한 높은 응집도, 낮은 결합도를 유지할 수 있도록 설계하는것

응집도?

  • 모듈의 독립성을 나타내는 개념으로, 모듈 내부 구성요소 간의 연관 정도
  • 정보 은닉의 확장 개념으로, 하나의 모듈은 하나의 기능을 수행하는 것을 의미

결합도?

  • 모듈 내부가 아닌 외부의 모듈과의 연관도 또는 모듈 간의 상호의존성을 나타내는 정도
  • 소프트웨어 구조에서 모듈 간의 관련성을 측정하는 척도

딱딱하고 어려운말로 설명하면 한없이 어려워지는 개념이지만, 여기서 응집도란 쉽게 말해서 한 쪽의 변화가 일어나도 다른 쪽에서 미치는 사이드 이펙트를 최소화 시키는 것을 의미합니다.
즉, 객체 내부의 구현이 어떻게 되어있는지 감추는 것입니다. 이를 통해 외부에서 실수가 발생해도 객체를 손상시키는 일을 방지할 수 있습니다.

결합도는 어떤 기능을 실행할 때 다른 클래스나 모듈에 얼마나 의존적인지를 나타내는 지표입니다.
여기서 잠깐 우리는 객체지향은 객체 간의 독립성을 강조하기 위해 등장했습니다. 그런데 결합도가 높아지면 객체지향으로 설계하는 의미가 있을까요?
따라서, 독립적으로 만들어진 객체들 간의 의존도를 최대한 낮게 만드는 것이 중요합니다. 때문에 소프트웨어 공학적으로, 객체 내의 모듈 간의 요소가 최대한 밀접한 것으로 구성하여 응집도를 높이고, 결합도를 줄여야 요구 사항 변경에 대처하는 좋은 설계라고 배웁니다.

그렇다면 어떻게? 일반적으로 높은 응집도와 낮은 결합도는, ‘은닉화’를 통해 이루어낼 수 있습니다. 은닉화의 예시로는 외부의 접근이 불필요한 객체에 대해 접근지정자를 private으로 설정하여 접근에 제한을 두는 것입니다. 외부 객체는 객체 내부의 구조를 모르게 하고, 해당 객체가 접근을 허가한 필드와 메소드만 이용할 수 있도록 하여 의도하지 않은 동작 오류를 방지하고 유지보수 효율을 높일 수 있습니다.

3. 상속 (Inheritance)

💡 여러 개체들이 지닌 공통된 특성을 부각시켜 하나의 개념이나 법칙으로 성립하는 과정

상속의 개념은 우리가 흔히 알고있는 상속처럼, 자식 객체가 부모 객체로부터 필드 혹은 메서드 등을 물려받아 사용할 수 있게, 혹은 조금 변화시켜 사용할 수 있게 해주는 것입니다. 조금 더 어렵게 말하면 상속은 자식 객체를 외부로부터 은닉하는 일종의 캡슐화 입니다.

이전에 추상화를 통해 설명한 포유류와 더해서 알아봅시다. 어떤 동물(공룡)객체는 먹이로 포유류만 먹는다고 가정해 봅시다. 이 때 포유류라는 부모객체는 자식 객체에 해당하는 고래, 돼지, 고양이, 개 등을 은닉해둔 상태입니다.

이때 이 포유류만 먹는 공룡 관점에서는, 포유류가 어떤 동물이 해당하는지 모르는 상태입니다. 하지만 이 공룡 입장에서는 먹이가 어떤 동물인지 크게 중요하지 않습니다. 그냥 맛있는 포유류면 됩니다.
포유류를 먹었을때 그 포유류가 어떤 동물인지는 공룡 입장에서는 영향이 없어야 한다는 점이 가장 중요합니다. 따라서 캡슐화를 통해 공룡입장에선 확인할 수 없도록 하는 것입니다.

💡 이처럼, 상속 관계에서는 단순히 하나의 클래스 안에서 속성, 메소드들의 캡슐화에 한정되지 않습니다.
즉 자식 객체 또한 캡슐화되어 ‘공룡’과 같은 외부 객체에 은닉하는 것으로 확정되는 것입니다. 이렇게 자식 객체를 캡슐화 해두면, 외부 객체에서는 개별적인 자식 객체와 무관하게 개발을 이어갈 수 있는 장점이 있습니다.

왕관을 쓰려는 자, 그 무게를 견뎌라

상속을 활용하면 상위 객체의 구현을 활용함으로써, 코드 재사용이 용이해집니다.
그러나 상속을 통한 재사용을 할 때 나타나는 단점 또한 명확합니다.
따라서 객체지향 프로그래밍에서는 ‘코드 재사용’을 목적으로 하는 상속 행위는 엄격히 금합니다.

  1. 부모 객체의 변경이 불편해짐

    부모 객체에 의존하는 자식 객체가 많아졌을때 부모 객체의 변경이 필요하다면
    이를 의존하는 자식 객체는 영향을 받게 됩니다.

  2. 불필요한 클래스의 증가

    유사 기능 확장시, 필요 이상의 불필요한 클래스를 만들어야할 수 있습니다.

  3. 잘못된 상속 사용

    상속 관계라고 볼 수 없는 객체들을 단순히 구현을 재사용하기 위해 상속을 사용하게되면,
    문제가 발생할 수 있습니다.
    → 상속받는 객체가 부모 객체와 IS-A 관계가 아닐때 발생합니다.

💡 그럼 어떻게 하라고!!
Favour composition over inheritance
객체의 Composition(구성)은 객체 내부 필드에서 다른 객체를 참조하는 방식으로 구현합니다.
상속에 비해 런타임 구조가 복잡하고 구현이 어렵지만, 변경시 유연함을 확보할 수 있다는 것이 장점입니다. → 같은 종류가 아닌 객체를 상속하고 싶을 땐, 객체 구성을 먼저 적용해 볼 필요가 있습니다.

상속은 반드시

  • IS-A 관계가 성립할 때
  • 재사용을 위한 상속은 X, 기능의 확장 관점 O

이러한 상황에서만 사용되야 합니다. 상속을 코드 재사용 개념으로 이해하시면 절대 안됩니다. 코드를 재사용하는 개념으로 무분별한 상속을 사용하는 경우가 간혹 있는데 이렇게 되면 객체간의 결합도가 너무 높아져 유지보수가 오히려 어려워지는 현상이 발생합니다. 일반적인 개념을 구체화하는 상황에서 상속을 사용합시다.

IS-A
문자 그대로 포함 관계를 지칭합니다. 한 객체 A가 다른 객체 B의 자식 객체임을 이야기합니다.
→ 곰은 포유류다.

HAS-A
상속이 아닌 구성(Composition)이 필요한 관계로 한 객체가 다른 객체에 속할때를 말합니다.
→ 바다에는 고래가 있다.

4. 다형성 (Polymorphism)

💡 서로 다른 객체가 같은 동작 수행을 명령 받았을때, 각자의 특성에 맞는 방식으로 동작하는것

다형성은 객체지향 패러다임의 핵심입니다. 추가로 다형성과 상속의 시너지는 엄청납니다. 다형성을 통한 구현으로 코드를 간결하게 해주고, 유연함을 갖추게 해줍니다. 또한, 구체적으로 현재 어떤 객체가 참조되는지 무관하게 헐렁한 프로그래밍이 가능해집니다.

돼지 소 개 고양이등 포유류들은 같은 동물유형이지만 각각 울음소리가 다릅니다.
우리는 포유류라는 동물유형을 일반화(상속)하여 돼지, 소, 개, 고양이등의 객체를 만들었습니다.
포유류라는 클래스에 “짖어”라는 메소드를 만들어서 실행했을 때, 자식 객체들(돼지, 소, 개, 고양이등)은
각기다른 방법으로 울음소리를 내는것은 다형성이 부각된 부분입니다.

상속 관계에 있다면, 새로운 자식 객체가 추가되어도 부모 객체의 함수를 참조해오면 되기 때문에 다른 클래스는 영향을 받지 않습니다.

0개의 댓글