개인적으로 SOLID 는 객체지향의 "기초" 라고 하기엔 너무 어렵다고 생각하지만, SOLID를 통해 객체지향 학습을 시작하는 사례가 많으니, SOLID를 이해하기 위한 흐름을 잡아 설명하도록 하겠다.
1. 객체지향 프로그래밍의 기본 특성
객체지향 프로그래밍은 소프트웨어 설계와 구현에 있어 효율성과 유지보수성을 높이기 위한 패러다임이다. 대표적인 네 가지 특성은 캡슐화, 상속, 다형성, 추상화이며, 각각 다음과 같이 설명할 수 있다.
1.1 캡슐화
캡슐화는 데이터(필드)와 그 데이터를 처리하는 메서드를 하나의 객체로 묶어, 외부에서 불필요하거나 민감한 정보에 직접 접근하지 못하도록 제한하는 개념이다. 이를 통해 객체 내부의 상태를 보호하고, 변경의 영향을 최소화할 수 있다.
1.2 상속
상속은 기존의 클래스를 기반으로 새로운 클래스를 정의하여 기능을 확장하거나 수정할 수 있도록 하는 메커니즘이다. 이를 통해 코드의 재사용성을 극대화하고, 중복된 코드를 줄이며, 계층적인 구조를 통해 시스템을 보다 명확하게 설계할 수 있다.
1.3 다형성
다형성은 동일한 인터페이스나 상위 클래스를 공유하는 객체들이, 각기 다른 방식으로 동작할 수 있는 특성을 의미한다. 이를 통해 하나의 코드가 여러 타입의 객체에 대해 유연하게 동작할 수 있으며, 확장성과 유지보수성이 향상된다.
1.4 추상화
추상화는 복잡한 시스템을 단순화하기 위해 핵심적인 개념과 기능만을 도출하는 과정이다. 불필요한 세부 사항은 감추고, 중요한 요소들만을 모델링함으로써 설계의 명료성과 이해도를 높인다.
2. 추상 클래스와 인터페이스의 차이
객체지향 설계에서 추상 클래스와 인터페이스는 공통적인 특징을 추출하여 다른 클래스들이 이를 구현하거나 상속받게 하는 역할을 한다. 두 개념은 다음과 같은 차이가 있다.
2.1 추상 클래스 (Abstract Class)
- 단일 상속: 자바에서는 한 클래스가 단 하나의 추상 클래스로부터 상속받을 수 있다.
- 변수를 포함할 수 있음: 추상 클래스는 일반 필드와 메서드를 포함하여 상태를 가질 수 있다.
- 추상 메소드의 존재: 하나 이상의 추상 메소드를 포함해야 하며, 이를 상속받는 클래스에서 반드시 구현해야 한다.
- 공통 기능 제공: 일부 메소드의 기본 구현을 제공하여 상속받은 클래스들이 공통적으로 사용할 수 있도록 한다.
2.2 인터페이스 (Interface)
- 다중 상속 지원: 클래스는 여러 개의 인터페이스를 구현할 수 있어, 다양한 역할을 동시에 부여받을 수 있다.
- 상태 유지 불가: 전통적으로 인터페이스는 인스턴스 변수를 가질 수 없었으나, 상수(final static 변수)는 선언할 수 있다.
- 기본 메소드 제공 가능: Java 8부터는 default 메소드와 static 메소드의 도입으로, 선언부뿐만 아니라 일부 구현도 제공할 수 있다.
- 순수 계약 역할: 주로 기능의 명세(계약)를 정의하며, 실제 구현은 이를 구현하는 클래스에 위임된다.
3. SOLID 원칙
SOLID 원칙은 객체지향 설계에서 코드의 가독성, 확장성, 유지보수성을 높이기 위한 다섯 가지 원칙을 의미한다. 각 원칙은 아래와 같이 설명할 수 있다.
3.1 단일 책임 원칙 (SRP; Single Responsibility Principle)
한 클래스는 하나의 책임만을 가져야 하며, 그 책임을 완벽하게 수행하도록 설계되어야 한다. 이를 통해 클래스가 변화의 원인을 명확히 파악할 수 있고, 정보 전문 지식(Information Expert) 패턴을 적용할 수 있다.
3.2 개방-폐쇄 원칙 (OCP; Open-Closed Principle)
소프트웨어 구성 요소는 확장에는 열려 있으면서, 변경에는 닫혀 있어야 한다. 즉, 새로운 기능을 추가할 때 기존의 코드를 수정하지 않고도 확장이 가능해야 한다. 이를 위해 보호된 분산(Protected Variance) 전략을 고려할 수 있다.
3.3 리스코프 치환 원칙 (LSP; Liskov Substitution Principle)
서브타입은 언제나 기반 타입으로 대체할 수 있어야 한다. 상속받은 클래스는 상위 클래스의 역할을 완벽히 수행할 수 있어야 하며, 이를 통해 다형성(Polymorphism)을 보장할 수 있다.
3.4 인터페이스 분리 원칙 (ISP; Interface Segregation Principle)
클라이언트는 자신이 사용하지 않는 인터페이스에 의존하지 않아야 한다. 인터페이스는 사용 목적에 맞게 세분화되어야 하며, Controller와 같이 구체적인 역할에 최적화된 설계가 요구된다.
3.5 의존 역전 원칙 (DIP; Dependency Inversion Principle)
고수준 모듈은 저수준 모듈에 의존해서는 안 되며, 추상화된 인터페이스에 의존해야 한다. 이를 통해 Controller, 다형성(Polymorphism), 보호된 분산(Protected Variance) 원칙을 효과적으로 적용할 수 있다.
여기서 레이어는 "컨트롤러", "서비스", "리포지토리" 순으로 접근하는 물리적 레이어가 아니다.
고수준 모듈과 저수준 모듈의 차이는, 도메인, 비즈니스 로직에 가까울 수록 고수준 모듈(상위 레이어)이고, 인프라, 구현체에 가까울 수록 저수준 모듈(하위 레이어)를 의미함에 유의하자.
따라서 여기서 컨트롤러, 서비스, 리포지토리를 고수준 모듈 순으로 정렬하자면, "서비스", "컨트롤러", "리포지토리" 라고 할 수 있다.
참고) 자바에서 다중 상속이 불가능한 이유
자바는 여러 부모 클래스로부터 직접 상속받는 다중 상속을 지원하지 않는다. 그 이유는 다음과 같다.
- 모호성 문제: 여러 부모 클래스로부터 상속을 받을 경우 동일한 이름의 메소드나 필드가 다수 존재하여 어떤 것을 우선적으로 사용할지 결정하기 어려워진다.
- 구조의 단순화: 단일 상속을 통해 클래스 계층구조를 명확하게 유지할 수 있으며, 복잡한 상속 관계로 인한 문제를 사전에 방지할 수 있다.
- 인터페이스의 활용: 자바는 인터페이스를 통해 다중 상속의 효과를 제공함으로써, 코드 재사용성과 유연성을 확보하면서도 모호성 문제를 해결하고 있다.
'Article > 도메인 주도 설계 이해하기' 카테고리의 다른 글
도메인 이벤트 vs. 애플리케이션 이벤트. 그리고 이벤트의 설계 방법 (0) | 2025.04.28 |
---|---|
DDD - JDBC에서 Child Entity를 생성/수정/삭제하는 방법 (0) | 2025.04.27 |
도메인 이벤트와 State Machine 시스템 설계 - 강아지 키우기 (0) | 2025.04.05 |
도메인 서비스 - DDD의 적용, JPA 엔티티와의 비교 (0) | 2025.04.03 |
DDD 트릴레마 - 도메인 모델 완전성 vs 도메인 모델 순수성 (0) | 2025.04.01 |