일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- DB
- javascript
- HTTP
- 로그인
- Repository
- di
- 라이프 사이클
- Kotlin
- Java
- Vue.js
- vuex
- Security
- 싱글톤
- 프로토타입
- VUE
- JPA
- thymeleaf
- dependency injection
- cache
- Spring
- Excel
- 캐시
- Singleton
- HTTP 메서드
- Setter
- vue-cli
- BEAN
- Stateless
- js
- 의존성 주입
- Today
- Total
jhhan의 블로그
객체지향 설계의 원칙 5가지 본문
이번 포스트에서는 객체지향 설계의 원칙 5가지에 대해 알아보겠습니다.
(이 원칙은 면접에서도 물어볼 수 있다고 하니 알아두면 유용할 것 같습니다.)
클린 코드로 유명한 '로버트 마틴' 이라는 사람이 좋은 객체 지향 설계의 5가지 원칙
-> 기존에 있던 내용들을 알아보기 쉽게 정리한 것이라고 합니다.
줄여서 SOLID 라고 합니다.
- SRP: 단일 책임 원칙(Single Responsibility Principle)
- OCP: 개방-폐쇄 원칙(Open/Closed Principle)
- LSP: 리스코프 치환 원칙(Liskov Substitution Principle)
- ISP: 인터페이스 분리 원칙(Interface Segregation Principle)
- DIP: 의존관계 역전 원칙(Dependency Inversion Principle)
이제 이것에 대해 하나씩 알아보겠습니다.
1. SRP: 단일 책임 원칙(Single Responsibility Principle)
'하나의 클래스는 하나의 책임만 가져야 한다.' -> 라고 말할 수 있습니다.
하지만, 하나의 책임이라는 말은 너무 모호합니다.
코드 관점에서 더 이해하려면 -> '변경'을 했을 때, 파급효과가 적으면 SRP를 잘 지켰다고 할 수 있습니다.
ex) A라는 Class를 하나 변경했습니다. -> B Class, C Controller, 프론트에 해당하는 D파일, E 라는 DB... 등등 바꿔야 할 부분이 많다면 SRP가 제대로 지켜진 것이 아닙니다.
ex) A라는 Class를 하나 변경했습니다. -> A Class 이외에는 변경할 부분이 없다면 SRP가 제대로 지켜졌다고 할 수 있습니다.
제가 이전에 만들어봤던 컨트롤러 파일입니다.(이전에 올린 포스트에 이게 있을 수도 있을 것 같습니다.. 이전에 올렸던 포스트의 코딩 내용을 그대로 가져왔거든요.)
만약 저 코드에서 create이라는 함수를 변경하는데 ->
- 다른 클래스 OR 모델들이 같이 변경되야 함 -> SRP가 제대로 안 지켜짐
- 다른 클래스 OR 모델들의 변경이 필요없음 -> SRP가 제대로 지켜짐
이라고 볼 수 있습니다.
(...어쩌면 예시가 잘못 됐을 수도 있지만,, 그래도 이런 방법으로 접근하면 됩니다.)
2. OCP: 개방-폐쇄 원칙(Open/Closed Principle)
[5가지 원칙 중에서 가장 중요하다고 볼 수 있습니다.]
'소프트웨어 요소는 확장에는 열려있으나, 변경에는 닫혀 있어야 한다.' -> 라고 직관적으로 말할 수 있습니다.
-> 근데 이 말에는 모순이 있는 듯 합니다.
코드 확장을 하기 위해서는 당연히 기존 코드의 변경이 있을 수 밖에 없습니다.
그래서 다형성의 측면에서 생각해봐야 합니다.
- 인터페이스 1개가 존재
- 인터페이스를 구현한 A클래스 -> 기능 구현
- 인터페이스를 구현한 B클래스 -> 기능 구현
- 이 때 B클래스를 추가(확장)하는 것이므로 OCP를 만족하려면 코드의 변경이 없어야 한다.
- 만약 변경을 해야한다면 OCP를 제대로 지켜지지 않은 것
이전에 짰던 코드를 살펴보겠습니다.
@Bean
public MemberRepository memberRepository() {
return new MemoryMemberRepository();
return new JdbcMemberRepository(dataSource);
return new JdbcTemplateMemberRepository(dataSource);
return new JpaMemberRepository(em);
}
이전에 SpringConfig라는 클래스를 만들고 그 안에 들어있던 일부입니다.
MemberRepository라는 인터페이스를 만들었었고, 그 구현체로는 Memory~, Jdbc~ JdbcTemplate~, Jpa~로 4개를 만들었습니다.
그런데 return으로 각각 다 쓰고 있죠?
이는 제가 클래스를 하나 만들 때마다 저렇게 추가해서 사용했다는 것입니다.
즉, OCP를 제대로 지키지 않고 코딩을 했다는 것입니다...(머, 그럴수도 있죠.)
여기서 저는
- MemberRepository memberRepository = new MemoryMemberRepository(); // 기존
- MemberRepository memberRepository = new JdbcMemberRepository(); // 변경
이런 식으로 사용한 것 같습니다.
이렇게 코드를 짜는 것은 클라이언트가 구현 클래스를 직접 선택하는 코드입니다.
OCP를 제대로 지키려면 이렇게 짜면 안됩니다. ㅎ
제대로 지켜지려면 -> 객체 생성, 연관관계, 설정자 등이 필요합니다.
-> 그래서 스프링에서는 IOC , DI 등을 제공합니다.
저도 아직 공부하는 중이어서 이 코드를 어떻게 바꾸는지는 아직 모르겠습니다...
(알게 되면 다시 포스트 작성을 해보겠습니다.)
어쨌든 OCP는 확장(추가)은 가능하나, 변경은 하지 말 것 이라는 것을 기억하셔야 합니다.
3. LSP: 리스코프 치환 원칙(Liskov Substitution Principle)
프로그램의 객체는 프로그램의 정확성을 깨뜨리지 않으면서 하위 타입의 인스턴스로 바꿀 수 있어야 한다. -> 라는 정의를 가집니다.
말만 보면 너무 어렵습니다. 좀 더 쉽게 이해해 보겠습니다.
- 자동차 객체가 있다고 가정해보겠습니다.
- 그리고 이 객체는 악셀 이라는 함수가 있다고 해봅니다.
- 악셀을 실행하면 현재 속력에서 '플러스'가 되게 실행이 될 것입니다.
- 그런데 !! 악셀이라는 함수가 실행될 때, 속력이 줄어들게 코드를 짤 수 있습니다.
- 컴파일 상에서도 문제가 없겠죠? 단순히 속력을 줄이는 것 뿐이니까요..
- 하지만, 일반적이라면 악셀이 실행되면 속력이 증가된다고 생각합니다.
- LSP는 이런 기능들을 보장하게 해주는 원칙입니다.
- 그래서 악셀 이라는 함수는 무조건 속력이 증가해야 합니다.
- 속력이 감소하게 된다면 LSP를 위반하는 것입니다.
인터페이스에 기능이 하나 있고, 구현체가 이 기능을 믿고 사용하려면 -> LSP를 지켜야 합니다.
4. ISP: 인터페이스 분리 원칙(Interface Segregation Principle)
인터페이스 여러 개가 범용 인터페이스 하나보다 낫다 -> 라고 생각하시면 됩니다.
하나의 인터페이스 안에 다양한 기능보다는 인터페이스를 여러개로 나누어서 기능을 분리하는게 낫다는 것입니다.
- 자동차 인터페이스가 있다고 해봅니다.
- 운전 인터페이스, 정비 인터페이스로 분리를 해봅니다.
- 그러면 사용자 클라이언트는 운전자 클라이언트, 정비사 클라이언트로 분리가 가능해집니다.
- 이렇게 되면 정비 인터페이스가 변하더라도 정비사 클라이언트에만 영향을 주지, 운전자 클라이언트에는 영향을 주지 않습니다.
ISP를 잘 지켜서 코드를 짠다면 -> 인터페이스가 조금 더 명확해지고, 대체 가능성이 높아집니다.
그리고 인터페이스가 여러 개로 나뉘게 되면, 그만큼 기능들도 적게 들어가기 때문에, 구현체를 만들 때 기능 구현도 줄어들게 될 것입니다!
실제로 스프링 코드를 분석하면 정말 잘게잘게 인터페이스가 쪼개져 있다고 하네요! 시간이 된다면 확인해 보는 것도 괜찮을 것 같습니다!
5. DIP: 의존관계 역전 원칙(Dependency Inversion Principle)
코드는 추상화에 의존해야하고, 구체화에 의존하면 안된다 -> 라고 말할 수 있습니다.
즉, 코드가 구현 클래스가 아닌 인터페이스에 의존해야 한다는 뜻입니다.
이 원칙 역시 2번째 원칙만큼 중요합니다. 2번째 원칙과 관련된 부분이 좀 있기도 해서 그럽니다.
// MemberRepository mr = new MemoryMemberRepository();
MemberRepository mr = new JdbcMemberRepository();
이 코드를 보면,
MemberRepository 라는 인터페이스에만 의존해야 하고,
MemoryMemberRepository 혹은 JdbcMemberRepository에 의존하면 안됩니다.
DIP를 잘 지킨다면 인터페이스에만 의존하기 때문에 구현체를 유연하게 변경이 가능합니다.
하지만 위의 코드는 인터페이스와 구현 클래스 동시에 의존하고 있습니다.
그래서 구현체가 변경이 될 때 코드 변경이 힘들게 됩니다.. 위의 코드는 DIP가 지켜지지 않은 것이죠...
이렇게 객체지향의 설계 5가지 원칙을 살펴봤습니다.
이 5가지 원칙 중 핵심적인 2가지는 2번째 원칙과 5번째 원칙입니다.
객체 지향의 핵심으로 다형성이 있긴 하지만, 다형성 만으로는 OCP, DIP를 지킬 수 없습니다.
스프링이 이 부분을 보완해 줄 수 있다고 합니다.
관련 글이 올라간다면 이제는 Spring 탭에 올라갈 것으로 보이네요.
공부 힘드네요... ㅎㅎㅎ
이렇게 마치겠습니다.
'기타' 카테고리의 다른 글
사이드 프로젝트...? (0) | 2024.01.18 |
---|---|
DBeaver 설치 + MariaDB 연결 (4) | 2021.04.12 |
GitHub 과 Slack 연동 (0) | 2020.07.12 |