참고도서
|
1. SRP(Single Responsibility Principle, 단일 책임 원칙)
"어떤 클래스를 변경해야 하는 이유는 오직 하나뿐이어야 한다." - 로버트 C. 마틴
하나의 클래스가 수 많은 역할과 책임을 맡아서 수행하고 있고, 그런 클래스에 의존하는 다양한 클래스가 있다고 가정하면,
-> 의존하고 있는 클래스 중 하나에 변화가 생기고
-> 수 많은 역할과 책임을 맡고 있는 클래스에 변화가 생기고
-> 의존하고 있는 다양한 클래스들에게 영향이 미치게 된다.
남자 클래스가 여자친구에 대해 남자친구로서의 역할과 책임,
직장상사에 대해 사원으로서의 역할과 책임을 가지고 있다고 가정하면,
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 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 | class 남자 { // 남자친구 역할과 책임 void 기념일챙기기() { System.out.println("선물 뭐 줄까?"); } // 사원 역할과 책임 void 아부하기() { System.out.println("부장님 사랑합니다"); } } class 여자친구 { // 여자친구의 역할과 책임은 기념일알리기 // 여자친구가 피터팬에게 남자의 역할과 책임 중 하나인 남자친구로서의 // 기념일챙기기를 수행시키기 위해 피터팬에게 기념일이라고 메시지를 날리면 // 피터팬은 남자의 역할과 책임 중 하나인 남자친구로서의 기념일챙겨주기를 수행한다 void 기념일알리기() { 남자 피터팬 = new 남자(); // 여자친구로부터 기념일알리기 메시지를 받아서 // 피터팬은 남자의 역할과 책임 중 하나인 남자친구로서 역할과 책임을 충실히 수행함 피터팬.기념일챙기기(); // 여자친구는 피터팬이 남자의 역할과 책임 중 하나인 사원으로서의 아부하기는 필요없다 // 피터팬.아부하기();를 수행할 수 있으므로 문제가 발생 피터팬.아부하기(); } } class 직장상사 { // 직장상사의 역할과 책임은 사원호출 // 직장상사가 후크에게 남자의 역할과 책임 중 하나인 사원으로서의 // 아부하기를 수행시키기 위해 후크 사원을 호출하는 메시지를 날리면 // 후크는 남자의 역할과 책임 중 하나인 사원으로서의 아부하기를 수행한다 void 사원호출() { 남자 후크 = new 남자(); // 직장상사로부터 사원호출 메시지를 받아서 // 후크는 남자의 역할과 책임 중 하나인 사원으로서 역할과 책임을 충실히 수행함 후크.아부하기(); // 여자친구와 마찬가지의 문제가 발생 후크.기념일챙기기(); } } | cs |
이와 같은 문제가 발생한다. 남자 클래스를 SRP 원칙에 따라 클래스를 분리해야 한다.
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 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 | //남자친구로서의 역할과 책임만 가짐 class 남자친구 { // 남자친의 역할과 책임 void 기념일챙기기() { System.out.println("선물 뭐 줄까?"); } } // 사원으로서의 역할과 책임만 가짐 class 사원 { // 사원의 역할과 책임 void 아부하기() { System.out.println("부장님 사랑합니다."); } } class 여자친구 { // 여자친구의 역할과 책임은 기념일알리기 // 여자친구가 남자친구 역할을 하는 피터팬에게 기념일이라고 메시지를 날리면 // 피터팬은 기념일을 챙겨줘야 하는 남자친구의 역할과 책임을 수행해서 기념일을 챙겨준다 void 기념일알리기() { 남자친구 피터팬 = new 남자친구(); // 여자친구로부터 기념일알리기 메시지를 받아서 // 피터팬은 남자친구로서 역할과 책임을 충실히 수행함 피터팬.기념일챙기기(); // 불가능, 피터팬은 남자친구로서의 역할과 책임은 가지고 있지만 // 사원으로서의 역할과 책임은 가지고 있지 않다 // 피터팬.아부하기(); } } class 직장상사 { // 직장상사의 역할과 책임은 사원호출 // 직장상사가 사원 역할을 하는 후크에게 사원호출이라고 메시지를 날리면 // 후크는 아부해야 하는 사원의 역할과 책임을 수행해서 직장상사에게 아부한다 void 사원호출() { 사원 후크 = new 사원(); // 직장상사로부터 사원호출 메시지를 받아서 // 후크는 사원으로서 역할과 책임을 충실히 수행함 후크.아부하기(); // 불가능, 후크는 사원으로서의 역할과 책임은 가지고 있지만 // 남자친구로서의 역할과 책임은 가지고 있지 않다 // 후크.기념일챙기기(); } } | cs |
이렇게 SRP에 따라 클래스를 분리하면 혹시 발생할 지도 모를 문제를 사전에 차단할 수 있다.
SRP는 클래스뿐만 아니라 속성, 메소드에도 적용된다.
속성과 메소드도 하나의 역할과 책임을 가져야 한다.
극단적인 예로
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 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 | class 건물{ // 건물크기, 건물종류 속성이 하나의 역할과 책임을 지고 있지 않다 String 건물크기; String 건물종류; // 건물크기구하기() 메소드가 하나의 역할과 책임을 지고 있지 않다 void 건물크기구하기(){ // 조건문으로 덕지덕지 도배하게 된다. if(건물종류.equals("단독주택")){ 건물크기 = "전체면적"; System.out.println(건물크기); } if(건물종류.equals("아파트")){ 건물크기 = "층수+동수"; System.out.println(건물크기); } } } class 건물정보{ public static void main(String[] args){ 건물 마이홈 = new 건물(); 마이홈.건물종류 = "단독주택"; 마이홈.건물크기구하기(); 건물 유어홈 = new 건물(); 유어홈.건물종류 = "아파트"; 유어홈.건물크기구하기(); } } // SRP 원칙에 따라 변경 class 건물{ String 단독주택크기; String 아파트크기; void 단독주택크기구하기(){ 단독주택크기 = "전체면적"; System.out.println(단독주택크기); } void 아파트크기구하기(){ 아파트크기 = "층수+동수"; System.out.println(아파트크기); } } class 건물정보{ public static void main(String[] args){ 건물 마이홈 = new 건물(); 마이홈.단독주택크기구하기(); 건물 유어홈 = new 건물(); 유어홈.아파트크기구하기(); } } | cs |
이런 속성과 메소드가 있을 수 있다.
한번 더 리팩토링 하면,
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 | abstract class 건물{ abstract void 건물크기구하기(); } class 단독주택 extends 건물{ void 건물크기구하기(){ System.out.println("전체면적"); } } class 아파트 extends 건물{ void 건물크기구하기(){ System.out.println("층수+동수"); } } class 건물정보{ public static void main(String[] args){ 건물 마이홈 = new 단독주택(); 마이홈.건물크기구하기(); 건물 유어홈 = new 아파트(); 유어홈.건물크기구하기(); } } | cs |
이렇게 설계할 수 있을 것이다.
2. OCP(Open Closed Principle, 개방 폐쇄 원칙)
운전자가 마티즈를 운전하다가 쏘나타로 바꿔 운전하는 경우,
운전자는 수동으로 조작하던 창문개방과 기어조작을 자동으로 조작해야 한다.
운전자에게 변화가 온 것이다.
객체지향 세계에서는 차가 바뀌더라도 운전자에게 변화가 없게 할 수 있다.
이제 운전자는 그냥 자동차를 운전하는 법만 알며 된다.
그 자동차가 마티즈에서 쏘나타로 바뀌더라도 운전자의 조작법에는 변화는 일어나지 않는다.
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 32 33 34 35 36 37 38 39 40 41 42 43 44 45 | interface 자동차{ void 창문개방(); void 기어조작(); } class 마티즈 implements 자동차{ void 창문개방(){ System.out.println("창문개방 수동조작"); } void 기어조작(){ System.out.println("기어 수동조작"); } } class 쏘나타 implements 자동차{ void 창문개방(){ System.out.println("창문개방 자동조작"); } void 기어조작(){ System.out.println("기어 자동조작"); } } class 운전자{ public static void main(String[] args){ // 마티즈 타입의 객체를 만들어서 자동차 역할을 할 수 있는 마이카라고 한다 자동차 마이카 = new 마티즈(); // 자동차 역할을 하는 객체를 가지고 조작 마이카.창문개방(); 마이카.기어조작(); // 쏘나타 타입의 객체를 만들어서 자동차 역할을 할 수 있는 유어카라고 한다 자동차 유어카 = new 쏘나타(); // 자동차 역할을 하는 객체를 가지고 조작 // 자동차의 종류가 바뀌었지만 운전자는 자동차 역할을 할 수 인터페이스의 조작법만 알고 있으면 된다. 유어카.창문개방(); 유어카.기어조작(); } } | cs |
3. LSP(Liskov Substitution Principle, 리스코프 치환 원칙)
4. ISP(Interface Segregation Principle, 인터페이스 분리 원칙)
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 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 | interface 남자친구 { void 기념일챙기기(); } interface 사원 { void 아부하기(); } class 남자 implements 남자친구, 사원 { @Override public void 아부하기() { System.out.println("부장님 사랑합니다."); } @Override public void 기념일챙기기() { System.out.println("선물 뭐 줄까?"); } } class 여자친구 { void 기념일알리기() { // 남자친구의 역할을 할 수 있는 남자 객체를 만들어서 피터팬이라 한다 남자친구 피터팬 = new 남자(); // 피터팬은 남자친구로서 기념일 챙기기 역할을 수행할 수 있다 피터팬.기념일챙기기(); // 피터팬은 남자친구의 역할만 할 수 있는 남자이기 때문에 // 사원으로서의 역할은 할 수 없다. // 피터팬.아부하기(); } } class 직장상사 { void 사원호출() { // 사원 역할을 할 수 있는 남자 객체를 만들어서 후크라 한다 사원 후크 = new 남자(); // 후크는 사원으로서 아부하기 역할을 수행할 수 있다 후크.아부하기(); // 후크는 사원의 역할만 할 수 있는 남자이기 때문에 // 남자친구로서의 역할은 할 수 없다. // 후크.기념일챙기기(); } } |
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 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 | // 조류가 가지는 공통 부분 class 조류 { void 먹이먹기() { System.out.println("냠냠 맛나게 먹는다"); } void 잠자기() { System.out.println("쿨쿨 잘 잔다"); } } // 날 수 있는 조류만 구현하게 한다 interface 날수있는 { void 훨훨난다(); } // 조류의 역할과 기능을 상속, 날수있는 기능은 구현 안함 class 펭귄 extends 조류 { } // 조류의 역할과 기능을 상속, 날수있는 기능을 구현 class 독수리 extends 조류 implements 날수있는 { @Override public void 훨훨난다() { System.out.println("슈우웅 난다"); } } class 조류연구{ public static void main(String[] args) { 펭귄 펭펭 = new 펭귄(); 펭펭.먹이먹기(); 펭펭.잠자기(); // 날 수 없다 // 펭펭.훨훨난다(); 독수리 독독 = new 독수리(); 독독.먹이먹기(); 독독.잠자기(); // 날 수 있다 독독.훨훨난다(); } } | cs |
5. DIP(Dependency Inversion Principle, 의존 역전 원칙)
위의 경우 자동차가 스노우타이어에 의존하고 있다.
만약 겨울이 끝나 스노우 타이어를 일반 타이어로 바꾸어야 한다면,
스노우 타이어에 의존하고 있는 자동차가 일반 타이어에 의존하도록 바꾸어 주어야 한다.
즉, 자주 변경되는 구체 클래스에 의존해서는 안 된다는 것이다.
타이어가 바뀌어도 자동차에 변화가 없게 할려면,
OCP 를 이용해서 바꾸면 된다.
이제 자동차는 스노우타이어와 일반타이어에 의존적이지 않다.
대신 무엇에도 의존하지 않던 스노우 타이어가 타이어 인터페이스에 의존적이게 되었다.
의존의 방향이 역전된 것이다.
그리고 자동차는 자신보다 변하기 쉬운 스노우타이어에 의존하던 관계를
중간에 추상화된 타이어 인터페이스를 추가해 두고 의존 관계를 역전시키고 있다.
스프링은 의존 관계를 스프링 컨테이너를 이용해 역전시켜 DIP를 극한까지 구현한 프레임워크이다.
'Programming > OOP' 카테고리의 다른 글
[Design Pattern] Factory Method Pattern, 팩토리 메서드 패턴 (0) | 2022.04.25 |
---|---|
[Design Pattern] Decorator Pattern, 데코레이터 패턴 (0) | 2022.04.21 |
[Design Pattern] Observer Pattern, 옵저버 패턴 (0) | 2022.04.19 |
[Design Pattern] Strategy Pattern, 전략 패턴 (0) | 2022.04.18 |
OOP(Object Oriented Programming) 기본 개념 (0) | 2017.08.04 |
댓글