해당 포스팅은 <자바/스프링 개발자를 위한 실용주의 프로그래밍>의 2번째 챕터인 ‘객체의 종류’를 읽고 요약한 글이다.
공지사항
2025년 6월 9일 NEW 시스템 점검 안내 더 나은 서비스 제공을 위해 6월 9일 (월) 현재 시스템 점검 진행중입니다. 해당 시간 동안 서비스 이용이 일시적으로 제한될 수 있으니 양해 부탁드립니다. 시
m.yes24.com
객체지향 프로그래밍은 객체에게 역할과 책임을 부여하고, 이러한 객체들 간의 협력을 기반으로 동작하는 프로그래밍 기법을 말한다.
다시 말해 객체가 마치 자아를 가진 것처럼 동작해야 하는 것이다. 그렇다면 어떻게 자아를 가진 것처럼 객체가 행동하게 할 수 있을까?
🔗 TDA 원칙(Tell Don’t Ask)
TDA 원칙을 지키며 클래스를 설계하는 것이 그 답이 될 것이다. TDA는 ‘물어보지 말고 시켜라’는 뜻인데, 이를 통해 객체가 어떤 행동을 하게 만들 수 있다.
👨💻 자동차 클래스 만들어 줄 수 있나요?
A와 B에게 각각 이런 부탁을 했다면 어떻게 클래스를 설계할 것인지 생각해보자. 개발자 A는 프레임, 엔진, 바퀴, 속도, 방향 등 자동차를 구성하기 위한 것들을 떠올렸다. 그리고 다음과 같이 작성하였다.
public class Car {
private Frame frame;
private Engine engine;
private List<Wheel> wheels;
private float speed;
private float direction;
}
반면에 개발자 B는 주행하기, 액셀, 브레이크, 방향 전환 등 자동차로 할 수 있는 행동들을 떠올렸다. 그리고 다음과 같이 작성하였다.
public class Car {
public void drive() {}
public void changeDirection(float amount) {}
public void accelerate(float speed) {}
public void decelerate(float speed) {}
}
두 개발자의 결과물이 다른 것을 볼 수 있다. 개발자 A는 자동차를 구성하기 위해 필요한 특성들을 떠올리며 설계했고, 개발자 B는 자동차가 어떤 행동들을 하는지 떠올리며 설계했다.
개발자 A는 데이터 위주의 사고 방식으로사고방식으로 설계를 했고, 개발자 B는 행동 위주의 사고방식으로 설계를 한 것이다.
둘 중 어느 코드가 좋은 코드인 것인지 단정할 수는 없지만, 어느 것이 객체지향적인 코드인지에 대해서는 의심의 여지없이 행동 위주의 사고로 만들어진 개발자 B의 코드이다.
다시 말하지만 객체지향 프로그래밍은 역할과 책임을 가진 객체간의 협력을 기반으로 동작하는 프로그램을 만드는 것이다. 서로 협력을 하기 위해서는 객체가 다른 객체에게 행동을 요구할 수 있어야 한다. 이것이 행동 위주의 사고 방식이 객체지향적인 코드라고 말하는 이유이다.
이제 아래 코드에서 클래스 이름이 무엇이 될 것 같은지 생각해 보자.
public class ??? {
private float speed;
private float direction;
}
앞서 Car 클래스에 대해 이야기를 했다 보니 Car라고 쉽게 생각해 볼 수 있겠지만, 사실은 어떤 클래스라고 단정 짓기 어렵다. 자전거가 될 수도 있고, 오토바이가 될 수도 있고, 혹은 자동차, 비행기나 심지어 사람이나 동물도 말이 된다.
그럼 이제 아래 코드에서 클래스 이름이 무엇이 될 것 같은지 다시 생각해 보자.
public class ??? {
public void ride() {
내용 생략
}
public void run() {
내용 생략
}
public void stop() {
내용 생략
}
}
위 클래스는 탑승(ride)할 수 있고, 달릴(run) 수 있고, 멈출(stop) 수 있다. 필자는 ‘탈것’을 뜻하는 Vehicle이라고 명명하겠다. 앞서서 예시로 만들었던 speed와 direction 같은 데이터 위주의 사고방식으로 만들어진 클래스는 이름을 짓기 어려웠지만, 행동 위주의 사고 방식으로 만들어진 클래스는 명확히 네이밍을 할 수 있게 됐다.
객체는 책임과 역할을 가지고 있다. 속성에 대해서 고민했을 때에는 해당 객체의 책임이나 역할에 대해서 고민하기가 어려웠지만, 행동에 대해서 고민하니 자연스럽게 책임과 역할을 고민하게 됐다.
이것으로 TDA가 왜 객체지향적인 설계 방식에 필요한 원칙인지를 보여준다고 생각한다.
🔗 덕 타이핑(Duck Typing)
덕 타이핑은 덕 테스트에서 유래된 것인데 “만약 어떤 새가 오리처럼 걷고, 헤엄치고, 꽥꽥거리는 소리를 낸다면 나는 그 새를 오리라고 부를 것이다.”라는 개념을 가지고 있다.
개발자 관점에서 이를 해석한다면 ‘행동이 같다면 같은 클래스로 부르겠다’는 의미이다. 예시 코드로 이해해 보자.
class Duck {
walk = (): void => {
// do something...
}
swim = (): void => {
// do something...
}
quakquak = (): void => {
// do something...
}
}
class UnknownBird {
age: number;
walk =(); void => {
// do something...
}
swim = (); void => {
// do something...
}
quakquak = (); void => {
// do something...
}
}
Java의 관점으로 Duck과 UnknownBird 두 클래스를 보면 서로 다른 클래스이다. 상속 관계로 묶여 있지도 않기 때문에 아무런 연관이 없는 클래스인데, 타입 스크립트의 경우에는 위와 같은 경우에 상속 관계를 맺지 않은 객체를 할당할 수 있다고 한다.
이와 같이 말이다. const duck: Duck = new UnknownBird();
Java에서는 불가능한 선언이지만 타입스크립트에서 가능한 이유는 행동이 같은 두 클래스를 같은 클래스로 보겠다는 덕 타이핑의 개념을 지원하기 때문이다.
🔗 행동과 구현
객체지향 프로그래밍을 위해서는 TDA 원칙을 지키며 클래스를 설계해야 하며, 이는 행동 위주의 사고방식이라고 이야기했다. 다시 돌아가서 위에서 예시로 봤던 코드에 대해 이야기를 해보겠다.
public class Car {
public void drive() {}
public void changeDirection(float amount) {}
public void accelerate(float speed) {}
public void decelerate(float speed) {}
}
위 코드는 메서드의 구현이 없기 때문에 미완성된 코드인데 만약 자동차의 방향을 바꾸는 changeDirection() 메서드를 구현한다면 어떻게 될까?
public class Car {
private int degree; // 자동차의 각도(0도 ~ 360도)
public void drive() {}
public void changeDirection(float amount) {
float result = (degree + amount) % 360;
if (result < 0) result += 360;
return result;
}
public void accelerate(float speed) {}
public void decelerate(float speed) {}
}
메서드를 구현했더니 degree라는 속성이 생긴 것을 확인할 수 있다. 행동 위주의 사고방식을 하려 했지만 구현을 하려는 순간 데이터 위주의 사고 방식을 하게 된 것이다.
이러한 현상이 일어난 이유는 구현을 고민했기 때문이다. 행동을 고민하는 순간에는 순수하게 이 클래스에 어떤 동작을 시킬 수 있을 것인지만 고민하는 것이 좋다고 한다.
👨💻 그럼 어떻게 해야 철저하게 행동 위주의 사고 방식을 하며 클래스를 설계할 수 있을까?
순수하게 어떤 동작을 시킬 수 있을 지만 고민한다면 메서드만 정의하고 메서드의 본문은 정의를 안 하면 되는 걸까? 하지만 이것도 메서드의 반환값이 void인 경우에만 가능한 것일뿐더러 메서드를 구현하지 않았다가 아예 놓치는 경우도 생길 수 있어서 좋은 방법은 아닐 것이다.
철저히 행동 위주의 사고방식을 하게 해주는 것은 인터페이스이다. 인터페이스는 구현 없이도 메서드를 정의할 수 있기 때문이다.
public interface Car {
void drive();
void changeDirection(float amount);
void acclerate(float speed);
void decelerate(float speed);
}
인터페이스를 활용해 클래스를 설계하는 단계에서 철저히 행동 위주의 사고 방식을 할 수 있게 됐다.
👨💻 구현은 정말 신경 쓰지 않아도 될까?
어차피 누군가는 구현을 해야 할 테지만 초기 단계에서는 문제가 없다. 이유를 설명하기 위해 객체지향 프로그래밍의 정의로 떠올려보면 객체지향 프로그래밍은 객체에게 역할과 책임을 부여해 객체 간의 협력을 기반으로 동작하는 것을 말한다.
역할과 책임을 가진 객체들이 모여 서로 협력을 하는 것인데 행동 위주의 사고방식으로 클래스를 설계함으로써 어떠한 행동들이 필요한지를 정의해 두었고, 이를 기반으로 각자 맡은 것을 구현한다면 올바른 객체지향 프로그래밍을 할 수 있다는 것이다.
👨💻 인터페이스는 곧 계약이다
인터페이스는 객체끼리 서로 일을 시키기 위해 행동을 정의하는 데 사용하는 것이다. 그렇기 때문에 인터페이스는 반드시 지켜져야 하는 것이다. 따라서 계약을 제대로 지키고 있는지, 유지하고 있는지 확인할 필요가 있다.
🔗 인터페이스
인터페이스에 행동을 정의하는 것을 보았기에 인터페이스와 행동은 같은 것이라고 생각을 할 수 있을 것 같다. 그렇지만 인터페이스와 행동은 다르다. 행동 위주의 사고방식에 집중하다 보니 인터페이스로 이어졌지만 행동이 인터페이스인 것은 아니라고 한다. 책에서는 인터페이스를 ‘나를 조작하고 싶다면 이런 메시지를 보내면 된다’라고 외부에 알려주는 수단이라고 말한다. 정의가 좀 와닿지 않을 수 있지만 몇 가지 예시를 보면 이해하기 쉬울 것이다.
- API(Application Programming Interface)는 애플리케이션을 조작하고 싶을 때 어떻게 메시지를 보내야 되는지를 알려주는 것
- UI(User Interface)는 사용자가 프로그램을 조작하고 싶을 때 어떻게 메시지를 보내야 되는지를 알려주는 것
마찬가지로 자바의 인터페이스는 어떤 객체를 어떻게 사용하면 되는지를 다른 외부 객체에게 알려주는 것과 같다.
👨💻 객체지향 프로그래밍에서 인터페이스란
객체에게 책임과 역할을 부여하고 이들 간 협력을 기반으로 동작하도록 만드는 것이 객체지향 프로그래밍이라고 했다. 하지만 객체지향적 설계를 잘하기 위해 행동 위주의 사고방식을 하면서 인터페이스를 생성해 행동(역할)들을 정의했다. 즉 엄밀히 말하면 객체에게 책임과 역할을 부여하는 것이 아니라 인터페이스에게 책임과 역할을 부여한다고 할 수 있다.
인터페이스가 존재함으로써 행동과 역할을 고민할 수 있게 도와준다. 그렇기에 객체지향 프로그래밍에서 인터페이스는 매우 중요한 존재이다.
🔗 행동과 역할
객체지향 프로그래밍을 위해 행동 위주의 사고 방식을 하면서 프로그래밍을 했고 이에 인터페이스가 중요한 역할을 한다는 것을 깨달았다. 행동이 아니라 데이터 위주의 사고 방식을 하며 구현하려고만 했을 때 발생할 수 있는 문제점에 대해서 이야기를 해보겠다.
class User {
public ride(String type, Object object) {
switch (type) {
case "CAR":
((Car) object).ride();
break;
case "BICYCLE":
((Bicycle) object).ride();
break;
}
}
}
만약 여기에서 또 다른 이동 수단이 생긴다면 switch의 분기문은 계속해서 추가될 것이기에 확장성이 좋다고 할 수 없다.
반대로 행동에 집중하여 설계한다면 요구사항에 유연하게 대처할 수 있는 유지보수성과 확장성 높은 설계가 될 수 있을 것이다.
👨💻 마무리
객체지향적인 프로그래밍을 위해 TDA 원칙을 준수하며 클래스를 설계하는 것에 대해 익혔고, 이 과정에서 행동 위주의 사고방식과 인터페이스의 중요성에 대해 체감했다.
다음 포스팅에서는 SOLID 원칙에 대해 이야기해 보겠다.
'IT 서적 > 자바&스프링 개발자를 위한 실용주의 프로그래밍' 카테고리의 다른 글
| CH 02 - 객체의 종류 | DTO, DAO, 엔티티 (1) | 2025.05.28 |
|---|---|
| CH 02 - 객체의 종류 | 값 객체 (2) - 동등성과 자가 검증 (3) | 2025.05.27 |
| CH 02 - 객체의 종류 | 값 객체(1) - 불변성 (3) | 2025.05.26 |
| 객체 지향적인 클래스 설계 방식 (0) | 2025.03.10 |
| CH01 - 절차 지향 프로그래밍과 객체 지향 프로그래밍 (0) | 2025.02.28 |