본문 바로가기

Develop/소프트웨어 설계

[SOLID] Single Responsibility 원칙

반응형

목차

Single Responsibility 원칙

Open/Closed 원칙

Liskov Substitution 원칙

Interface Segregation 원칙

Dependency Inversion 원칙

 

 

단일 책임 원칙
Single Responsibility 원칙

Single Responsibility (단일 책임) 원칙

모든 클래스는 하나의 책임만을 부여받으며, 단 하나의 이유만을 바탕으로 변경되어야 한다.

클래스가 제공하는 모든 기능은 클래스가 부여받은 책임에 기반하여 작성되어야 한다. 만약 모든 기능을 하나의 클래스가 책임진다면, 접근하지 말아야 할 변수에 엉뚱한 함수가 접근하여 내부를 부패시킬 수 있다. 반대로 여러 클래스가 하나의 기능을 책임진다면, 기능에 변경이 있을 때마다 각각의 클래스를 변경에 맞추어 완벽하게 작업해야 하는 부담이 발생한다. 특히 다른 개발자가 클래스의 책임을 잘못 이해하고 변경하면 반드시 버그가 발생하고 생산성이 저하되게 된다.

그래서 Single Responsibility 원칙은 클래스가 하나의 책임을 담당하도록 하여, 특정 기능에 대한 변경이 필요할 때 하나의 클래스만 변경하면 해결되도록 설계하는 원칙이다.

 

예제

public class InvitationService
{
	public void SendInvite(string email, string firstName, string lastName)
    {
    	if(String.IsNullOrWhiteSpace(firstName) || String.IsNullOrWhiteSpace(lastName))
        {
         	throw new Exception("Name is not valid!");
        }
    	
    	if(!email.Contains("@") || !email.Contains("."))
        {
        	throw new Exception("Email is not valid!!");
        }
        SmtpClient client = new SmtpClient();
        client.Send(new MailMessage("mysite@nowhere.com", email) { Subject = "Please join me at my party!" });
    }
}

SendInvite 함수로 인해 InvitationService 클래스는 초대장을 발송하는 책임만이 아니라, 초대장에 사용될 내용을 검증해야 하는 책임도 수행한다. 따라서 InvitationService 클래스는 초대장을 발송하는 방법이 변경되어도 수정되어야 하고, 초대장의 내용을 검증하는 알고리즘이 정교해지더라도 수정되어야 한다.

 

따라서 Single Responsibility 원칙에 따라 아래와 같이 리팩토링한다.

 

public class UserNameService
{
    public void Validate(string firstName, string lastName)
    {
        if(String.IsNullOrWhiteSpace(firstName) || String.IsNullOrWhiteSpace(lastName))
        {
            throw new Exception("The name is invalid!");
        }
    }
}

public class EmailService
{
    public void Validate(string email)
    {
        if (!email.Contains("@") || !email.Contains("."))
        {
            throw new Exception("Email is not valid!!");
        }
    }
}

public class InvitationService
{
    UserNameService _userNameService;
    EmailService _emailService;

    public InvitationService(UserNameService userNameService, EmailService emailService)
    {
        _userNameService = userNameService;
        _emailService = emailService;
    }
    public void SendInvite(string email, string firstName, string lastName)
    {
        _userNameService.Validate(firstName, lastName);
        _emailService.Validate(email);
        SmtpClient client = new SmtpClient();
        client.Send(new MailMessage("sitename@invites2you.com", email) { Subject = "Please join me at my party!" });
    }
}

 InvitationService 클래스의 검증 책임은  UserNameService 와 EmailService 에게 부여하고, 초대장을 발송하는 책임 하나에 집중하도록 한다.

 

원칙 적용 전에 심사숙고할 사항

Single Responsibility 원칙의 핵심은 클래스의 변경으로 인한 파급 효과를 최소화해, 개발자의 실수에서 비롯된 디버그 비용 및 기능 확장에 투입되는 비용을 줄여 개발 생산성을 향상하는 것이다. 하지만 원칙을 엄격하게 준수하려고 하면 오히려 개발 생산성을 저하시킬 수 있다. 불필요하게 많은 클래스를 양산하게 될 수도 있고, 책임을 명확하게 나누기 어려운 경우도 있기 때문이다. 그리고 이미 작성된 코드에게 원칙을 무리하게 도입할 경우 개발자 간에 다툼도 발생할 수 있다. 그럼에도 불구하고 분명 Single Responsibility 원칙은 대체적으로 개발 생산성을 향상시킬 수 있는 원칙이며, 약간의 융통성을 발휘하며 더욱 효과적인 원칙이다.

반응형