5 분 소요

해당 글의 내용은 객체 지향 프로그래밍에서 쉽게 가질 수 있는 잘못된 개념과 오해들에 대해 객체지향의 사실과 오해클린 소프트웨어라는 책의 내용과 더불어 내 생각을 덧붙여 정리한 것이다. (아직 작성 중이다..)

시작하기 전에 여러분들이 인지해야 할 잘못된 오해 한 가지를 짚어주려한다. “객체 지향 세계는 실세계를 프로그래밍 세계에 구현한 것이 아니다.” 그 이유를 깨닫는다면 객체 지향 프로그래밍 설계를 넘어서 개발을 대하는 생각과 태도가 많은 부분의 변화를 꽤 할 것이라 확신한다.

객체란 무엇인가

객체는 상태, 행위, 식별자를 가진 무언가라면 모두 객체라고 지칭될 수 있다. 그 예시는 다음과 같다.

객체 예시

  • 사람
  • 자동차
  • 강아지
  • 책 등

여기서 우리가 주목해야 할 것은 이다. 책에는 행위가 있는가? 필자는 없다고 생각한다. 책은 스스로 무언가의 행위를 하지 않는다. 그런데 우리 개발자들은 도서(book)라는 객체를 프로그래밍 세계에 반영하여 시스템을 구축하곤 한다. 왜 그럴까..? 결론은 “실제 세계와 프로그래밍 세계가 엄연히 다르기 때문”이다. 계속해서 살펴보자.

상태, 행위, 식별자에 대하여

  • 상태: 값.
    변수를 의미하며 지금 순간에 특정 객체가 어떠한 값을 가지고 있다면 “상태를 가졌다”고 표현한다.
  • 행위: 동작.
    함수/메서드를 의미하며 특정 객체가 어떠한 행위든지 할 수 있다면 “행위를 가졌다”고 표현한다. 어떤 유형의 행위일지라도 상관없다.
  • 식별자: 이름.
    객체에 부여된 이름을 의미한다. 객체라면 특정 객체를 지칭할 수 있는 고유한 식별자가 존재해야만한다.

상태, 행위, 식별자를 포함한다면 그 어떤 것이든 객체라는 것을 꼭 기억하고 계속해서 살펴보자.

객체 지향 프로그래밍(OOP)이란 무엇인가

OOP: Object-Oriented Programming(객체 지향 프로그래밍)

이를 더 잘 이해하기 위해서는 객체 지향 프로그래밍의 개념이 왜 생겨났는지를 이해하면 좋다.

OOP의 등장

인터넷을 이용하는 고객이 많아지면서 자연스레 프로그램 별 규모도 굉장히 커졌다. 이러한 대규모 시스템들을 개발해보니 유지보수가 안되면서 시스템은 엉망이 되었다. 당시 소프트웨어 장인들은 소프트웨어의 지속 가능한 성장을 위해서는 “인간이 이해하기 쉬운 방식의 프로그램”이 필요하다고 느꼈다. 그렇게해서 등장한 것이 객체 지향 프로그래밍이다.

객체 지향 프로그래밍은 “인간이 이해하기 쉬운 방식의 프로그램”을 개발하기 위해 등장한 기법이라는 걸 꼭 명심하고 계속해서 살펴보자.

현실과 객체 지향 프로그램

현실에서는 책을 스스로 움직일 수 있는 존재로 바라보지 않는다. 그러나 소프트웨어에서는 책을 스스로 움직이는 존재로 바라본다. 그 이유를 간략하게 나마 이해해보자.

예를 들어, 사람이 책을 읽는 행위를 한다고 할 때 현실에서는 다음과 같이 할 것이다.

현실

사람이 5 페이지를 펼치면 책은 사람에 의해 5페이지가 펼쳐진다. 즉, 책은 어떠한 행위도 하지 않은 채 사람의 행위에 의해 5페이지로 펼쳐진 것이다.

객체 지향 프로그램

사람이 5페이지를 펼치면 책은 본인의 page 값(상태)을 5로 변경하는 특정 메서드(행위)를 실행한다. 아마 아래처럼 구현할 수 있을 것이다.

예시 코드

여기서는 java로 구현한다. Person 클래스는 책을 펼치는 행위를 하고, Book 클래스는 페이지를 변경하는 메서드를 가진다.

Person: 사람

사람은 책을 읽으면 지식 점수가 10 올라가고, 책 객체에게는 turnToPage() 메서드로 책 페이지를 이동해달라는 메시지를 요청하게 된다. 아래 코드를 살펴보자.

class Person { /* 식별자 */
    private int knowledge; /* 상태 */

    public Person() {
        this.knowledge = 0; // 초기 지식 점수
    }

    public void readBook(Book book, int page) { /* 행위 */
        book.turnToPage(page); // 책에게 페이지 이동을 요청
        increaseKnowledge(); // 내 지식 점수 증가 시킴
        System.out.println("사람이 책을 읽기 시작합니다. 현재 페이지: " + book.getPage());
    }

    private void increaseKnowledge() { /* 행위 */
        this.knowledge += 10; // 지식 점수 증가. 예시로 10 증가
    }
}
Book: 책

책은 turnToPage()getPage() 두 가지의 행위를 외부에 제공한다. 해당 요청이 전달되면 책은 스스로의 상태 값을 변경하는 등의 행위를 수행한다.

class Book { /* 식별자 */
    private int page; /* 상태 */

    public Book() {
        this.page = 1; // 초기 페이지는 1로 설정
    }

    public void turnToPage(int page) { /* 행위 */
        this.page = page;
        System.out.println("책이 " + page + "페이지로 펼쳐집니다.");
    }

    public int getPage() { /* 행위 */
        return this.page;
    }
}
Main: 메인 호출부

책과 사람 객체를 new를 통하여 인스턴스화한 후 readBook 메서드를 실행한다. 즉, 사람이 책을 읽는 동작을 수행한다.

public class Main {
    public static void main(String[] args) {
        Book 객체지향의_사실과오해 = new Book();
        Person 우림 = new Person();

        우림.readBook(객체지향의_사실과오해, 5); // 사람이 책의 5페이지를 펼침
    }
}

책을 의인화하여 객체로 구현하는 이유

현실 세계라면 책의 페이지를 5로 변경시킨 주체는 사람이 될 것이다. 그러나 객체 지향의 세계에서 모든 객체는 자신의 상태를 스스로 관리하는 자율적인 존재다. 인간 객체가 본인의 지식 점수를 증가시키거나 1m를 이동하거나 이름을 개명하는 등의 행위 주체가 자신인 것처럼 책 객체의 페이지를 이동하는 주체는 자신이어야 한다. 따라서 사람은 직접적으로 책의 상태를 변경 할 수 없다. 단지 책에게 자신이 책을 읽었다는 메시지를 전달할 수 있을 뿐이다.

책의 페이지를 이동하는 것은 메시지를 전달받은 책 스스로의 몫이다.

자신의 상태는 자신만 변경 가능하다는 전제로 소프트웨어를 개발할 때 얻어지는 이점들은 상당히 많다. 상태의 변경이 어디서 발생했는지 바로 파악 가능하며, 각 객체들이 서로 메시지로만 협력하게 함으로써 소프트웨어 설계는 유연하고 간결해진다.

지금 단계에서 모든 걸 이해하려고 하는 것은 불가능일 수 있다. 수없이 많은 소프트웨어 장인과 연구자분들이 “이렇게 해야한다”고 주장하는 이유가 분명히 있다는 것을 일단은 믿고 수용하는 자세가 더 중요할 것 같다. 이후 얻어지는 보상은 기대 보다 상당할 것이라 믿자.

OOP와 다른 패러다임을 구분짓는 네가지 특징: 추상/캡슐/상속/다형성

OOP에는 추상/캡슐/상속/다형성의 네가지 특징이 존재한다. 이는 객체 지향 프로그래밍과 다른 패러다임들을 구분짓게 해주는 특징이면서도 “인간이 이해하기 쉬운 프로그램”을 개발하는데 도움을 주는 특성이기도 하다.

사실 위에서 언급한 “자신의 상태는 자신만 변경 가능 해야 한다”라는 전제는 네가지 특징 중 캡슐화에 대한 내용이었다. 네가지 특징들은 사실 하나의 공통 문제를 해결하기 위해 구현하다보니 표면 위에 드러난 특징들이다.

여기서는 이해가 쉬운 수준에서 얕게 언급하지만 이를 읽는 여러분은 각자만의 현실에 어떻게 적용할 수 있을 지를 심도있게 고민하면서 읽기를 권장한다. 자세한 구현 방법들에 대해서는 필자 스스로도 계속된 공부를 통해서 하나씩 포스팅할 것이다.

네가지 특징이 바라보는 결국 한 가지

“객체는 메시지(행위)를 기반으로 협력한다.”

위 예시 코드를 생각해보면 각 객체들은 메시지(행위 메서드)로 협력했다는 사실을 알 수 있다. main 호출 부에서 사람에게 우림.readBook() 메서드로 “책을 읽어” 라는 메시지를 요청했고, 사람은 책에게 book.turnToPage() 메서드로 “책 페이지를 이동시켜” 라는 메시지를 요청하면서 자기 스스로의 지식 점수는 10을 증가시켰다. 이후 책은 자기 스스로의 page를 5 증가시켰다.

이처럼 메시지라는 행위들을 기반으로 객체들이 협력해야 하며, 이를 잘 수행 가능하도록 돕는 네 가지 특징이 바로 추상화, 캡슐화, 상속, 다형성인 것이다.

추상화

네 가지 특성 중 가장 중요한 특성 한 가지를 뽑으라한다면 필자는 추상화를 말할 것이다. 추상화의 본질을 이해한다면 네 가지의 특성이 결국엔 같은 문제를 해결하고 있다는 것을 알게 될 것이다.

상태와 행위, 그리고 협력

OOP에서 추구하는 다섯가지 원칙: SOLID

이 원칙들은 소프트웨어 공학 분야의 수많은 개발자 장인과 연구자들이 수십 년간의 경험으로 힘들게 얻은 소산이다. 지금 당장은 모두 이해하기 어려울 수 있다. 그러나 우리는 다섯가지 원칙이 공통적으로 바라보는 “한 가지의 공통적인 구조가 있다”는 사실만 이해하면 된다. 이 얼마나 다행인가. SOLID를 통해 객체 지향 프로그래밍의 상당히 넓은 개념을 이해 해 보자.

이 또한 이해가 쉬운 수준에서 얕게만 언급하고 자세한 구현 방법은 별도 포스팅으로 공유할 예정이다.

SOLID가 바라보는 결국 한 가지

“빠른 변화에 빠르게 대응 가능한 소프트웨어 개발”

애자일 선언에 참여한 로버트 C. 마틴클린 소프트웨어 책에서 다음과 같이 말했다. “애자일한 개발자는 SOLID 원칙을 폭포수 모델과 같은 과도한 사전 설계에는 적용하지 않는다(p.122)” 라고. 이 말인 즉슨 SOLID는 애자일한 개발을 가능하게 돕는 원칙이라는 것이다. 이는 수많은 개발자 장인들과 연구자들의 경험과 고찰로 드러난 결과이다.

훌륭한 소프트웨어를 만들기 위해 지켜야하는 다섯가지 원칙으로 이해해도 좋다. SOLID가 왜 훌륭한 소프트웨어를 개발하게 돕는지 하나씩 살펴보도록 하자.

SRP: 단일 책임 원칙(Single Responsibility Principle)

SOLID의 S를 의미한다.

댓글남기기