이상한 나라의 객체

su dong·2023년 5월 29일
0
post-thumbnail

질문거리

  • 캡슐화와 자율성은 어떤 관계에 있는 걸까?
  • 행동이 상태를 결정한다는 말은 무슨 뜻일까?

<이번 장 핵심>

  • 객체는 현실세계의 은유다. 의인화다.
  • 캡슐화(Tell, Don't Ask)
  • RDD(Responsibility-Driven Design):행동이 상태를 결정한다/ 협력 -> 행동 -> 객체 -> 상태

우리가 객체지향 프로그래밍을 선택하는 근본적 이유

인간은 선천적으로 (개념적, 물리적)객체를 인식하여, 세상의 복잡성을 극복하기 때문임.


🤖 객체란?

상태[state], 행동[behavior], 식별자[identity]를 지닌 실체를 의미한다


🧘 상태란?

과거에 발생한 행동의 이력들의 합 = 상태

프로퍼티(상태의 구성요소)

1. attribute(속성)

각각의 객체의 특성을 표현하기 위한 개념 - 객체라고 하기에는 너무 단순한 개념.
ex) 토끼(객체)의 속도(attribute)
ex) 음료(객체)의 양(attribute)

2. 다른 객체

객체와 객체 사이의 연결 여부(Link)로 표현

객체끼리 직접적으로 다른 객체의 상태를 변경할 수 없음.
-> 객체는 스스로의 행동에 의해서만 상태가 변경됨.
-> 간접적으로 객체의 상태를 변경하거나 조회할 수 있는 방법이 필요.


👷‍♀️ 행동

행동이란 외부의 요청 또는 수신된 메시지에 응답하기 위해 동작하고 반응하는 활동.
행동의 결과로 객체는 자신의 상태를 변경하거나 다른 객체에게 메시지를 전달할 수 있음.
객체는 행동을 통해 다른 객체와의 협력에 참여하므로, 행동은 외부에 가시적이어야 함.


상태 캡슐화

모든 객체의 상태는 객체 자신만 수정가능.(이 부분이 현실세계와 다르다.)

가령 음료를 마시는 엘리스의 상황을 상상해보자. 현실에서는 음료의 양이라는 (음료 객체의)상태를 변경하는 것은 엘리스다.
하지만 객체지향 프로그램에서는 앨리스는 얼마만큼 마셨다는 메시지를 보낼 뿐, 음료의 양을 조절하는 것은 음료가 된다.
(물론 프로그래밍에서도 앨리스 객체가 음료객체의 상태를 바꾸도록 설계할 수는 있지만, 이는 객체지향적인 프로그래밍에 위배된 설계이다.)

다시 말하지만 객체의 상태는 객체 자신의 행동(외부로 부터 온 수신 메세지에 응답하기 위한 능동적 반응)을 통해서만 변경 가능하다.

그런데 이러한 행동은 다음과 같은 아주 신기한 특성을 가진다.

가령 예를 들어 홍길동씨가 여자친구인 나이뻐씨를 만나러간다고 하자. 홍길동씨가 나 이뻐씨에게 "간다"라는 행동을 하고 행동을 했다는 사실을 나이뻐씨에게 알려줬을때, 제 3자가 홍길동씨의 "간다"는 행동만을 보고 나이뻐씨가 어떤 행동을 할지, 그리고 그로인해 나이뻐씨의 속성값들이 어떻게 변할지 알 수 있을까? 아니 없다.
즉 홍길동씨의 "간다"는 행동에 대한 소식을 듣고 어떤 행동을 할지는 나이뻐씨만 알 수 있으며, 이를 "캡슐화"되었다고 한다.

메세지 송신자는 메세지 수신자에 상태 변경에 대해서 전혀 알지 못한다.

객체가 외부에 노출하는 것은 행동 뿐이며, 외부에서 객체에 접근할 수 있는 유일한 방법 역시 행동뿐이다.

상태를 잘 정의된 행동 집합 뒤로 캡슐화하는 것은 객체의 자율성을 높이고 협력을 단순하고 유연하게 만든다.
이것이 상태를 캡슐화해야하는 이유다.

단순한 이유: 메시지만 서로 주고받으니깐.
유연한 이유: 해당 역할을 수행할 수 있는 객체라면 언제든지 대체 가능하니깐.


식별자

프로퍼티 타입으로 단순한 값과 객체가 있는데, 그 둘의 차이점은 식별자의 유무에 있었다!!

식별자란 어떤 객체를 다른 객체와 구분하는데 사용하는 객체의 프로퍼티다. 값은 식별자를 가지지 않기 때문에 상태를 이용한 동등성 검사를 통해 두 인스턴스를 비교해야 한다. 객체는 상태가 변경될 수 있기 때문에 식별자를 이용한 동일성 검사를 통해 두 인스턴스를 비교할 수 있다.

객체: 참조객체, 엔티티
값: 값 객체

call by reference/ 주솟값.(엄밀히 말하면 call by sharing, call by value)

객체가 외부에 제공하는 행동

쿼리(query): 객체의 상태를 조회
명령(command): 객체의 상태를 변경하는 작업


행동이 상태를 결정한다

public class Car {
    public String model;
    public int speed;

    public Car(String model) {
        this.model = model;
        this.speed = 0;
    }

    public void increaseSpeed(int increment) {
        this.speed += increment;
    }
    
    public void decreaseSpeed(int decrement) {
        if (this.speed - decrement < 0) {
            this.speed = 0;
        } else {
            this.speed -= decrement;
        }
    }
}

질문

위 코드는 객체지향적설계에 충실한가?

답변

이 코드는 객체 지향 설계 원칙 중 일부에는 부합하지만, 완벽하게 충실하지는 않습니다. 그 이유는 다음과 같습니다:

캡슐화 위반: 클래스의 속성(model과 speed)이 public으로 선언되어 있습니다. 이로 인해 외부에서 직접 이 속성들을 변경할 수 있습니다. 이는 객체의 상태가 외부에 노출되고 외부 코드에 의해 변경될 수 있음을 의미합니다. 이는 캡슐화 원칙에 위반됩니다. 객체의 상태는 private으로 선언되어 외부로부터 보호되어야 하며, 해당 객체가 제공하는 메서드를 통해서만 변경되어야 합니다.

객체 간의 협력 부족: 이 Car 클래스는 상태를 변경하는 메서드(increaseSpeed와 decreaseSpeed)만 제공합니다. 하지만 객체는 단지 상태를 변경하는 것뿐만 아니라 다른 객체와 메시지를 주고 받으며 협력해야 합니다. 이 클래스는 다른 객체와 어떻게 협력할 것인지, 어떤 메시지를 주고 받을 것인지에 대한 설명이 없습니다.

상태보다 행동에 집중해야 함: 이 Car 클래스는 주로 상태에 초점을 맞추고 있습니다. 즉, 차량의 speed 상태를 증가시키거나 감소시키는 것이 주된 관심사입니다. 하지만 객체 지향 설계에서는 객체의 상태보다는 행동에 집중해야 합니다. 예를 들어, 차량이 언제 속도를 증가시키고, 언제 속도를 감소시켜야 하는지에 대한 행동적 측면이 설계에 반영되어야 합니다.

따라서 이 코드는 객체 지향 설계 원칙에 완벽하게 부합하지는 않습니다. 위의 문제점들을 해결하기 위해 코드를 개선할 필요가 있습니다.

수정코드

public interface Drivable {
    void startEngine();
    void stopEngine();
    void accelerate();
    void brake();
    void drive();
}

public class Car implements Drivable {
    private String model;
    private int speed;
    private boolean isEngineRunning;

    public Car(String model) {
        this.model = model;
        this.speed = 0;
        this.isEngineRunning = false;
    }

    @Override
    public void startEngine() {
        this.isEngineRunning = true;
    }

    @Override
    public void stopEngine() {
        this.isEngineRunning = false;
        this.speed = 0;
    }

    @Override
    public void accelerate() {
        if (this.isEngineRunning) {
            this.speed += 10;
        } else {
            System.out.println("Cannot accelerate, the engine is not running!");
        }
    }

    @Override
    public void brake() {
        this.speed -= 10;
        if (this.speed < 0) {
            this.speed = 0;
        }
    }

    @Override
    public void drive() {
        if (this.speed > 0) {
            System.out.println(this.model + " is driving at " + this.speed + " mph.");
        } else {
            System.out.println(this.model + " is not moving.");
        }
    }
}


이 코드에서는

model과 speed 필드는 private으로 선언되어 외부로부터 보호됩니다. 이는 캡슐화 원칙을 준수합니다.
getModel()과 getSpeed() 메서드를 통해 상태 정보를 안전하게 가져올 수 있습니다.
accelerate()와 brake() 메서드는 특정 조건에 따라 자동차의 행동을 결정합니다.
Car 클래스는 이 인터페이스를 구현하며, 각 메서드 내부에서 어떻게 행동이 수행되는지의 로직이 캡슐화되어 있습니다. 클래스의 사용자는 Drivable 인터페이스를 통해 Car 객체와 상호작용할 수 있으며, 내부 상태는 완전히 숨겨져 있습니다.

이러한 설계는 상태보다 행동에 더 초점을 맞추고 있으며, 다양한 차량 타입을 추가하거나 기존 코드를 변경할 때 더 유연하게 대응할 수 있습니다.


객체지향 설계의 순서

RDD(Responsibility-Driven Design):행동이 상태를 결정한다
app에 필요한 협력을 생각
-> 협력에 참여하는데 필요한 행동을 생각
-> 행동을 수행할 객체를 선택

객체는 현실세계의 은유다. 의인화다.

profile
매일매일 성장하는 백엔드 엔지니어 박지수입니다.

0개의 댓글