개발 과정에서 복잡한 코드와 구조에 직면했을 때, SOLID 원칙은 강력한 지침이 됩니다.
처음 들어본다면 다소 어려울 수 있지만, 이 원칙들은 깔끔하고 유지보수가 쉬운 코드를 작성하는 데 필수적입니다.
Single Responsibility Principle 단일 책임 원칙
단일 책임 원칙이라는 단어도 왠지 맘에 안듭니다.
하나의 객체는 하나의 역할만 해야한다는 내용입니다.
- 예를 들어, "요리사" 객체는 요리만 담당해야 하며, 웨이터의 역할인 "서빙"을 해서는 안 됩니다.
- 객체가 여러 역할을 수행하면 단일 책임 원칙을 위반하게 됩니다.
Open-Closed Principle 개방-폐쇄 원칙
이름이 더 이해하기 어렵게 만드는 것 같습니다.
새로운 기능을 추가할 때 기존 코드가 변경되면 안된다는 내용입니다.
예를들어 쇼핑몰에서 매달 특정 금액(1000원)을 할인해주는 이벤트를 하는데, %로 할인을 하는 비율 할인 기능을 추가해야 한다합니다.
이때 "비율 할인 기능"을 코드로 작성하면서, 기존에 있던 "고정 금액 할인" 기능을 수정하지 않아야 하며 오류도 발생해서는 안됩니다.
만약 수정이 필요하다면 이는 '개방 폐쇄 원칙에 위배된다' 라고 할 수 있습니다.
public Object purchase(Long itemId) {
Item item = itemRepository.find(itemId);
// 이달의 이벤트 할인가
int discountPrice = 1000;
int discountedPrice = item.price - discountPrice;
return purchaseService.purchase(item, discountedPrice);
}
위 코드에서 purchase 메서드의 수정 없이는 할인 정책을 고정금액에서 비율금액으로 변경할 수 없으므로 개방 폐쇄 원칙에 위배되는 코드입니다.
Liskov Substitution Principle 리스코프 치환 원칙
리스코프란 미국인 컴퓨터 과학자의 이름입니다(?)
자식(서브) 클래스는 부모(슈퍼) 클래스의 모든 기능을 사용 가능해야 한다는 것입니다.
간단한 예제로 설명하면 '사각형'이라는 클래스가 '직각삼각형'이라는 클래스의 자식으로 있습니다.
public class 사각형 extends 직각삼각형 {
직각삼각형에는 다음과 같은 메서드가 정의되어 있습니다.
- 둘레 구하기
- 넓이 구하기
- 빗변 구하기
이 때 직각 삼각형에 있는 '둘레 구하기', '넓이 구하기'는 사각형에서 오버라이딩을 하여 사용할 수 있습니다.
그러나 사각형은 빗변이 없으므로 이 '빗변 구하기' 라는 메서드는 사각형에서 사용이 불가능합니다.
이것을 '리스코프 치환 원칙에 위배된다' 라고 합니다.
슈퍼 클래스에서 정의된 기능들은 서브 클래스에서 모두 사용이 가능해야 합니다. 그래야 서브타입이 슈퍼타입을 완벽히 대체할 수 있고, 이 때 비로소 리스코프 치환원칙에 맞다 라고 할 수 있습니다.
Interface Segregation Principle 인터페이스 분리 원칙
클래스가 인터페이스에 있는 추상 메서드를 구현하지 않으면 안된다. 라는 내용입니다.
코드로 보면 매우 간단합니다.
여기 스마트폰 인터페이스가 있고, 순서대로 전화/인터넷/mp3 기능이 있습니다.
public interface SmartPhone {
void call();
void internet();
void mp3();
}
public class MyOldPhone implements SmartPhone { ...
해당 인터페이스를 구형해서 핸드폰을 잘 만들고 있었는데, 새로운 핸드폰부터 mp3기능이 필요없어서 제거해야 합니다.
이 때 인터페이스를 분리하면 해결이 가능합니다.
public interface OldSmartPhoneFunctions {
void mp3();
}
public interface SmartPhone {
void call();
void internet();
}
코드를 분리했습니다. 적용해보겠습니다.
public class MyOldPhone implements SmartPhone, OldSmartPhoneFunctions {
...
}
public class MyNewPhone implements SmartPhone {
...
}
이렇게 분리하면 MyNewPhone class에서도 SmartPhone의 기능을 모두 사용가능하게 됩니다.
Dependency Inversion Principle 의존성 역전의 원칙
구현체가 아닌 인터페이스를 사용해야 한다는 내용입니다.
코드를 보시면 DataProcessor 클래스에서 DatabaseDataService 혹은 FileDataServcie를 직접사용하지 않습니다.
interface DataService {
void saveData(String data);
}
class DatabaseDataService implements DataService {
@Override
public void saveData(String data) {
// 데이터를 데이터베이스에 저장하는 코드
}
}
class FileDataService implements DataService {
@Override
public void saveData(String data) {
// 데이터를 파일에 저장하는 코드
}
}
class DataProcessor {
private final DataService dataService;
public DataProcessor() {
this.dataService = new DatabaseDataService();
}
public void processData(String data) {
// 데이터 처리 로직
dataService.saveData(data);
}
}
DataService를 사용하므로써 구현체를 쉽게 변경 가능하도록 만들었습니다.
'Architecture' 카테고리의 다른 글
의존성 역전 원칙(DIP): 유연하고 확장 가능한 코드 설계의 핵심 (0) | 2024.02.05 |
---|---|
좋은 코드를 위한 5가지 핵심 원칙: SOLID부터 리팩토링까지 (0) | 2024.01.21 |
효율적인 데이터 분산 저장 전략: 샤딩과 리밸런싱의 이해 (0) | 2023.08.11 |
Kafka의 고가용성: 장애 대응 및 데이터 손실 방지 (0) | 2023.08.08 |
DB 성능 저하 해결 전략: 스케일링, 리플리케이션, 샤딩 그리고 클러스터링 (1) | 2023.08.04 |