본문 바로가기

Project/기록

객체지향의 사실과 오해 - 1

역할, 책임, 그리고 자율성 – 객체지향을 말하다.

프로그래밍을 처음 배울 때는 어떻게 구현할까에 초점을 두고 코드를 구현했습니다. 예를 들어서 알고리즘 테스트 문제를 풀 때는 이것에만 집중했고, 실무에서도 어떻게 구현할까에 초점을 많이 맞췄습니다.

이전 글에 팀을 리드하면서  코드의 유지보수를 높이기 위해서  점점 더 어떻게 협력할까, 어떻게 분리할까, 무엇이 역할이고, 무엇이 책임인가라는 질문을 던지게 되었고, 프로젝트를 진행하면서 책을 한권 읽게 되었습니다. 

객체지향의 사실입니다. 최근 다시 책을 읽어보면서 와 닿는 내용들을 작성해보려고 합니다.

"여러 사람이 동일한 역할을 수행할 수 있다." 에 관한 내용입니다.

 

커피는 누가 만들어도 커피이다?

카페에가서 "김 바리스타가 직접 만든 커피 주세요."라는 말은 하지 않고 그저 "아메리카노 한 잔 주세요."라고 말을 합니다.

고객 입장에서는 중요한 건 누가 만드냐보단, 커피가 잘 나오는가입니다.

이러한 상황을 객체지향적으로 해석하면 다음과 같다고 생각됩니다.

바리스타는 역할(Interface)이고, 커피를 추출하는 책임(Method)을 갖고 있습니다. 그렇지만, 그 책임을 어떻게 수행할지는 객체 스스로가 결정(Polymorphism)합니다.

 

해당 내용을 코드 풀이하자면?

// 역할 정의
interface Barista {
  brew(coffeeBean: string): string;
}

// 책임을 다르게 수행하는 여러 객체들
class ExpertBarista implements Barista {
  brew(coffeeBean: string) {
    return `${coffeeBean} 전문가의 기술로 추출한 커피`;
  }
}

class RobotBarista implements Barista {
  brew(coffeeBean: string) {
    return `${coffeeBean} 최적화 알고리즘으로 추출한 커피`;
  }
}

여기서 ExpertBarista와 RobotBarista는 모두 Barista라는 동일한 역할을 수행합니다. 하지만 각자의 방식으로 수행하고 있는 것입니다. 이는 다형성( polymorphism)입니다.

또 다른 예시를 들어 보겠습니다.

배송만 되면 되는 거 아닐까?

어떤 시스템에서든 중요한 건 요청이 처리되는 것입니다. 그걸 누가 어떻게 처리하는가는 필요하지 않습니다. 특히 클라이언트 입장에서는 굳이 관심 가질 이유가 없습니다. 정해진 시간이 알맞게 배송이 잘 되면 되기 때문입니다.

// 역할 계약
interface DeliveryService {
  send(packageId: string): Promise<void>;
}

// 다양한 책임 수행 방식
class DroneDelivery implements DeliveryService {
  async send(packageId: string) {
    console.log(`드론으로 [${packageId}] 긴급 배송 시작`);
    // GPS 최적화 알고리즘 실행
  }
}

class RobotDelivery implements DeliveryService {
  async send(packageId: string) {
    console.log(`지하 터널 통해 [${packageId}] 전송`);
    // 자율 주행 경로 계획
  }
}

DeliveryService는 역할(Interface)입니다. DroneDelivery, RobotDlivery는 그 역할을 다른 방식으로 수행하는 객체들입니다. 이 객체들은 각자의 방식으로 책임을 수행하고, 이게 다형성입니다.

@Injectable()
class OrderController {
  constructor(
    @Inject('DeliveryService') 
    private delivery: DeliveryService
  ) {}

  async handleOrder() {
    // 어떤 방식의 배송인지는 신경 쓰지 않는다
    await this.delivery.send('BOX');
  }
}

OrderController는 오직 DeliveryService 역할에만 의존하고 있습니다. 구체적인 구현체가 드론이든, 로봇이든 전혀 신경 쓰지 않습니다.(해당 내용은 DIP와도 연관이 깊습니다.)  의사소통은 인터페이스 기반으로 이루어지고, 협력만 수행하면 됩니다.

정리하자면, 객체지향은 단순한 코드 구조를 말하는 것이 아닙니다. 책에서도 클래스에 매몰되는 것은 좋지 않다는 뉘앙스의 글이 존재합니다. 클래스란 객체지향하기 위해 사용되는 하나의 도구입니다. 객체지향에서 중요한 점은 책임과 역할 그리고 협력입니다.

인터페이스는 역할이고, 메서드는 그 역할이 요구하는 책임입니다. 다형성으로 그 책임을 자율적으로 수행하도록 하며, 클라이언트는 역할에만 관심을 가지고, 세부 구현은 숨깁니다.

결과적으로, 객체지향은 어떻게 수행하는가 보단 무엇을 요청하는가에 집중하는 설계라고 생각합니다.

OCP(Open Closed Principle)와 객체 지향의 연결

OOP의 중요한 원칙 중 하나인 OCP도 여기에 자연스럽게 녹아있습니다. 

새로운 배송 수단이 추가되어도 기존 OrderController는 바뀔 필요가 없습니다. 다만, 새로운 구현체만 인터페이스를 구현하면 되므로 변화에 닫혀 있고, 확장에 열려있습니다. 위의 예시로 Nest에 관한 예시를 들면 아래와 같습니다.

@Module({
  providers: [
    DroneDelivery,
    // RobotDelivery,
    {
      provide: 'DeliveryService',
      useClass: DroneDelivery, // 여기만 바꾸면 동작이 변경됨
    },
    OrderService,
  ],
  exports: [OrderService],
})
export class DeliveryModule {}

 

결과적으로 객체 지향은 자유롭고 강력한 시스템을 만들 것입니다.

단순하게 클래스를 나누는 것이 아니고 책임을 위임합니다. 그리고 객체 간 협력을 통해 시스템이 유연하게 돌아가게 됩니다.

이러한 구조는 느슨한 결합으로 테스트에 유용합니다. 유연한 확장성으로 대처가 가능합니다. 그리고 변화에 강합니다.

코드를 작성하면서 어렴풋이 정립되어 있는 내용들을 이제야 조금 더 와닿았던 거 같습니다. AI의 등장으로 코드를 작성할 일이 없다고는 하지만 이러한 기본적인 철학은 알고 있어야 좋은 코드를 꿰뚫어 보고 잘못된 방향의 객체지향 코드를 고칠 수 있는 그런 눈을 가진 개발자가 되어가야 함을 유념해야겠습니다.

'Project > 기록' 카테고리의 다른 글

내 API는 실패할 수 있다  (0) 2025.05.26
객체지향의 사실과 오해 - 2  (0) 2025.05.10
회사 코드 문화를 바꾸었다 - 완  (1) 2025.05.07
회사 코드를 바꿔보자 - 3  (0) 2025.05.07
회사 코드를 바꿔보자 - 2  (0) 2025.05.07