객체지향-객체(Object)

4 minute read

Step 1 : 객체의 핵심은 기능을 제공하는 것

객체 지향의 가장 기본은 객체(Object)이다. 객체는 데이터와 그 데이터를 조작하는 프로시저(오퍼레이션, 메소드, 함수)로 구성되는데, 이는 객체의 물리적인 특징일 뿐이다. 실제로 객체를 정의할 때 사용되는 것은 객체가 제공해야 할 기능이며, 객체가 내부적으로 어떤 데이터를 갖고 있는 지로는 정의되지 않는다.


예를들어, 전등 제어 객체가 있다고하자. 이 객체의 기능은 다음과 같다.

  • 전등 끄기
  • 전등 켜기
  • 전등 불빛 조절

이 객체가 내부적으로 전등 불을 어떤 데이터 타입 값으로 보관하는지는 중요치 않다.

또한, 실제로 객체가 어떻게 전등 불빛을 조절하는지는 알 수 없다.

단지, 전등 제어 객체는 위의 3가지 기능을 제공한다는 것이 중요할뿐이다.

Step 2: 시그니처(Signiture)

객체는 객체가 제공하는 기능으로 정의된다고 했는데, 보통 객체가 제공하는 기능을 오퍼레이션(Operation)이라고 부른다. 즉, 객체는 오퍼레이션이라 정의 될 수 있으며 객체가 제공하는 기능을 사용한다는 것은, 결국 객체의 오퍼레이션을 사용한다는 의미이다.

오퍼레이션의 사용법은 다음과 같이 세 개로 구성되며, 이 세가지를 합쳐서 **시그니처(Signature)**라고 부른다.


  • 기능 식별 이름 -> 메소드 명
  • 파라미터 및 파라미터 타입 -> 메소드 파라미터
  • 기능 실행 결과 값 -> 예상 결과 값

Step 3: 메시지

객체 지향은 기능을 제공하는 여러 객체들이 모여서 완성된 어플리케이션을 구성하게 된다.

예를들어, 다음과 같이 파일에 관련된 세가지 객체가 있다고 하자.


1.데이터를 읽어 오는 객체

2.데이터를 암호화 해주는 객체

3.파일에 데이터를 쓰는 객체


1번 파일 읽기 객체가 제공하는 인터페이스가 다음과 같은 오퍼레이션을 제공한다고 치자.

  • 오퍼레이션 이름 : read
  • 파라미터 : 없음
  • 리턴 타입 : byte 배열

이 경우 암호화 처리 객체는 파일 읽기 객체에게 read 오퍼레이션을 실행해 달라는 요청을 전달하며,

요청을 받은 파일 읽기 객체는 해당 요청에 해당하는 기능을 실행한 뒤에 응답을 전달한다.

이때, 오퍼레이션의 실행을 요청하는 것을 메시지를 보낸다고 표현한다.

자바와 같은 언어에서는 메서드를 호출하는 것이 메시지를 보내는 과정에 해당된다.

(참고로 캡슐화 관련 내용에서 보겠지만 메시지와 관련되서 알아야 할 Tell, Don’t Ask 규칙이 있다.)

Step 4: 객체의 책임과 크기

객체는 객체가 제공하는 기능으로 정의된다고 했는데, 이는 다시 말하면 객체마다 자신만의 책임(responsibility)이 있다는 의미를 갖는다. 앞서 위에서 정의한 객체들의 책임은 무엇일까?

1.데이터를 읽어 오는 객체 -> 파일에서 데이터를 읽어와 제공하는 책임

2.데이터를 암호화 해주는 객체 -> 제공받은 데이터를 암호화해서 다른 파일에 보내는 책임

3.파일에 데이터를 쓰는 객체 -> 파일에 데이터를 쓰는 책임


한 객체가 갖는 책임을 정의한 것이 바로 타입/인터페이스 라고 한다.

그럼, 객체가 갖는 책임은 어떻게 결정될까? 이 결정을 하는 것이 바로 객체 지향 설계의 출발점이다.

객체 지향적으로 프로그래밍을 할 때, 가장 어려우면서 가장 중요한 것이 바로 객체마다 기능을 할당하는 과정이다.


상황에 따라 객체가 가져야 할 기능의 종류와 개수가 달라지기 때문에, 모든 상황에 들어맞는 객체-책임 구성 규칙이 존재하는 것은 아니다. 하지만 객체가 얼마나 많은 기능을 제공할 것인가에 대한 확실한 규칙이 하나 존재하는데, 그 규칙은 바로 객체가 갖는 책임의 크기는 작을수록 좋다는 것이다.


객체가 갖는 책임이 작아야 한다는 것은 객체가 제공하는 기능의 개수가 적다는 걸 의미한다.

예를 들어 한 객체에 많은 기능이 포함되면, 그 기능과 관련된 데이터들도 한 객체에 모두 포함된다. 이러한 구조는 객체에 정의된 많은 오퍼레이션들이 데이터들을 공유하는 방식으로 프로그래밍되는 절차지향 방식과 동일한 구조가 된다.


즉, 객체가 갖는 책임이 커질수록(즉, 기능이 많을 수록) 절차 지향적으로 변질[기능 변경의 어려움을 가지게 됨]된다는 것이다.

따라서 객체가 갖는 책임의 크기는 작을수록 객체 지향의 장점인 변경의 유연함을 얻을 수 있게 된다.

(9객체의 크기와 관련된 원칙은 뒤에서 보게 될 단일 책임 원칙(Single Responsibility Principle: SRP)이다.)

Step 5: 의존

객체 지향적으로 프로그램을 구현하다 보면, 다른 객체가 제공하는 기능을 이용해서 자신의 기능을 완성하는 객체가 출현하게 된다.

한 객체가 다른 객체를 이용한다는 것은, 실제 구현에서는 한 객체의 코드에서 다른 객체를 생성하거나 다른 객체의 메서드를 호출한다는 것을 뜻한다.

이렇게 한 객체가 다른 객체를 생성하거나 다른 객체의 메서드를 호출 할 때, 이를 그 객체에 의존(dependency) 한다고 한다.

또한, 객체를 생성하거나 메서드를 호출하는 것뿐만 아니라 파라미터로 전달받는 경우에도 의존한다고 볼 수 있다.

public void process(ByteEncryptor encryptor) {
}

예를 들어, 위의 코드와 같이 ByteEncryptor를 파라미터로 받으면, 메서드 구현 과정에서 파라미터로 전달받은 ByteEncryptor 객체를 사용할 가능성이 높기 때문에, 이는 process() 메서드가 ByteEncryptor에 의존한다고 볼 수 있다.

객체를 생성하든 메서드를 호출하든 또는 파라미터로 전달받든 다른 타입에 의존한다는 것은 의존하는 타입에 변경이 발생할 때 나도 함께 변경될 가능성이 높다는 것을 뜻한다.


의존의 영향은 꼬리에 꼬리를 문 것 처럼 전파되는 특징을 갖는다.


예를들어 C클래스가 B클래스에 의존하고, B클래스가 A클래스에 의존한다고 할 때, A클래스의 변경은 B클래스에 영향을 줄 가능성이 높고, 이는 또 C클래스의 영향을 주게 된다.

의존의 이런 특징 때문에 의존이 순환해서 발생할 경우 다른 방법이 고민해야한다.

A클래스의 변화는 C클래스까지 영향을 줄 수 있는데, C클래스의 변화는 다시 A클래스에 영향을 줄 수 있다.

순환 의존이 발생하지 않도록 하는 원칙 중의 하나로 의존 역전 원칙 (Dependency inversion pricinple : DIP) 가 있다.

Step 6: 캡슐화

객체 지향의 장점은 한 곳의 구현 변경이 다른 곳에 변경을 가하지 않도록 해주는데 있다.

즉, 수정을 좀 더 원활하게 할 수 있도록 하는 것이 객체 지향적으로 프로그래밍을 하는 이유인 것이다.

객체 지향은 기본적으로 캡슐화를 통해서 한 곳의 변화가 다른곳에 미치는 영향을 최소화한다.

캡슐화란 객체가 내부적으로 기능을 어떻게 구현하는지를 감추는것이다.

-> 솔직히 말이 어려운데.. 그냥 메소드화 하는거라고 생각한다..;;

이를 통해 내부의 기능 구현이 변경되더라도 그 기능을 사용하는 코드는 영향을 받지 않도록 하여

내부 구현 변경의 유연함을 주는 기법이 바로 캡슐화다.


캡슐화를 위한 두개의 규칙이 있다.

  • Tell, Don’t Ask

    • 이 규칙은 데이터를 물어보지 않고, 기능을 실행해 달라고 말하라는 규칙이다.
    • (개인적인 의견은 method 화인듯 싶다..)
    • 기능 실행을 요청하는 방식으로 코드를 작성하다 보면, 자연스럽게 해당 기능을 어떻게 구현했는지 여부가 감춰진다. 즉, 기능 구현이 캡슐화되는 것이다.
  • 데미테르의 법칙 (Law of Demeter)

    • 데미테르의 법칙은 Tell Don’t Ask 규칙을 따를 수 있도록 만들어주는 또 다른 규칙이다.

    • 메서드에서 생성한 객체의 메서드만 호출

    • 파라미터로 받은 객체의 메서드만 호출

    • 필드로 참조하는 객체의 메서드만 호출

    • 신문 배달부와 지갑

      • 데미테르의 법칙을 설명할 때 사용되는 유명한 예로 신문 배달부 예제가 있다.
참조 - 개발자가 반드시 정복해야 할 객체지향과 디자인패턴 By 최범균

Categories:

Updated:

Comments