객체지향 프로그래밍(OOP)은 현대 소프트웨어 개발의 중심이 되는 패러다임으로, 현실 세계의 개념을 소프트웨어 설계에 반영하여 효율적이고 유지보수하기 쉬운 코드를 작성할 수 있도록 돕습니다. 이 글에서는 캡슐화, 상속, 다형성, 추상화라는 OOP의 네 가지 핵심 개념을 중심으로 구체적인 사례와 함께 자세히 설명합니다.
캡슐화(Encapsulation) – 데이터 보호의 시작
캡슐화는 객체지향 프로그래밍에서 가장 기본이 되는 개념으로, 객체의 내부 상태와 동작을 외부로부터 숨기고, 정해진 방법을 통해서만 접근할 수 있도록 제한하는 방식입니다. 다시 말해, 데이터와 해당 데이터를 처리하는 메서드를 하나의 단위로 묶고 외부에서 직접 접근하지 못하게 하는 것이 캡슐화입니다. 프로그래밍에서는 일반적으로 private 접근 제어자를 사용해 멤버 변수에 직접 접근하지 못하도록 하고, getter, setter 메서드를 통해 간접적으로 값을 설정하거나 읽을 수 있도록 합니다. 예를 들어, 은행 계좌 클래스에서 balance 변수는 외부에서 직접 변경할 수 없고, deposit()이나 withdraw() 같은 메서드를 통해서만 조작할 수 있어야 합니다. 캡슐화는 코드의 안정성을 높이는 데 크게 기여합니다. 외부에서 객체의 상태를 무분별하게 변경할 수 없기 때문에, 예기치 않은 오류나 부작용을 방지할 수 있으며, 내부 구현을 변경해도 외부에는 영향을 미치지 않아 유지보수성이 뛰어납니다. 또한 캡슐화를 통해 데이터 유효성 검사를 중앙화할 수 있어, 예를 들어 나이 입력 값이 음수인 경우 오류를 반환하도록 설정할 수 있습니다. 이러한 방식은 코드의 재사용성과 확장성을 높이며, 객체 간 결합도를 낮추는 데도 효과적입니다. 객체지향적 사고를 시작할 때 가장 먼저 이해해야 할 개념이 바로 캡슐화입니다.
제가 파이썬으로 간단한 가계부 앱을 만들었을 때의 이야기입니다. 처음에는 balance(잔액) 변수를 public으로 선언해서 누구나 값을 바꿀 수 있게 했는데, 실수로 코드 어딘가에서 잔액이 마이너스가 되는 일이 있었어요.
그 뒤로 balance를 private으로 바꾸고, deposit()과 withdraw() 메서드만 통해 잔액을 변경하도록 수정했습니다. 이 메서드 안에 조건문을 넣어 잔액이 0 이하로 내려가지 않도록 유효성 검사도 추가했죠.
이 경험을 통해 캡슐화는 단순히 보안이 아니라, 프로그램의 안정성을 확보하는 기본 설계라는 걸 깨달았어요.
상속과 다형성 – 코드 재사용과 유연성의 열쇠
상속은 객체지향 프로그래밍의 또 다른 핵심 개념으로, 기존 클래스(부모 클래스)의 속성과 메서드를 새로운 클래스(자식 클래스)가 물려받는 구조를 말합니다. 상속을 통해 중복 코드를 줄이고, 공통된 기능을 추상화하여 유지보수를 용이하게 만들 수 있습니다. 예를 들어, Animal이라는 부모 클래스를 만들고, 이를 상속받는 Dog, Cat 등의 자식 클래스를 생성하면, 공통된 동작인 eat(), sleep() 등은 부모 클래스에서 정의하고, 각 동물 특유의 동작은 자식 클래스에서 따로 구현할 수 있습니다. 이렇게 하면 코드가 명확하고 일관되며, 중복 작성이 줄어들게 됩니다. 다형성은 하나의 인터페이스로 여러 구현을 처리할 수 있도록 하는 기능입니다. 예를 들어, 같은 move() 메서드를 호출하더라도 객체가 Car일 경우에는 바퀴로, Bird일 경우에는 날개로 이동하게 구현할 수 있습니다. 이는 프로그램의 유연성과 확장성을 극대화하는 핵심 기능입니다. 특히 Java나 C++ 같은 언어에서는 오버라이딩(Overriding)과 오버로딩(Overloading) 기능을 통해 다형성을 효과적으로 구현할 수 있습니다. 오버라이딩은 부모 클래스의 메서드를 자식 클래스에서 재정의하는 것이고, 오버로딩은 동일한 이름의 메서드를 인자의 타입이나 개수로 구분하여 여러 개 정의하는 것입니다. 상속과 다형성은 함께 작동하여, 객체 중심의 설계를 가능하게 하고, 변화에 유연하게 대응할 수 있도록 도와줍니다. 이는 규모가 큰 시스템이나 팀 프로젝트에서 특히 빛을 발하는 개념입니다.
추상화 – 복잡성을 줄이고 본질에 집중하기
추상화는 복잡한 시스템을 단순화하는 기법으로, 객체의 필수적인 특징만을 표현하고, 불필요한 세부사항은 감추는 것을 의미합니다. 이는 프로그래머가 복잡한 코드를 더 쉽게 이해하고 사용할 수 있도록 돕는 중요한 설계 원칙입니다. 예를 들어, Shape라는 추상 클래스를 정의하고, 그 안에 draw()라는 메서드를 선언해 둡니다. Circle, Rectangle, Triangle 등의 구체적인 클래스들은 각각 이 메서드를 구현하면서 자신만의 방식으로 그림을 그릴 수 있습니다. 여기서 핵심은 사용자나 다른 개발자가 Shape 타입만 알고 있으면 어떤 모양이든 동일한 방식으로 다룰 수 있다는 것입니다. 추상 클래스나 인터페이스를 사용하는 이유는 '계약'의 개념을 코드에 적용하기 위해서입니다. 즉, 특정 기능을 반드시 구현해야 한다는 약속을 명시함으로써, 클래스 간의 관계를 보다 명확하게 설정할 수 있습니다. 이는 팀 프로젝트나 외부 API 설계 시 강력한 구조를 제공해 줍니다. 또한 추상화는 유지보수 측면에서도 유리합니다. 구현체가 바뀌더라도 외부 인터페이스는 그대로 유지되므로, 코드 의존성을 줄이고 시스템을 보다 안정적으로 유지할 수 있습니다. 대규모 애플리케이션이나 프레임워크 설계 시, 추상화를 적절히 활용하면 전체 구조가 깔끔하고 확장성 높은 형태로 발전할 수 있습니다. 추상화는 결국 소프트웨어의 복잡성을 다루는 기술이며, 본질적인 문제 해결에 집중할 수 있도록 돕는 객체지향의 가장 강력한 무기 중 하나입니다.
객체지향 프로그래밍은 캡슐화, 상속, 다형성, 추상화라는 핵심 개념을 바탕으로 코드의 재사용성과 유지보수성을 극대화합니다. 이 네 가지 원칙을 깊이 이해하고 실무에 적용한다면 더욱 견고하고 확장 가능한 소프트웨어를 만들 수 있습니다. 지금 작성 중인 코드에 하나씩 적용해 보세요.
동생이 작은 병원을 운영하면서 온라인 예약 시스템을 외주 개발하게 됐는데, 개발사에 요청할 때 “예약 시스템은 어떤 병원이든 동일한 인터페이스만 유지하자”는 원칙을 세웠어요.
그래서 ReservationService라는 인터페이스를 만들고, 병원 유형(치과, 내과, 한의원 등)에 따라 각각 다른 예약 로직을 구현하게 했죠. 외부 시스템이나 웹사이트는 이 공통 인터페이스만 호출하면 어떤 병원이든 같은 방식으로 예약할 수 있게 된 거예요.
이 구조는 나중에 병원이 늘어나거나 예약 방식이 바뀌어도 전체 시스템을 수정할 필요 없이 일부 구현체만 고치면 되는 추상화의 장점을 잘 보여준 사례였습니다.