[개발지식] SOLID 원칙- 1.단일 책임 원칙

Posted by 김성철

SOLID 원칙

SOLID 원칙은 소프트웨어 개발에서 디자인 원칙을 제시하는 가이드라인으로, 이를 따르면 유지 보수 가능하고 확장성이 좋은 소프트웨어를 개발할 수 있습니다.  
SOLID는 다음의 다섯 가지 원칙을 나타냅니다.  
  
- SRP (Single Responsibility Principle - 단일 책임 원칙):  
	클래스는 단 하나의 책임만 가져야 합니다. 어떤 클래스도 변경되는 이유는 단 하나여야 하며, 이를 통해 클래스의 재사용성, 유지 보수성, 테스트 용이성이 증가합니다.  
  
- OCP (Open-Closed Principle - 개방-폐쇄 원칙):  
	소프트웨어 개체(클래스, 모듈, 함수 등)는 확장에는 열려 있어야 하고, 수정에는 닫혀 있어야 합니다. 즉, 기존의 코드를 변경하지 않고도 새로운 동작을 추가하거나 기존 동작을 변경할 수 있어야 합니다.  
  
- LSP (Liskov Substitution Principle - 리스코프 치환 원칙):  
	부모 클래스는 언제나 자식 클래스로 대체될 수 있어야 합니다. 이를 위해 자식 클래스는 부모 클래스의 기능을 완전히 지원하고, 부모 클래스가 정의한 규약을 준수해야 합니다.  
  
- ISP (Interface Segregation Principle - 인터페이스 분리 원칙):  
	클라이언트는 자신이 사용하지 않는 인터페이스에 의존해서는 안 됩니다. 인터페이스는 클라이언트의 요구에 따라 작게 분리되어야 하며, 이를 통해 클라이언트는 필요한 인터페이스에만 의존할 수 있습니다.  
  
- DIP (Dependency Inversion Principle - 의존성 역전 원칙):  
	의존성은 추상화에 의존해야 하며, 구체화에 의존해서는 안 됩니다. 즉, 코드는 인터페이스나 추상 클래스와 같은 추상화에 의존해야 하고, 구체적인 구현에는 의존하지 않아야 합니다.  
  
이러한 SOLID 원칙들은 소프트웨어의 유연성, 확장성, 재사용성, 유지 보수성을 높이기 위한 지침으로, 객체지향 프로그래밍과 관련하여 많이 언급됩니다. 이러한 원칙들을 준수하면 변경에 유연하고 모듈화된 소프트웨어를 개발할 수 있습니다.  

SRP (Single Responsibility Principle - 단일 책임 원칙)

SRP (Single Responsibility Principle) 또는 단일 책임 원칙은 SOLID 원칙 중 하나로, 클래스나 모듈은 단 하나의 책임만 가져야 한다는 원칙입니다.  
이는 클래스나 모듈을 변경할 때 한 가지 이유만 있어야 한다는 것을 의미합니다.  
SRP를 따르면 각 클래스는 특정한 책임에 집중하고, 한 가지 도메인 개념에 대한 기능을 제공하게 됩니다.  
  
SRP는 소프트웨어의 유지 보수성, 재사용성, 테스트 용이성을 개선하는 데 도움이 됩니다.  
이를 통해 코드의 응집성(cohesion)이 높아지고 결합도(coupling)가 낮아지게 되어 코드의 이해와 변경이 용이해집니다.  
SRP를 따르기 위해서는 다음과 같은 지침을 따를 수 있습니다:  
  
- 한 클래스에는 한 가지 책임만 부여합니다.  
  
- 클래스가 너무 많은 책임을 갖게 되면 코드가 복잡해지고 이해하기 어려워질 수 있습니다.  
	각 클래스는 명확하고 구체적인 역할을 갖도록 설계되어야 합니다.  
  
- 클래스의 변경이 한 가지 이유로만 발생하도록 합니다.  
  
- 클래스를 변경할 때는 해당 클래스의 책임과 관련된 요구 사항에만 따라야 합니다.  
	다른 책임을 변경하기 위해서는 다른 클래스를 수정해야 합니다.  
  
- 관련된 메서드와 속성을 함께 유지합니다.  
  
- 클래스의 책임에 따라 메서드와 속성을 그룹화하여 클래스의 응집성을 높입니다.  
	이는 코드의 가독성을 향상시키고 잠재적인 버그를 줄일 수 있습니다.  
  
SRP를 준수하는 예를 들어보면, 고객 정보를 저장하고 검색하는 클래스와 메일을 전송하는 클래스를 분리하는 것입니다.  
이렇게 하면 고객 정보에 대한 변경이나 고객과 관련된 이메일 전송에 대한 변경이 각각의 클래스에서 독립적으로 이루어질 수 있습니다.  

SRP 예시

-  잘못된 코드 예시  
인터넷 쇼핑몰을 개발하고 있다고 가정하겠습니다. 이때 고객 정보를 처리하는 클래스를 생각해보겠습니다.  
=====================================================================  
public class Customer {  
	private String name;  
	private String email;  
	private Address address;  
  
	public void saveCustomer() {  
		// 고객 정보를 데이터베이스에 저장하는 로직  
	}  
  
	public void sendConfirmationEmail() {  
		// 고객에게 확인 이메일을 전송하는 로직  
	}  
  
	public void validateCustomer() {  
		// 고객 정보를 유효성 검사하는 로직  
	}  
  
	public void calculateDiscount() {  
		// 고객에게 할인 금액을 계산하는 로직  
	}  
}  
=====================================================================  
  
위의 예시에서는 Customer 클래스가 다양한 책임을 가지고 있습니다.  
saveCustomer() 메서드는 데이터베이스에 고객 정보를 저장하는 책임을 담당하고,  
sendConfirmationEmail() 메서드는 이메일을 전송하는 책임을 담당하며,  
validateCustomer() 메서드는 유효성 검사를 담당하고,  
calculateDiscount() 메서드는 할인 금액을 계산하는 책임을 담당합니다.  
  
이렇게 하나의 클래스에 다양한 책임을 부여하면 클래스가 복잡해지고 변경이 발생했을 때 다른 책임들에도 영향을 주는 문제가 발생할 수 있습니다.  
  
- 올바른 예시  
=====================================================================  
public class Customer {  
	private String name;  
	private String email;  
	private Address address;  
  
	// Getter, Setter, Constructor  
  
	// 데이터베이스와 상호작용하는 책임을 담당하는 클래스로 분리  
	public void saveCustomer() {  
		// 고객 정보를 데이터베이스에 저장하는 로직  
	}  
}  
  
public class EmailService {  
	public void sendConfirmationEmail(Customer customer) {  
		// 고객에게 확인 이메일을 전송하는 로직  
	}  
}  
  
public class CustomerValidator {  
	public void validateCustomer(Customer customer) {  
		// 고객 정보를 유효성 검사하는 로직  
	}  
}  
  
public class DiscountCalculator {  
	public void calculateDiscount(Customer customer) {  
		// 고객에게 할인 금액을 계산하는 로직  
	}  
}  
=====================================================================  
  
- Customer 클래스는 고객 정보만을 관리하고, 데이터베이스와의 상호작용을 위한 saveCustomer() 메서드를 제공합니다.  
이렇게 단일 책임을 갖는 클래스는 고객 정보에 관련된 작업에 집중할 수 있으며, 코드의 응집성이 높아집니다.  
  
- EmailService 클래스는 이메일 전송에 대한 책임을 담당합니다. sendConfirmationEmail() 메서드는 고객에게 확인 이메일을 전송하는 로직을 수행합니다.  
이렇게 분리된 이메일 전송 기능은 필요에 따라 재사용할 수 있으며, Customer 클래스의 변경 없이도 독립적으로 확장할 수 있습니다.  
  
- CustomerValidator 클래스는 고객 정보의 유효성을 검사하는 책임을 담당합니다. validateCustomer() 메서드는 고객 정보를 받아 유효성을 검사하는 로직을 수행합니다.  
이를 통해 유효성 검사 기능을 별도로 관리할 수 있고, Customer 클래스와 분리되어 변경될 수 있습니다.  
  
- DiscountCalculator 클래스는 고객에게 할인 금액을 계산하는 책임을 담당합니다.  
calculateDiscount() 메서드는 고객 정보를 받아 할인 금액을 계산하는 로직을 수행합니다.  
이를 통해 할인 계산 로직을 담당하는 클래스를 따로 분리함으로써 코드의 응집성을 높일 수 있습니다.  
  
이렇게 단일 책임 원칙을 준수하면 각 클래스는 자신의 목적과 책임에 집중하며, 코드의 가독성, 유지 보수성, 재사용성이 향상됩니다.  
또한, 하나의 책임에 대한 변경이 다른 책임에 영향을 주지 않아 변경의 범위를 최소화할 수 있습니다.