객체지향을 향해

November 6, 2023 (1y ago)

객체란 무엇일까?

객체는 상태(state)와 행동(behavior)을 지닌 실체다.

이 말이 의미하는 것이 무엇일까? 요리사를 예제로 이 의미에 대해서 생각해보자. 우리가 방문한 음식점의 요리사는 특정한 상태를 지닐것이다. 요리사는 아마도 어떤 대회에서 우승한 경력이 있는 요리사 일 것이다. 그렇다면 요리사는 요리 대회에서 우승한 요리사라는 상태를 갖고 있는 것이다. 또한 행동을 지닌 실체라고 했다. 요리사는 요리를 한다는 행동을 갖고 있다.

이러한 설명을 기반으로 객체에 대한 설명을 생각해보면 이제 꽤 그럴싸 해진거 같다.

객체의 속성

객체들은 서로 협력(collaboration) 관계를 가지며 각자의 역할(role)이 있고 각자 맡고 있는 역할에 따른 책임(responsibility)을 지녀야 한다.

요리사의 역할은 무엇일까? 손님이 요청한 음식을 제공하는 역할을 지닌다. 그렇다면 이러한 책임은 무엇일까? 손님이 지불하는 금액에 합당한 음식을 제공해야하는 역할을 지닌다.

위의 간단한 설명에서 알 수 있는 것이 있다. 요리사라는 객체가 요리를 만들기 위해서 필요한 것은 무엇일까? 바로 요청(request)다. 손님이 음식을 주문하지 않은 이상 요리사가 요리할 이유가 없다.

손님이 요리사에게 요리를 요청(request)하면 이 요청에 맞는 응답(response)으로 요리를 제공하는 것이다.

객체 지향의 기둥들

1. 추상화

흔히들 객체지향 언어는 현실의 모방이라는 이야기를 한다. 하지만 이는 틀린 말은 아니지만 그렇다고 해서 100% 맞는 말은 아니다.

예를 들어서 생각해보자 현실 세계에서의 비행기를 생각해보자. 비행기를 비행 중 오른쪽으로 방향을 전환하고 싶으면 어떻게 하는 것이 좋을까? 현실에서는 날개의 받음각을 조절하여 양력을 가장 적절한 샅애로 조절하고 주날개에 있는 플랩을 통해 날개 모양을 변형 시켜 양력을 조정하여...

너무 길다. 우리는 프로그래밍에서 이렇나 복잡한 현실의 부분을 추상화해줄 필요가 있다. 앞서 주구절절 설명했던 내용들을 그대로 클래스의 내부로 옮기기 보단 간단하게 바꿔주는 것이다.

class Airplane{
	#speed;
	construtor() {
		this.speed = speed;
	}

	get speed() {
		return this.#speed;
	}

	fly() {
		if(this.speed > appropriateSpeed) {
			this.doSomething();
		}
		...
	}
}

위의 비행기 객체를 날개하려면 어떻게 하면 될까? 간단하다. 비행기 객체의 인스턴스를 생성하고 fly() 메서드를 실행 시켜주면 된다.

이렇듯 우리는 현실 세계의 복잡한 부분을 추상화를 통해 클라이언트가 알지 않아도 되는 내용들을 과감하게 생략하고 필요한 기능들만 접근하여 사용할 수 있게끔 추상화 해줄 필요가 있다.

2. 캡슐화

캡슐화란 무엇일까? 우리가 약을 먹을 때 캡슐 안에 무엇이 들었는지 알고서 먹는 사람은 아마 매우 드물 것이다. 우리는 단순히 알약을 먹음으로써 질병이 사라지기를 꾀할 것이다.

우리는 복잡한 내부 사정보다는 객체(현실에서의 물건)을 통해서 얻고자 하는 목적만을 빠르게 얻기를 원한다.

캡슐화는 이러한 목적을 달성하기 위한 방법이다. 복잡한 내부 사정을 외부에서 알 수 없게끔 데이터를 감추고 중요한 부분만 들어내는 방법을 캡슐화라고 한다.

이러한 캡슐화를 위해 우리는 interface를 만든다.

interface란 무엇일까? interface는 객체 내부의 필드나, 특정 상태는 존재하지 않고 method 만 존재하는 객체를 interface 객체라고 표현한다.

아래의 코드 예시를 봐보자

class FlyingTransPort {
	fly(origin, destination, passengers) {...}
}

class Helicopter extends FlyingTransPort {
	constructor() {
		super();
	}
}

const helicopter = new Helicopter();
helicopter.fly(korea, ameria, 300);

위의 경우 FlyingTransPortinterface에 해당한다. 위의 예시의 경우 헬리콥터를 날게 하기 위해서는 FlyingTransPortfly() 메소드를 사용하기만 하면 된다. 이런 interface를 만든 덕분에 우리는 헬리콥터가 아니더라도 비행기든 제트기든 날 수 있는 것들을 쉽게 날게 만들 수 있다. 또한 헬리콥터가 어떤 상태인지 우리는 신경쓰지 않는다. 이렇듯 불필요한 부분은 감추고 외부에서는 목적에 부합한 요소만 접근할 수 있게끔 객체를 만드는 것을 캡슐화라고 한다.

3. 상속

상속은 기존 클래스들 위에 새 클래스들을 구축하는 기능입니다. 이 말이 의미하는 것은 클래스들 중 A is B라고 표현된다는 것입니다. 상속의 가장 큰 이점은 코드를 재사용할 수 있다는 점입니다. 기존 클래스와 약간 다른 클래스를 만들고 싶을 경우 기존 코드를 복재하지 않고 단순히 상속하는 것 만으로 기능과 이름 그대로 사용할 수 있다는 장점이 있습니다.

하지만 이러한 장점은 가장 조심해야할 부분이기도 합니다.