티스토리 뷰
객체 지향 프로그래밍을 하면서 지켜야 하는 5대 원칙.
https://mangkyu.tistory.com/194
[OOP] 객체지향 프로그래밍의 5가지 설계 원칙, 실무 코드로 살펴보는 SOLID
이번에는 객체 지향 프로그래밍의 5가지 핵심 원칙인 SOLID에 대해 알아보고자 합니다. 실제로 애플리케이션을 개발할 때 어떻게 적용할 수 있을지 구체적인 예시를 들어 살펴보고자 합니다. 아
mangkyu.tistory.com
SRP, Single Responsibility Principle, 단일 책임 원칙
모듈이 변경되는 이유가 한가지여야 함.
해당 모듈이 여러 대상 또는 액터들에 대해 책임을 가져서는 안되고, 오직 하나의 액터에 대해서만 책임을 져야한다.
만약 어떤 모듈이 여러 액터에 대해 책임을 갖고 있다면 여러 액터들로부터 변경에 대한 요구가 올수 있으므로 해당 모듈을 수정해야 하는 이유가 여러개가 될수 있다.
반면 어떤 클래스가 단 하나의 책임 만을 갖는다면 특정 액터로부터 변경을 특정할수 있으므로 해당 클래스를 변경해야 하는 이유와 시점이 명확해진다.
@Service
@RequiredArgsConstructor
public class UserService {
private final UserRepository userRepository;
public void addUser(final String email, final String pw) {
final StringBuilder sb = new StringBuilder();
for(byte b : pw.getBytes(StandardCharsets.UTF_8)) {
sb.append(Integer.toString((b & 0xff) + 0x100, 16).substring(1));
}
final String encryptedPassword = sb.toString();
final User user = User.builder()
.email(email)
.pw(encryptedPassword).build();
userRepository.save(user);
}
}
위의 UserService 의 사용자 추가 로직에는 다음과 같은 다양한 액터로 부터 변경이 발생할수 있다.
- 기획팀 : 사용자를 추가할때 역할에 대한 정의가 필요하다
- 보안팀 : 사용자의 비밀번호 암호화 방식에 개선이 필요하다
이러한 문제가 발생하는것은 UserService 가 여러 액터로부터 단 하나의 책임을 갖고있지 못하기 때문이다.
이를 위해 비밀번호 암호화에 대한 책임을 분리해야한다.
// 비밀번호 암호화를 책임지는 클래스
@Component
public class SimplePasswordEncoder {
public void encryptPassword(final String pw) {
final StringBuilder sb = new StringBuilder();
for(byte b : pw.getBytes(StandardCharsets.UTF_8)) {
sb.append(Integer.toString((b & 0xff) + 0x100, 16).substring(1));
}
return sb.toString();
}
}
@Service
@RequiredArgsConstructor
public class UserService {
private final UserRepository userRepository;
private final SimplePasswordEncoder passwordEncoder;
public void addUser(final String email, final String pw) {
final String encryptedPassword = passwordEncoder.encryptPassword(pw);
final User user = User.builder()
.email(email)
.pw(encryptedPassword).build();
userRepository.save(user);
}
}
단일 책임 원칙을 제대로 지키면 변경이 필요할때 수정할 대상이 명확해진다.
단일 책임 원칙을 적용하여 적절하게 책임과 관심이 다른 코드를 분리하고,
서로 영향을 주지 않도록 추상화함으로서 애플리케이션 변화에 쉽게 대응할수 있다.
OCP, Open Closed Principle, 개방 폐쇄 원칙
- 확장에는 열려있으나 변경에는 닫혀 있어야 한다
- 다형성을 활용하면 가능하다. (인터페이스 활용)
public class MemberService {
private MemberRepository memberRepository = new MemoryMemberRepository();
}
다형성을 지원하기 위해 MemberRepository 를 추상화했다.
그런데 만약에 구현체 JDBCMemberRepository 로 바꾸고 싶다면 결국 코드는 아래와 같이 수정되어야 한다.
public class MemberService {
private MemberRepository memberRepository = new JDBCMemberRepository();
}
이러면 변경에는 닫혀있어야 한다는 Open Closed Principle 에 위배되는것 아닌가?
그렇다.
이를 해결하기 위해서는 구현체가 MemberService 에서 만들어지지 않는 아래 그림이 되어야한다.
public class MemberService {
private MemberRepository memberRepository;
// 생성자
public MemberService(MemberRepository memberRepoistory) {
this.memberRepository = memberRepository;
}
}
그런데 이렇게 되면 구현체가 없으니 당연히 컴파일때 에러가 난다.
이걸 해결하기 위해서는 MemberService 객체가 만들어질때 아래와 같이 MemberRepository 의 구현체가 주입이 되어야한다.
public static void main(String[] args) {
MemberRepository jdbcMemberRepository = new JDBCMemberRepository();
MemberService memberService = new MemberService(jdbcMemberRepository);
}
그리고 보통 스프링을 사용한다면 스프링이 이런 DI(Dependency Injection) 을 대신 해주는 것이다.
LSP, Liskov substitution priciple, 리스코프 치환 원칙
- 하위 타입은 상위 타입을 대체할수 있어야 한다
- 클라이언트의 관점에서 부모 클래스와 자식 클래스의 행동이 호환되어야 한다
예를들어 Rectangle(사각형) 클래스가 있고, Square(정사각형) 클래스는 사각형의 자식이다.
public class Rectangle {
private int width, height;
public int getArea() {
return width * height;
}
}
public class Square extends Rectangle {
public Square(int size) {
super(size, size);
}
@Override
public void setWidth(int width) {
super.setWidth(width);
super.setHeight(width);
}
@Override
public void setHeight(int height) {
super.setWidth(height);
super.setHeight(height);
}
}
아래와 같이 사각형의 크기를 변경하는 메소드를 보자.
public void resize(Rectangle rectangle, int width, int height) {
rectangle.setWidth(width);
rectangle.setHeight(height);
}
Square 는 Rectangle 의 자식이므로 resize() 메소드에 전달될수 있다.
Rectangle rectangle = new Square();
resize(rectangle, 100, 150);
위와 같이 Square 객체의 너비, 높이를 각각 100,150 으로 변경한다면 컴파일 에러는 나지 않는다.
하지만 논리적으로 정사각형이 너비=100, 높이=150을 갖게된다.
이런 케이스가 리스코프 치환 원칙을 위반하는 경우이다.
리스코프 치환 원칙이 성립한다는 것은 자식 클래스(Square) 가 부모 클래스(Rectangle) 대신 사용할수 있어야 한다.
위의 경우 Square 는 resize 를 호출하지 못하게 하는 방법이 있다.
ISP, Interface segregation principle, 인터페이스 분리 원칙
- 범용적인 인터페이스 보다는 클라이언트가 실제로 사용하는 인터페이스를 만들어야 한다
- 인터페이스를 사용에 맞게 끔 분리해야 한다
- 만약 인터페이스의 추상 메서드들을 범용적으로 이것저것 구현한다면, 그 인터페이스를 상속받는 클래스는 자신이 사용하지 않는 인터페이스마저 억지로 구현해야 하는 상황이 올 수도 있다.
위와 같이 Pet 인터페이스를 더 잘게 나눔으로서, 각 애완동물 클래스의 역할과 맞게 상속 시켜야 한다.
-> 클래스의 기능을 쉽게 파악, 유연하게 객체의 기능을 확장하거나 수정 가능.
DIP, Dependency inversion princople, 의존관계 역전 원칙
- 프로그래머는 '추상화에 의존해야지, 구체화에 의존하면 안된다'
- 구현 클래스에 의존하지말고, 인터페이스에 의존하라
- 클라이언트가 구현체에 의존하게 되면, 변경이 어려워진다
출처:
https://mangkyu.tistory.com/194
'CS 정리 > Spring' 카테고리의 다른 글
의존관계 주입 방법들 (0) | 2023.11.17 |
---|---|
컴포넌트 스캔 (0) | 2023.11.17 |
싱글톤 컨테이너 (0) | 2023.11.16 |
스프링 컨테이너와 스프링 빈 (0) | 2023.11.16 |
Spring, 단계적으로 OCP가 어떻게 지켜지는지 (0) | 2023.11.16 |
- Total
- Today
- Yesterday
- graph
- db
- C++
- DP
- BFS
- floyd warshall
- Python
- back tracking
- greedy
- 자료구조
- Stack
- recursion
- C
- two pointer
- Spring
- CSS
- Brute Force
- Implementation
- MVC
- Kruskal
- Dijkstra
- priority queue
- dfs
- binary search
- Unity
- permutation
- 이분탐색
- 재귀
- 조합
- Tree
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |