객체 지향 SOLID 원칙은 코드를 작성하면서 지켜야할 객체 지향 몇가지 원칙중 하나입니다. 디자인 패턴을 공부할때 SOLID 원칙에 기반해서 패턴이 생성되기 때문에 필자는 이 참에 "객체 지향 기본을 잘 공부해야겠다" 라고 생각하고 공부를 했습니다.
SRP를 하나의 문장으로 정의하면 아래와 같습니다.
한 클래스는 오직 하나의 변경 이유만을 가져야 합니다.
이는 객체 지향 프로그래밍(OOP)에서 클래스를 설계할 때, 하나의 클래스에 모든 기능을 넣는 '갓 클래스'를 경계하는 원칙입니다.
처음에 저게 가능할까? 라는 생각을 했었습니다. 물론 개발하면서 객체 지향 원칙을 모두 다 지켜가며 할수는 없습니다. 다만 한 클래스의 변경 이유는 하나만 가져야 한다니 당황을 했습니다. 그래도 SOLID는 프로그래밍 신들이 정해놓은 규칙이라 어떤 내용인지 확인하면서 공부를 했습니다.
예를 들어 SRP를 설명해보겠습니다. 우선 스타트업 직원의 모습을 상상해보겠습니다. 스타트업 특성상 한 사람이 여러 가지 업무를 담당하게 되어, 회사가 특정 직원 한 명에 대한 의존도가 매우 높아집니다. 이는 한 가지 업무만 하는 것이 거의 불가능한 환경 때문입니다.
나중에 이 직원이 장기 휴가를 가거나 퇴사하면 회사는 그로 인한 공백을 그대로 회사가 감당해야 합니다. 이는 직원과 회사 간의 의존도가 높기 때문입니다. 이는 단순한 예시지만, 객체 지향 설계에서도 이러한 의존성을 잘 관리해야 합니다. 의존성을 효과적으로 관리하면 지속 가능하고 성장 가능한 소프트웨어를 만들 가능성이 높아집니다.
SRP를 적용하면 각 클래스나 모듈이 하나의 책임 만을 가지게 되어, 코드의 유지보수성과 재 사용성이 향상됩니다. 예를 들어, 데이터베이스 연결, 비즈니스 로직 처리, 사용자 인터페이스 등을 별도의 클래스로 분리하면 각 부분을 독립적으로 수정하고 테스트할 수 있습니다. 이는 소프트웨어의 복잡성을 줄이고, 변경에 대한 영향을 최소화하여 더 안정적이고 확장 가능한 시스템을 구축하는 데 도움이 됩니다.
다시 돌아와서 직원이 모두 책임지는 스타트업 직원을 클래스를 아래와 같이 설계해보았습니다.
해당 구조를 코드로 구현했다면 대략적으로 아래와 같이 클래스를 생성해 봤습니다.
public class StartUpEmployee {
// 마케팅 관련 변수들
private String facebookToken;
private String instagramToken;
// 개발 관련 변수들
private String githubToken;
private String awsAccessKey;
// 고객 지원 관련 변수들
private String zenDeskToken;
private String slackWebhook;
public void handleMarketing () {
// SNS 마케팅 업무
updateSocialMedia();
// 콘텐츠 제작
createContent();
// 광고 관리
manageAdvertisements();
}
public void handleDevelopment () {
// 웹사이트 개발
updateWebsite();
// 버그 수정
fixBugs();
// 새로운 기능 개발
developNewFeatures();
}
public void handleCustomerSupport () {
// 고객 문의 응대
answerCustomerInquiries();
// 고객 불만 처리
handleComplaints();
// 고객 피드백 정리
organizeCustomerFeedback();
}
}
스타트업 직원이 회사에서 처리하는 하는 업무를 가정하여 만든 클래스입니다. IT 스타트업 직원이 마케팅도 하고, 개발도 하고, 고객 응대까지 모두 담당을 하고 있다고 가정을 해봅시다.
코드의 문제
- 마케팅 전략이 변경될 때마다 개발 코드와 고객응대가 있는 클래스를 수정해야 합니다. 즉 클래스의 책임과 역할이 불분명 해집니다.
- 만약 마케팅과 고객 응대가 서로 간의 협업을 한 코드가 있고 독립적으로 동작해야하는 코드가 동시에 존재한 상태로 코드 수정을 해야한다면 코드 수정 범위는 정말 커지게 될거고 코드 가독성과 테스트도 거의 불가능에 가까울 것입니다. 즉 산탄총 수술이 되는것입니다.
- 산탄총 수술이란 산탄이 사방으로 퍼져서 동물에게 맞았을 때 수술해야하는 부위가 많아지는 것으로 이걸 소프트웨어에 적용하면 어떤 변경이 생겼을 때 여러군데를 변경해야한다는 점에서 착안된 단어입니다.
- 하나의 클래스가 가지고 있는 정보가 많아 단위 테스트 작성 시 모든 것을 모킹해야할수도 있습니다. (DB연결,이메일 전송 등이 기능이 있다면)
그렇다면 구조를 개선하기 위해서 각 업무 영역 별로 담당자를 두어 업무 할당을 하고 이걸 관리하는 Team두어서 구조를 개선 시켜 보았습니다.
그리고 구조를 코드로 구현해 보겠습니다.
// 마케팅만 담당하는 클래스
public class MarketingManager {
public void updateSocialMedia () { ... }
public void createContent () { ... }
public void manageAdvertisements () { ... }
}
// 개발만 담당하는 클래스
public class Developer {
public void updateWebsite () { ... }
public void fixBugs () { ... }
public void developNewFeatures () { ... }
}
// 고객 지원만 담당하는 클래스
public class CustomerSupportRepresentative {
public void answerCustomerInquiries () { ... }
public void handleComplaints () { ... }
public void organizeCustomerFeedback () { ... }
}
public class StartupTeam {
private final MarketingManager marketingManager;
private final Developer developer;
private final CustomerSupportRepresentative customerSupport;
// 생성자를 통한 의존성 주입
public StartupTeam (
MarketingManager marketingManager,
Developer developer,
CustomerSupportRepresentative customerSupport
) {
this.marketingManager = marketingManager;
this.developer = developer;
this.customerSupport = customerSupport;
}
// 마케팅 캠페인 실행
public void executeMarketingCampaign() {
System.out.println("마케팅 캠페인 시작...");
marketingManager.updateSocialMedia();
marketingManager.createContent();
marketingManager.manageAdvertisements();
System.out.println("마케팅 캠페인 완료");
}
// 개발 스프린트 실행
public void executeDevelopmentSprint() {
System.out.println("개발 스프린트 시작...");
developer.updateWebsite();
developer.fixBugs();
developer.developNewFeatures();
System.out.println("개발 스프린트 완료");
}
// 고객 지원 업무 실행
public void handleCustomerSupport() {
System.out.println("고객 지원 업무 시작...");
customerSupport.answerCustomerInquiries();
customerSupport.handleComplaints();
customerSupport.organizeCustomerFeedback();
System.out.println("고객 지원 업무 완료");
}
// 전체 업무 실행 (필요한 경우)
public void executeAllTasks() {
executeMarketingCampaign();
executeDevelopmentSprint();
handleCustomerSupport();
}
}
각 업무 별로 담당하는 클래스를 만들어 StartUpTeam 에서 사용을 하고있습니다. 이렇게 역할과 책임 단위로 클래스 분리를 했을 경우 아래와 같이 장점을 얻을 수 있습니다.
개선된 코드
- 각 클래스명 역할과 책임이 명확하게 분리가 되어 수정 및 기능 추가가 된다고 하더라도 유연하게 확장이 가능합니다.
- 예를 들면 마케팅 매니저의 업무가 수정이 될 경우 마케팅 매니저 클래스에서 수정이 되고 서로 협업을 하더라도 클래스의 역할에 따라 수정 범위가 제한이 됩니다.
- 클래스가 분리되어있어 테스트 진행 시 클래스에 필요한 부분만 모킹을 하여 단위 테스트 작성이 쉬워집니다.
클래스에 어느정도 기능을 넣어야하는지도 개발자가 판단을 해야하는거고 100% SRP 원칙을 지켜나가면서 하는게 아니라 모든 기능을 하나의 클래스에 다 넣지 말고 분리해서 유지보수와 생산성을 올리는것이 핵심인것 같습니다.
'개발이야기 > Design Pattern' 카테고리의 다른 글
객체 지향 SOLID 원칙 - DIP (0) | 2024.11.01 |
---|---|
객체 지향 SOLID 원칙 - ISP (1) | 2024.10.31 |
객체 지향 SOLID 원칙 - OCP (4) | 2024.10.29 |
디자인 패턴에 대한 필요성과 이해 (0) | 2024.10.14 |