개주 훈련일지/📚 코살대 교본 학습

객체지향 설계 원리

lshfood2 2026. 2. 6. 11:37

[ 객체지향 설계 원리 ] 

앞서 설명한 전통적인 패러다임의 설계 원리는

객체지향 프로그램의 새로운 특성과 함께 발전되었다.

 

객체지향 개념, 예를 들면 상속과 인터페이스 등

새로운 구문이 추가되면서 재사용,

수정 용이성 등의 품질을 높일 수 있게 되었고,

이를 위해 지켜야 할 설계 원리가 있다.

 

Martin이 제안한 객체지향의

일반적인 설계 원칙 5가지,

즉 SOLID 원칙은 다음과 같다.


각 원칙의 영문 명칭 앞글자를 따서 만든 이름이다.

  1. 단일 책임의 원리
    (Single Responsibility Principle)
  2. 개방 폐쇄의 원리
    (Open Close Principle)
  3. 리스코프 교체의 원리
    (Liskov Substitution Principle)
  4. 인터페이스 분리의 원리
    (Interface Segregation Principle)
  5. 의존 관계 역전의 원리
    (Dependency Inversion Principle)

SOLID 설계 원리는 객체지향 소프트웨어 설계를

이해하기 쉽고, 유지 관리하기 쉽고, 확장하기 쉽게 만든다.


인터페이스와 구현의 분리

객체지향 언어에서 인터페이스는

공개된 메서드의 프로토타입(추상)만을

정의해 놓은 것이다.

 

대부분의 클래스는 메서드의 구현을 포함하는데,

공개된 메서드를 인터페이스로 따로 정의하고

이를 구현하도록 관계를 맺는다.

 

상속과 다형성을 이용하면

하나의 인터페이스로 여러 구현을 공유할 수 있다.

 

즉 하나의 추상 개념이 필요에 따라

여러 버전으로 구체화될 수 있다.

 

인터페이스, 구현 분리의 원칙은

컴포넌트의 공개 인터페이스(사용자가 알아야 할 부분)와

컴포넌트가 실제로 구현되는 상세를 분리하는 것이다.

인터페이스만 바뀌지 않는다면 구현은 쉽게 변경할 수 있다.

 

예를 들어 정렬 기능을 생각하면,

정렬 기준이 달라져도 인터페이스가

동일하면 구현만 교체할 수 있다.

Client
-> Sorter(인터페이스)
-> SorterByName(구현) //분리
-> SorterById(구현) //분리

 

캡슐화와의 관계도 정리할 수 있다.
캡슐화는 구현을 포함한 필요한 세부 사항을

숨기면서 모듈이 제공하는 서비스에 공개적으로

접근할 수 있도록 인터페이스를 만들게 한다.


인터페이스와 구현 분리는

인터페이스와 구현을 분리해 추상화하도록 지시하며,

동일 인터페이스의 구현을 교환하거나 새로운 동작을

제공함으로써 유연한 설계를 가능하게 한다.


1. 단일 책임의 원리

클래스는 객체지향 설계에서 중요한 단위 모듈이다.

단일 책임의 원리는 클래스의 역할과 책임을

단순화하여 클래스를 변경해야 할 이유가

오직 하나뿐이 되도록 해야 한다는 원리다.

 

예를 들면 Book 클래스는 책명, 저자, 본문 등

책 정보를 저장하고 유지하는 책임이 있다.

 

그런데 본문 내용을 화면에 디스플레이하는 기능이

Book 안에 같이 있으면 책임이 다양해진다.

 

화면 해상도나 표시 방식이 달라질 때 책 정보와

무관한 부분까지 함께 수정하게 되기 때문이다.

 

따라서 BookPrinter 같은 클래스를 만들어

출력 책임을 분리하는 것이 좋다.

 

이렇게 하면 Book은 책 정보가 바뀔 때만 변경되고,

BookPrinter는 디스플레이 방법이 달라질 때만 변경된다.

Book(책 정보 유지)
+
BookPrinter(출력 담당)

2. 개방 폐쇄의 원리

개방 폐쇄 원리는 소프트웨어 개체가(클래스, 모듈, 기능 등)

확장을 위해서는 열려 있어야 하지만

수정을 위해서는 닫혀 있어야 한다는 것이다.

 

이 원리를 이해하려면

다형성에 대한 이해가 필요하다.

 

상속과 다형성을 이용하면,

기존 클래스를 수정하지 않고도

새로운 클래스를 추가하여 기능을 확장할 수 있다.

 

예를 들면 Client 프로그램이

SortAlgorithm을 이용한다고 할 때,

SortAlgorithm을 상속받은 여러 정렬 알고리즘은

다형성을 이용해 확장될 수 있도록 열려 있다.

 

정렬 알고리즘의 수정이나 교체가 필요해도

Client는 영향을 덜 받는다.

 

Client는 SortAlgorithm의 sort()를 호출할 뿐이며,

구체 알고리즘에 직접 의존하지 않기 때문이다.

Client(수정 영향 최소)
-> SortAlgorithm(공통)
-> BubbleSort(확장)
-> HeapSort(확장)
-> ShellSort(확장)

3. 리스코프 교체의 원리

클래스 B가 클래스 A에서 상속받은

하위 유형이라면 프로그램의 동작을

방해하지 않고 A를 B로 대체할 수 있어야 한다.

 

이는 상속에 의한 다형성이 적용되기 때문에,

하위 클래스가 상위 클래스로

대체 가능해야 한다는 의미다.

 

하위 클래스는 클라이언트의 관점에서

기능을 손상시키지 않는 방식으로

상위 클래스 메서드를 대체해야 한다.

 

예를 들어 makeNoise() 메서드를 가진

Animal 클래스가 있으면 Animal의 모든

하위 클래스는 makeNoise()를 타당하게 구현해야 한다.

 

만약 MuteMouse 클래스가 makeNoise()에서

예외를 던지도록 정의하면 makeNoise()를 적용할 수 없어

MuteMouse가 Animal을 대체할 수 없게 된다.

이는 리스코프 교체 원리에 위배된다.

 

이 원칙을 지키지 않고 설계한

상속 계층은 문제를 일으킨다.

 

서브클래스 인스턴스를 전달했을 때

메서드가 이상하게 작동할 수 있고,

상위 클래스에 대해 작성된 단위 테스트가

서브클래스에서는 동작하지 않을 수도 있다.

Animal(기대: makeNoise 가능)
<- MuteMouse(makeNoise에서 예외)
대체 불가능

4. 인터페이스 분리의 원리

하위 모듈을 추상화한 인터페이스를

설계할 때 주의해야 한다.

 

인터페이스를 상속받았지만

사용하지 않는 메서드가 있다면,

내용 없는 더미 메서드를 구현하게 된다.

 

이러한 인터페이스를 비만 인터페이스

또는 오염된 인터페이스라고 설명한다.

 

인터페이스 분리의 원리는 클라이언트가

사용하지 않는 인터페이스를 강제로

구현해서는 안 된다는 것이다.

 

하나의 큰 인터페이스 대신

다수의 작은 인터페이스를 만들어

필요한 것만 사용하도록 유도해야 한다.

 

예를 들어 Shape 인터페이스에

area()와 volume()이 함께 들어 있다고 하자.

 

Cylinder, Square, Circle 모두를 위한

단일 인터페이스로 만들면,

2D 도형인 Square나 Rectangle은

volume()이 필요하지 않다.

그런데도 volume()을 구현해야 하므로 문제가 된다.

 

따라서 3D 도형을 위한 별도의

인터페이스를 분리해 설계해야 한다.

 

이렇게 바꾸면 불필요한 메서드가 있는

인터페이스에 의존하지 않게 되고,

필요한 것에만 의존하는 구조가 된다.

즉 인터페이스 분리를 통해 결합이 느슨해진다.

//분리 전
Shape(area, volume)
-> Square(2D인데 volume 불필요)
-> Circle(2D인데 volume 불필요)
-> Cylinder(3D)

//분리 후
Shape(area)
-> Square
-> Circle

3DShape(volume)
-> Cylinder

5. 의존 관계 역전의 원리

일반적으로 높은 수준의 모듈은 재사용되고,

변화가 많은 낮은 수준의 모듈에 의해

쉽게 영향받지 않도록 설계해야 한다.

 

즉 구체적인 하위 모듈에 의존하지 않고

추상적인 부분에 의존해야 한다.

 

만약 WeatherService가

외부 API(OpenWeatherMapAPI)에

직접 의존하는 구조라면 외부 모듈이

변경되거나 교체될 때 문제가 생긴다.

= 둘 사이 결합이 강해져 유지보수에 문제가 된다.

 

이를 해결하기 위해 getWeatherData() 같은

메서드를 가진 WeatherAPI 인터페이스를 정의하고,

낮은 수준의 모듈이 이를 구현하도록 만든다.

 

그러면 WeatherService는 특정 API 구현이 아니라

인터페이스에 의존하므로, OpenWeatherMapAPI나

WeatherStackAPI 같은 구현의 교체에 좌우되지 않는다.

 

높은 수준의 모듈이 영향을 받지 않으려면,

높은 수준 모듈과 낮은 수준 모듈을

서로 분리하는 추상화를 도입해야 한다.

 

구체화된 모듈에 의존하지 않고,

추상화된 모듈에 의존하도록 설계하는 것이

의존 관계 역전 원리다.

 

이 원리는 아키텍처 설계에서 중요한

컴포넌트를 구성하는 원리에 기초가 되었다.

//분리 전
WeatherService
-> OpenWeatherMapAPI(직접 의존)

//분리 후
WeatherService
-> WeatherAPI(인터페이스)
<- OpenWeatherMapAPI(구현)
<- WeatherStackAPI(구현)

[ 마무리 ]

객체지향은 상속, 다형성, 인터페이스 같은

기능을 통해 재사용과 확장을 쉽게 만들 수 있다.

 

SOLID는 이러한 객체지향 설계를

이해하기 쉽게 하고, 유지관리와 확장을

쉽게 하기 위한 대표적인 설계 원칙이다.

'개주 훈련일지 > 📚 코살대 교본 학습' 카테고리의 다른 글

SQL) 윈도우 함수  (0) 2026.02.08
컴포넌트 구성 원리  (0) 2026.02.07
SQL) 그룹 함수  (0) 2026.02.02
전통적인 설계 원리  (0) 2026.02.01
품질 목표  (0) 2026.01.31