SOLID
SRP : Single Responsibility Principle -> 단일 책임 원칙
- 하나의 클래스가 하나의 책임(변경 이유) 만을 가져야 한다.
SRP를 지켰을 때의 이점
- 관심사의 분리
- 높은 응집도, 낮은 결합도
이걸 하기 위해서는"책임"을 볼 수 있는 눈이 있어야 한다.
MineswepperGame클래스를Minesweeper클래스와GameApplication클래스로 분리
-> 게임 실행에 대한 책임과 지뢰찾기 자체를 분리- 사용자 입출력 부분을
ConsoleInputHandler와ConsoleOutputHandler클래스로 분리
-> 입출력 책임을 분리 BOARD를GameBoard클래스 객체로 분리
-> Board 내부의 값에 대한 초기화, 변경 및 조회 책임을 분리
💭 : 클래스 단위로 책임을 분리한다.
OCP : Open-Closed Principle -> 개방-폐쇄 원칙
- 확장에는 열려 있고, 수정에는 닫혀 있어야 한다.
-> 기존 코드의 변경 없이 기능을 확장 할 수 있어야 한다.
추상화와 다형성을 활용해 OCP를 지킬 수 있다.
지뢰 찾기에 게임의 난이도를 변경해야 하는 새로운 요구사항이 발생
-> Board의 Size가 변경 될 수 있어야 한다
- switch 문이나 고정된 값을 이용해 사용하던 로직을 유동적인 변수를 받아 동작하도록 변경
GameLevel인터페이스를 활용해 인터페이스를 구현하는 난이도 구현체들을 생성해 난이도 변경
💭 : 고정된 값이나 클래스를 사용하기 보다 변수나 인터페이스를 활용해 프로그램을 유동성 있게 만든다.
LSP : Liskov Substitution Principle -> 리스코프 치환 원칙
- 상속 구조에서, 부모 클래스의 인스턴스를 자식 클래스의 인스턴스로 치환할 수 있어야 한다.
-> 자식 클래스는 부모 클래스의 책임을 준수하고 부모 클래스의 행동을 변경하면 안된다.
LSP를 위반하면 상속 클래스를 사용할 때 예외나 불필요한 타입 체크가 동반 될 수 있다.
보통 부모 자식 관계에서 자식이 더 기능을 많을텐데 부모가 쓰이던 자리에 자식이 쓰여도 동일하게 동작을 해야한다는 것이다!
Cell을 상속 구조로 변경해지뢰 Cell,숫자 Cell,빈 Cell이 자식이 되도록 변경- 이때 어떤
Cell인지 확인하기 위해 instanceof를 사용해야 하는 설계가 됨 - 각
Cell특성에 따라 메서드 구조 변경
💭 : 상속을 사용할 때에는 LSP를 신경써서 사용하자
ISP : Interface Segregation Principle -> 인터페이스 분리 원칙
- 클라이언트는 자신이 사용하지 않는 인터페이스에 의존하면 안된다
-> 인터페이스를 잘게 쪼개라
ISP를 위반하면 결합도가 높아지고 특정 기능의 변경이 여러 클래스에 영향을 미칠 수 있다.
run()과initialize()메서드를 가지는Game인터페이스를 만듬- 게임 인터페이스를 구현하는 다른 클래스가
initialize()메서드가 필요가 없을 수가 있다. - 여기서
Game인터페이스를GameInitializable인터페이스와GameRunnable인터페이스로 분리한다.
💭 : 인터페이스를 기능별로 잘게 쪼개서 사용하자
DIP : Dependency Inversion Principle -> 의존성 역전 원칙
- 상위 수준의 모듈은 하위 수준의 모듈에 의존해서는 안되고 둘 모두 추상화에 의존해야 한다.
의존성의 순방향 : 고수준 모듈이 저수준 모듈을 참조하는 것
의존성의 역방향 : 고수준, 저수준 모듈이 모두 추상화에 의존하는 것
-> 저수준 모듈이 변경되어도 고수준 모듈에는 영향이 가지 않는다.
고수준 : 추상화 레벨이 높음
저수준 : 추상화 레벨이 낮음
의존성 : 하나의 모듈이 다른 모듈을 알고 있거나, 생성하거나 사용하거나 참조할 때, 의존성이 있다고 한다.
순방향은 고수준 모듈이 직접 저수준 모듈을 가져다가 쓰는 것이다.
역방향은 추상화된 스펙 (인터페이스) 만 참조해 가져다가 쓰는 것이다. 이때 런타임 시에 구현체가 정해지게 된다.
consoleInputHandler와consoleOutputHandler는 콘솔에 의존하게 된다.InputHander와OutputHander인터페이스를 생성해 스펙을 정하고 구현체는 스펙에 따라 구현한다.
DI (Dependency Injection) -> 의존성 주입
필요한 의존성을 외부로 주입 받는다.
DI를 떠올리면 숫자 3을 떠올려야 한다. A가 B라는 의존성을 주입받고 싶을 때 제3자가 의존성을 맺어주게 된다. 스프링에서는 Spring Context(IoC Container)가 해주게 된다.
IoC (Inversion of Control) -> 제어의 역전
프로그램의 흐름, 제어의 주도권을 프레임워크로 넘겨주게 된다.
IoC Container가 객체를 생성해주고 생명주기를 관리해준다.
코드 리팩토링 과제
public boolean validateOrder(Order ordre) {
if (order.getItem().size() == 0) {
log.info("주문 항목이 없습니다.");
return false;
} else {
if (order.getTotalPrice() > 0) {
if (!order.hasCustomerInfo()) {
log.info("사용자 정보가 없습니다.");
return false;
} else {
return true;
}
} else if (!(order.getTotalPrice() > 0)) {
log.info("올바르지 않은 총 가격입니다.");
return false;
}
}
return true;
}변경 후
public boolean validateOrder(Order ordre) {
if (order.isEmpty()) {
log.info("주문 항목이 없습니다.");
return false;
}
if (order.isInvalidTotalPrice()) {
log.info("올바르지 않은 총 가격입니다.");
return false;
}
if (order.hasNotCustomerInfo()) {
log.info("사용자 정보가 없습니다.");
return false;
}
return true;
}'인프런 워밍업 클럽' 카테고리의 다른 글
| [인프런 워밍업 클럽] Mocking과 BDD (0) | 2025.03.26 |
|---|---|
| [인프런 워밍업 클럽] 레이어별 테스트 (0) | 2025.03.25 |