경일게임아카데미 멀티 디바이스 메타버스 플랫폼 개발자 양성과정 정리 번외 2. 객체 지향 프로그래밍 (발표용)

Jinho Lee·2022년 6월 5일

1. 객체 지향 프로그래밍 (Object-oriented programming)

객체 지향 프로그래밍은 객체를 중심으로 프로그램을 구성하는 방법으로, 절차 지향 프로그래밍의 한계를 극복하기 위해 등장하였습니다.

  • 절차 지향 프로그래밍의 한계

    1. 데이터와 데이터를 다루는 함수가 분리되어 있다.

      • 이로 인해 함수가 항상 첫 번째 인자로 주소값을 받아야 했고, 이는 쓸데없이 함수가 길어지는 요인이 되었습니다.

      • 마찬가지로 함수 내부에서도 포인터로 데이터를 다뤄야 하는 불편함이 있었습니다.

    2. 함수의 이름을 항상 다르게 작성해야 한다.

      • C언어를 생각해보면 함수의 이름은 전역 이름공간을 사용했기에, 모든 함수가 절대로 이름이 겹쳐서는 안 되었습니다.

      • 이는 프로그램이 복잡해지면 함수명의 결정에 큰 어려움을 주었지요.

    3. 프로그램을 확장하기 불편하다.

      • 항상 어떤 함수가 필요한지 명확히 호출해야 했기에, 프로그램에 수정 사항이 생기면 그에 따른 변경 사항을 관련된 모든 함수에 적용해야 했습니다.

      • 상속의 개념이 없었기에, 비슷하게 코드를 중복 사용하고자 하려면 분기문을 많이 사용해야 했습니다.

      • 이를 최대한 줄이기 위해 함수 포인터를 사용하는 등의 대책이 나왔지만, 이 또한 추가적인 데이터를 사용했죠.

  • 결국, 유지 보수가 힘들다.는 것이 절차 지향 프로그래밍의 한계였습니다.

  • 이러한 불편함을 줄이기 위한 고민의 결과가 객체 지향 프로그래밍입니다.

주요 4 개념

객체 지향 프로그래밍에는 4가지 주요 개념이 있습니다.

1.캡슐화(Encapsulation)

  • 데이터와 데이터를 다루는 함수를 같이 작성하는 것입니다.

  • 이로 인해 더 이상 포인터 변수를 작성할 필요도 없어질 뿐더러, 같이 있는 데이터와 함수는 서로 접근이 쉬워지기에 객체를 만들 때 비교적 직관적인 초기화가 가능해지는 것입니다.

  • 또한, 객체에서 함수를 호출하는 것과 접근 한정자를 이용해 객체의 내외부를 구분 짓는 것이 가능해졌습니다.

2.상속(Inheritance)

  • 코드를 물려받아 재사용할 수 있는 것을 의미합니다.

  • 각 타입을 상위와 하위로 나누고, 상위에 중복되는 코드를 모으고 하위 타입이 이를 '상속'하게 하는 것으로 코드의 중복을 줄이고, 이전에 비해 더 작은 단위로 모듈화가 가능해 집니다.

3.추상화(Abstraction)

  • 구현 세부 정보를 숨기는 일반적인 인터페이스를 정의하는 행위입니다.

  • 여기서 인터페이스란, 서로 다른 부분이 서로에게 영향을 미치는
    영역을 의미합니다. 스마트폰이라면 액정에 해당하겠지요.

  • '추상화'가 잘 이루어지면 잘 이루어질 수록 데이터를 다루기 쉬워지고, 이는 프로그램을 작성하는 입장에서 더 빠르고 편리하고 안전하게 작업을 할 수 있다는 것입니다.

  • 또한, 구현 세부 정보가 잘 숨겨져 있다면 내부에 변화가 있더라도 사용하는 입장에서는 알 수 없으니, 그러한 변화를 더 잘 만들 수 있으니 확장성 또한 좋아진다고 볼 수 있습니다.

4.다형성(Polymorphism)

  • 다형성은 문자 그대로 이해하자면 '여러가지 형태를 가지는 것'을 의미합니다.

  • 다만 OOP에서는 형태란 '서로 다른 동작'으로 이해할 수 있습니다.

  • 그 사용 되는 모습, '인터페이스'는 유지한 채로 내부의 동작이
    바꾸어지는 것입니다. 이는 C++에서는 '가상 함수'를 통해 이용할 수 있으며, 이런 변경을 '오버라이딩'이라고 합니다.

  • 이러한 다형성은 객체 지향 프로그래밍의 핵심입니다.

2. OOP의 구조

객체(Object)

  • 상태와 행동을 함께 지닌 실체로, 프로그래밍에서 주로 다루는 메모리입니다.

클래스(Class)

  • 클래스란 사용자 정의 타입으로, 멤버의 집합체입니다.

  • 인스턴스를 만들기 위한 틀의 역할을 합니다.

  • 멤버에는 데이터와 함수가 들어갈 수 있으며, 각각 필드와 메소드라고 부릅니다.

  • 그 외에도 내부 타입, 멤버 템플릿 등을 작성할 수 있습니다.

인스턴스(Instance)

  • 클래스를 바탕으로 할당된 메모리로, 객체의 부분 집합입니다.

3. 객체 지향적 설계

OOP가 주류가 된 이유

  1. 다형성에 의한 높은 확장성
  2. 캡슐화를 통한 직관적 모델링
  • 현재 OOP가 주류가 된 이유로는 다음의 두 가지를 들 수 있습니다.
  • 첫째, 다형성에 의한 높은 확장성과, 둘째, 캡슐화를 통한 직관적 모델링.
  • 다만, 이러한 장점을 살려 설계한다는 것은 어려운 일입니다.
  • 그런 설계를 돕기 위한 테크닉은 데이터 주도 설계, 책임 주도 설계, 테스트 주도 개발 등이 있습니다.
  • 그 중, 책임 주도 설계에 대해 알아보려 합니다.

책임 주도 설계(Responsibility-driven Design)

  • 책임 주도 설계란 객체 지향적 설계 테크닉 중, 객체 각각은 각자의 역할이 있고, 이에 대해 수행을 요청 받으며, 이를 처리해야 하는 책임이 있다는 점에 집중하는 설계 테크닉입니다.

협력

  • 한 소프트웨어에는 복수의 객체가 있습니다. 이 객체들은 소프트웨어의

  • 기능을 구현하기 위해 서로 요청하고 처리하며 협력합니다.

  • 여기서 좋은 협력이란, 요청이 그 요청을 처리할 수 있는 객체에게 갔을 때를 말하는 것입니다.

  • 따라서 객체 지향 설계는 책임을 그 책임을 수행하기에 적절한 객체에게 부여하는 것입니다.

책임

  • 책임은 한 객체가 다른 객체로부터 요청을 수신하고, 이를 처리해야 할 때 발생합니다.

  • 즉, 책임이란 객체가 수행해야 하는 행동이라 할 수 있습니다.

  • 그러므로 객체 지향 설계에서는 이 책임의 결과와 조건, 즉

    1. 소프트웨어의 기능과

    2. 기능을 수행하기 위한 협력관계

를 명백히 서술할 필요가 있습니다.

  • 여기서 오해해서는 안되는 부분은, 책임은 객체가 '무엇을' 해야 하는가를 설명할 뿐, '어떻게' 해야 하는가는 설명하지 않는다는 점입니다.

  • 책임을 어떻게 수행할지는 객체가 스스로 결정해야 할 부분입니다.
    책임은 수행 방법을 제한할 정도로 구체적이어도,
    협력의 의도를 표현 못할 정도로 추상적이어도 안되는 것입니다.

메시지(Message)

  • 객체 지향 프로그래밍에서, 객체 간의 의사소통 수단입니다.

  • 객체 외부에서의 요청을 표현하는 것을 메시지,

  • 그 요청을 내부에서 처리하기 위한 방법이 멤버 함수인 메소드라고 할 수 있습니다.

다형성에 대한 재해석

  • 책임 주도 설계의 시점으로 다형성을 재해석하자면, 다형성이란 서로 다른 객체들이 동일한 책임을 공유하는 것이라고도 할 수 있습니다.

  • 즉, 메시지 송신자의 관점에서 책임이 어떤 객체가 어떤 방법으로 처리하는가와 무관하게, 그저 수신자가 메시지를 받고 책임이 수행된다는 결과만이 중요한 것입니다.

  • 그렇기 때문에 객체 지향적 설계에서 다형성을 이용해 협력이 유연해지고, 송신자와 수신자 사이의 결합도가 낮아짐으로써

  • 유연하고, 확장 가능하고, 재사용 가능한, 즉 강력한 설계가 완성되는 것입니다.

SOLID 원칙

  • 클린 아키텍처에서 소개된 객체 지향적 설계의 바탕이 되는 5가지 원칙입니다.

1. 단일 책임 원칙(Single Responsibility Principle)

  • 소프트웨어의 각 모듈은 그 변경의 이유가 하나여야 합니다.

  • 이는 객체마다 단 하나의 책임이어야 객체가 간결하고 명확해지며, 유지 보수가 간편하기 때문입니다.

  • 여러가지 책임을 가진 전지전능한 객체(God object)는 그 복잡도로 인해 자멸하기 마련입니다.

2. 개방-폐쇄 법칙(Open-Closed Principle)

  • 객체는 확장에는 열려 있어야 하고, 변경에는 닫혀 있어야 한다는 원칙입니다.

  • 기존 코드를 수정하기보다 새로운 코드를 추가하는 쪽으로 시스템을 변경할 수 있게 설계해야 후에 유지 보수를 할 때도 쉽게 시스템을 변경할 수 있다는 것입니다.

  • 이는 캡슐화와도 관련이 깊은 원칙입니다.

3. 리스코프 치환 원칙(Liskov Substitution Principle)

  • 하위 타입에 관한 원칙으로, 한 T 타입 객체의 자리를 S 타입의 객체로 모두 치환했을 때 프로그램의 행위가 변하지 않는다면 S가 T의 하위 타입이라는 것입니다.

  • 이는 is-a 관계로 설명하면 이해하기 쉽습니다.

  • S is a T라는 문장에 넣었을 때 말이 된다면, S는 T의 하위 타입인 것입니다.

  • 예를 들어, Cat과 Pet이라는 타입들로 생각해봅시다. Cat is a Pet 은 말이 되는 문장입니다. 따라서 우리는 Cat이 Pet의 하위 타입이라고 이해할 수 있습니다.

4. 인터페이스 분리 원칙(Interface Segregation Principle)

  • 어떤 타입이 다른 타입에 의존한다고 할 때, 여기서 필요한 인터페이스만 분리해서 의존하게 설계하라는 원칙입니다.
  • 의존하는 부분만 분리해서 설계할 수 있다면, 결합도를 메시지 수준으로 낮출 수 있겠죠.
  • 즉 이를 통해 앞서 설명했듯이 설계가 유연해지고, 확장 가능해지고, 재사용 가능하게 됩니다.

5. 의존성 역전 원칙(Dependency Inversion Principle)

  • 고수준 정책을 구현하는 코드는 저수준 세부사항을 구현하는 코드에 절대로 의존해서는 안되며, 세부사항이 정책에 의존해야 한다는 원칙입니다.

  • 이는 인터페이스와 같은 전체적이고 추상적인 개념에 객체와 같은 구체적인 개념이 의존해야 한다는 것이며, 이로써 모듈 사이를 구분지어 확장성을 높입니다.

  • 즉, 다형성을 적극 활용하라는 것입니다.

0개의 댓글