Clean Code로 유명한 로버트 C. 마틴이 소프트웨어 개발에서 발생하는 설계 문제를 해결하고자 객체지향 설계 원칙을 체계적으로 정리하였다.
S : Single Responsibility Principle - 단일 책임 원칙
O : Open/Closed Principle - 개방/폐쇄 원칙
L : Liskov Substitution Principle - 리스코프 치환 원칙
I : Interface Segregation Principle - 인터페이스 분리 원칙
D : Dependency Inversion Principle - 의존 역전 원칙
이 5가지 원칙에 대해 살펴보자
Single Responsibility Principle - 단일 책임 원칙
클래스는 하나의 책임만 가져야 하며, 클래스가 변경되는 이유는 하나뿐이어야 한다.
클래스가 지나치게 많은 일을 하게 되면 재사용성이 낮아지고, 변경 시 부수적인 영향을 미칠 가능성이 높아진다. SRP는 각 클래스가 명확한 책임을 가지도록 하여 이러한 문제를 방지한다.
class ReportPrinter {
public void printReport(String report) {
System.out.println(report);
}
}
class ReportGenerator {
public String generateReport() {
return "Report Content";
}
}
ReportGenerator는 보고서를 생성하는 책임만, ReportPrinter는 보고서를 출력하는 책임만 가진다.
Open/Closed Principle - 개방/폐쇄 원칙
소프트웨어 엔티티(클래스, 모듈, 함수 등)는 확장에는 열려 있어야 하고, 수정에는 닫혀 있어야 한다.
기존 코드를 수정하지 않고 새로운 기능을 추가할 수 있도록 설계한다. 이는 변경에 따른 부작용을 줄이고 유지보수를 용이하게 만든다.
interface Shape {
double calculateArea();
}
class Circle implements Shape {
private double radius;
public Circle(double radius) {
this.radius = radius;
}
public double calculateArea() {
return Math.PI * radius * radius;
}
}
class Rectangle implements Shape {
private double width, height;
public Rectangle(double width, double height) {
this.width = width;
this.height = height;
}
public double calculateArea() {
return width * height;
}
}
class AreaCalculator {
public double calculateTotalArea(List<Shape> shapes) {
return shapes.stream().mapToDouble(Shape::calculateArea).sum();
}
}
새로운 도형을 추가하려면 Shape 인터페이스를 구현하는 클래스를 추가하면 된다. 기존 클래스는 수정할 필요가 없다.
Liskov Substitution Principle - 리스코프 치환 원칙
서브타입은 언제나 자신의 기반 타입으로 교체할 수 있어야 한다.
상속 관계에서 부모 클래스와 자식 클래스가 동일하게 동작해야 하며, 이를 통해 다형성을 안전하게 유지할 수 있다.
위반 예시
class Rectangle {
protected int width;
protected int height;
public void setWidth(int width) {
this.width = width;
}
public void setHeight(int height) {
this.height = height;
}
public int calculateArea() {
return width * height;
}
}
class Square extends Rectangle {
@Override
public void setWidth(int width) {
this.width = this.height = width;
}
@Override
public void setHeight(int height) {
this.width = this.height = height;
}
}
Square은 Rectangle을 상속받지만, setWidth와 setHeight의 동작이 다르다. 이는 Rectangle로 작성된 코드가 Square로 대체되었을 때 예상치 못한 결과를 초래할 수 있다.
-> Rectangle과 Square은 본질적으로 다른 도형이므로 상속 관계로 설계하기보다는 별도의 독립적인 클래스로 분리하는 것이 좋다.
Interface Segregation Principle - 인터페이스 분리 원칙
클라이언트는 자신이 사용하지 않는 메서드에 의존하지 않아야 한다.
하나의 거대한 인터페이스 대신 클라이언트에 맞는 여러 개의 작은 인터페이스로 분리하여 불필요한 의존성을 줄인다.
interface Printer {
void printDocument(String content);
}
interface Scanner {
void scanDocument();
}
class MultiFunctionPrinter implements Printer, Scanner {
public void printDocument(String content) {
System.out.println("Printing: " + content);
}
public void scanDocument() {
System.out.println("Scanning document...");
}
}
class BasicPrinter implements Printer {
public void printDocument(String content) {
System.out.println("Printing: " + content);
}
}
BasicPrinter는 프린트 기능만 필요하므로 스캔과 관련된 메서드를 구현할 필요가 없다.
Dependency Inversion Principle - 의존 역전 원칙
고수준 모듈은 저수준 모듈에 의존해서는 안 되며 둘 다 추상화된 것에 의존해야 한다.
의존성을 추상화하여 모듈 간 결합도를 낮추고 변화에 더 유연하게 대응할 수 있도록 한다.
interface NotificationService {
void sendNotification(String message);
}
class EmailNotificationService implements NotificationService {
public void sendNotification(String message) {
System.out.println("Sending Email: " + message);
}
}
class SMSNotificationService implements NotificationService {
public void sendNotification(String message) {
System.out.println("Sending SMS: " + message);
}
}
class UserController {
private NotificationService notificationService;
public UserController(NotificationService notificationService) {
this.notificationService = notificationService;
}
public void notifyUser(String message) {
notificationService.sendNotification(message);
}
}
UserController는 NotificationService 인터페이스에 의존하며 특정 구현(EmailNotificationService, SMSNotificationService)에 직접 의존하지 않는다. 이를 통해 의존성을 유연하게 교체할 수 있다.
SOLID 원칙을 준수하여 깨끗하고 견고한 객체지향 설계를 해보자.
'코딩 규칙과 방법들' 카테고리의 다른 글
[MVC패턴] 역할을 어떻게 나눠야할까? (0) | 2024.10.29 |
---|---|
테스트 주도 개발 TDD는 무엇일까? (1) | 2024.10.22 |
원활한 협업을 위한 '커밋 메시지' 작성 규칙 (0) | 2024.10.22 |
Code를 Clean하게 작성하는 방법! (1) | 2024.10.22 |
MVC 패턴이 무엇일까? (3) | 2024.10.22 |