일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
31 |
- jvm
- 사용자계정 #WSL2
- 호스트주소 #Host주소
- WSL #Linux #ubuntu
- Java환경
- 에러해결
- 회고
- JAVA기본
- 회고 #주간회고
- 코드스테이츠 #
- 시작 #꾸준히 #과하지않게
- mapstruct
- 롤중독
- 네트워크 기초
- 글 foramt
- OOP
- 백엔드로드맵
- git #intellij
- 코딩테스트
- 몰입학습 #몰입
- 회고록_코드스테이츠
- 피드백
- Git #회고록
- 코드스테이츠 #회고록
- 정리글 작성법
- 공부방식
- WSL2 #권한
- 인터넷 #ISP
- 회고록
- String
- Today
- Total
느리더라도 꾸준히
[OOP]객체지향 프로그래밍(OOP) - (4) SRP(Single-Relationship Principle) 본문
목표
(목표 수정)
객체 지향적이지 않은 코드를 객체 지향으로 바꾸면서 객체지향의 4가지 특징을 이해한다.
그리하여 최종적으로 객체 지향적인 코드를 만들어보고, 이를 미리 구현 해 놓은 Springframework를 이해해보자.
(1) 상속
(2) 추상화 및 다형성를 통한 DI(Dependency Injection)
(3) 캡슐화
(4) SRP(Single-Relationship Principle)
정리
SRP(단일 책임 원칙)
- 단일 책임 원칙은 "클래스는 단 한 개의 책임을 가져야 한다."를 의미하는 간단한 규칙입니다. 클래스가 여러 책임을 갖게 되면 그 클래스는 각 책임마다 변경되는 이유가 발생하기 때문에 클래스가 한 개의 이유로만 변경되려면 클래스는 한 개의 책임만을 가져야한다고 합니다.
이러한 이유로 이 원칙은 다른 말로 "클래스를 변경하는 이유는 단 한 개여야 한다."고도 표현합니다.
- SRP는 객체 간의 결합도를 낮추고, 응집도(자율성)을 높이기 위해 사용됩니다. 만약 SRP를 지키지 않을시 아래의 문제들이 발생합니다.
- 한 클래스 안에서 서로 다른 역할을 수행하는 코드끼리 강하게 결합되어 있는 경우, 하나의 변화에 대하여 결합된 모든 기능(메서드)들을 테스트 해야 하므로 유지 보수가 어렵다.
- 한 클래스 안에 서로 다른 역할을 수행하는 코드들을 많이 포함하고 있는 경우, 여러 요구 사항의 변화에 민감하게 대응하는 클래스가 된다.
- 책임에 맞는 클래스를 생성하고 의존성 주입을 사용하면 SRP 원칙을 지키도록 소스 코드를 작성 할 수 있다.
본문
문제 상황
MartApp
public class MartApp {
// 고객정보
CustomerRepository customerRepository = new CustomerRepository();
Discount discountClass = new Discount(new HashMap<String, DiscountCondition>(){{
put("학생",new StudentDiscountCondition(new RatePolicy(10)));
put("직원",new EmployeeDiscountCondition(new AmountPolicy(10000)));
Customer[] customers = customerRepository.findAll();
int originalPrice = new PhoneInfo(1000000, "iphone").getPrice();
// ----------------
MartService martService
= new MartService(customers, originalPrice, discountClass);
public void start(){
// 할인 로직 시작
martService.service();
}
}
MartApp 클래스는 Mart의 여러 서비스들(현재는 할인 로직만 존재)을 시작 시키는 클래스입니다. 그러나 현재 서비스시작의 책임과 객체 생성의 책임을 지고 있습니다. 이는 객체 지향의 특징인 SRP(single-Responsibility Principle)을 어기고 있다고 말 할 수 있습니다.
SRP란?
단일 책임 원칙은 "클래스는 단 한 개의 책임을 가져야 한다."를 의미하는 간단한 규칙입니다. 클래스가 여러 책임을 갖게 되면 그 클래스는 각 책임마다 변경되는 이유가 발생하기 때문에 클래스가 한 개의 이유로만 변경되려면 클래스는 한 개의 책임만을 가져야한다고 합니다.
이러한 이유로 이 원칙은 다른 말로 "클래스를 변경하는 이유는 단 한 개여야 한다."고도 표현합니다.
SRP 법칙을 지키지 않으면 어떻게 될까?
- 한 클래스 안에서 서로 다른 역할을 수행하는 코드끼리 강하게 결합될 수 있다. 이러한 코드끼리의 결합은 하나의 변화에 대해 많은 변경 사항을 발생 시키고 관련된 모든 기능을 테스트해봐야 하는 단점이 있다. 즉 이는 결국 유지 보수 하기 어려운 대상이 된다.
- SRP를 준수하지 않은 클래스는 여러 요구사항의 변화에 민감하게 대응해야 한다.
결국 객체 간의 결합도를 낮추고, 응집도(자율성)을 높이기 위해 SRP법칙을 지키려고 하는 것이다.
위 MartApp의 경우, 새로운 객체를 추가하거나 주입 시킬 객체의 변경이라는 요구 사항이 생겼을 때에도 MarkApp을 수정해야 한다.
또한 MartApp의 새로운 서비스를 만들어서, 해당 서비스를 실행시킬 때도, MartApp를 수정해야 한다. 그러므로 SRP 법칙을 지키지 않는다고 말할 수 있습니다.
필요한 객체를 생성해주는 클래스를 생성하고 의존성 주입(DI)를 통해 SRP 원칙을 지키도록 소스 코드를 수정해봅시다.
Appconfig class
객체를 생성하고 주입하는 책임만 가지는 Appconfig를 생성해줍시다.
public class AppConfig {
public CustomerRepository customerRepository(){
return new CustomerRepository();
}
public PhoneInfo phoneInfo(){
return new PhoneInfo(1000000, "iphone");
}
public Discount discount(){
return new Discount(new HashMap<String, DiscountCondition>(){{
put("학생",new StudentDiscountCondition(new RatePolicy(10)));
put("직원",new EmployeeDiscountCondition(new AmountPolicy(10000)));
}});
}
// 싱글톤 연습을 위해 생성한 메서드
public RemovedRepository removedRepository(){
return new RemovedRepository(customerRepository());
}
}
각 메서드들은 객체와 같은 이름을 가지며, 그저 객체를 생성해서 return 할 뿐입니다. 이제 이 객체들을 주입 받을 수 있도록 MartApp을 수정해줍시다.
MartApp
public class MartApp {
// 주입이 필요한 객체들-------------------
CustomerRepository customerRepository;
PhoneInfo phoneInfo;
Discount discountClass;
RemovedRepository removedRepository;
//-------------------------------------
public MartApp(CustomerRepository customerRepository, PhoneInfo phoneInfo, Discount discountClass, RemovedRepository removedRepository) {
this.customerRepository = customerRepository;
this.phoneInfo = phoneInfo;
this.discountClass = discountClass;
this.removedRepository = removedRepository;
}
public void start(){
// -----------인스턴스변수의 초기화순서 : 기본값 -> 명시적초기화 -> 인스턴스 초기화 블럭 -> 생성자
// 그래서 메서드 안에서 지역변수로 선언했다.
Customer[] customers = customerRepository.findAll();
int originalPrice = phoneInfo.getPrice();
MartService martService = new MartService(customers, phoneInfo.getPrice(), discountClass);
System.out.println("-".repeat(50));
System.out.println("핸드폰의 원가는 " + phoneInfo.getPrice() + "원 입니다.");
System.out.println("-".repeat(50));
martService.service();
}
}
주입이 필요한 객체를 변수로 선언해놓고, DI 중 생성자 주입을 통해 참조변수에 객체를 주입시킨 모습입니다. 이제는 만약 할인 조건이나 할인 정책이 추가되거나 변경되더라도 AppConfig의 소스 코드만 변경해주면 됩니다.
이제는 아래와 같이 책임이 분리되었습니다.
Appconfig
: 할인 로직의 변경이나 추가로 인해 객체의 생성해야 하는 요구 사항에 대한 책임
MartApp
: App에서 필요한 서비스를 실행시키는 책임
참고
이전의 customers, originalPrice 는 필드에 선언했었습니다. 그러나 생성자 초기화 순서가 필드 순서보다 늦기 때문에, null값이 들어가는 일이 발생할 수 있으므로, 지역변수에서 초기화 하도록 코드를 변경하였습니다.
출처
'Java' 카테고리의 다른 글
[JAVA기본]함수 클래스 객체 인스턴스 (0) | 2022.12.03 |
---|---|
[OOP]객체지향 프로그래밍(OOP) - (5) 싱글톤 패턴 (0) | 2022.11.20 |
[Java기본]생성자와 필드의 초기화 시점 및 순서 (0) | 2022.11.20 |
[Stream]map() 그리고 mapToInt(); (0) | 2022.11.20 |
[OOP]객체지향 프로그래밍(OOP) - (3) 캡슐화 (0) | 2022.11.17 |