SOLID 원칙

타미·2020년 10월 20일
0

Hello CS

목록 보기
8/8
post-thumbnail

[S]ingle Responsibility Principle

단일 책임의 원칙

객체는 단 한 개의 책임을 가져야 한다.

if (!단일 책임의 원칙)

변화가 생겼을 때 영향을 주는 부분이 많아진다.

하나의 객체가 여러가지 책임을 가지고 있다고 해도, 가만히 있으면 별다른 문제가 생기지는 않는다. 🥨변화가 있을 때 문제가 생긴다.
하나의 객체에 a,b,c 3가지 책임이 있을 때 a가 변화했을 때 b,c에게 까지 영향을 준다.

불필요한 의존성

하나의 객체가 DB 접근도 하고, View에 보여주기도 한다고 해보자.
그러면 그 객체로 DB 접근만 하고 싶을 때에도 View에 보여주는 의존성이 포함된다.

위험 시그널 : 서로 다른 이유로 변경이 생길 때

'서로 다른 이유'라는 말이 좀 헷갈렸다. 서로 다른 이유보다는 서로 다른 책임이 나에게 더 와닿았다.
DB 접근을 수정한다는 이유로, View에 보여주기를 수정한다는 이유로..
이런 식으로 다른 이유로 "🥨변경"이 생기면 단일책임원칙을 위배하고 있다는 시그널이다.

단일책임원칙은 변경과 관련이 크다.


[O] Open Closed Priciple

개방 폐쇄 원칙

기능은 확장할 수 있고 (개방) | 사용하는 쪽에서는 변경이 없어야 한다. (폐쇄)

이때, 기능이 변경이 아니라 확장이다. 🥨다형성과 관련이 깊다.
기능 추가시 🥨유연하게 추가할 수 있다.


🥨추상화 & 🥨다형성

  • 그렌저라는 자동차가 하나 더 추가되는 기능은 쉽다. (개방)
  • 운전자인 사용자쪽에서는 변경이 없다. (폐쇄)

이것은 곧 상속으로 연결된다.
상속하면 일부 필요한 구현은 오버라이딩할 수 있지만, 외부 사용하는 쪽에서는 변경이 없으니까!

위험 시그널 : 다운캐스팅, 분기

if (car isInstanceOf Matiz) {
    
}
if (car == Matiz) {
    
} else if (car == Sonata) {
    
}

개방폐쇄원칙에서 폐쇄가 위배되고 있다.


[L] Liskov Substitution Priciple

리스코프 치환 원칙

상위 Type의 객체를 하위 Type으로 교체해도 문제가 없다.

외부 Client 입장에서 상위 Type 개념에 맞게 사용했지만, 그게 하위 Type에는 적용되지 않을 수 있다.

하위 Type이 명세에서 벗어난 동작을 할 때

하위 Type이 상위 Type을 보고서는 예측할 수 없는 행동을 하는 것이다.

  • 명세에서 벗어난 값을 return할 때
  • 명세에서 벗어난 기능을 수행할 때
  • 명세에서 벗어난 예외를 발생할 때

(예)

InputStream : 데이터 없을 때 return -1 
MyInputStream : 데이터 없을 때 return 0
    
while (inputStream.read() != -1) {
    // 여기에 MyInputStream이 온다면 무한 loop
}

(예) 잘못된 상속 관계

class Square extends Rectangle {

}

사용자 입장에서는 Rectangle - 직사각형을 보고, width = height * 2라는 메서드를 만들 수 있다. 하지만 이게 Square에서는 적용되지 않는다.
개념적으로 정사각형->직사각형은 상속 관계이지만, 실제로는 적절하지 않은 상속관계

(예)

class NoDiscountItem extends Item {

}

사용하는 쪽에서는 Item만 보고 할인 로직을 작성할 수 있다.

void discount (Item item) {
	item.updatePrice (item.getPrice * 0.8);
}

그럼 사용하는 쪽에서는 NoDiscountItem인지 type 체크를 해줘야 한다.

  • 리스코프 치환원칙이 위배되면 자연스럽게 개방폐쇄의 원칙이 위배된다.
  • isDiscountAvailable() 로 변경 가능한 부분을 추상화한다.

[I] Interface Segregation Principle

인터페이스 분리 원칙

클라이언트가 사용하지 않는 인터페이스에 변경이 발생하더라도 영향을 받으면 안된다.

처음에 클라이언트가 아니라 구현체 관점에서 잘못 이해했다.
예를 들어

Dog extends Animal {
	void fly();
}

당연히 이렇게 안하잖아! 이게 왜 원칙이야;🤔라고 생각함.

구현체가 사용하지 않는 인터페이스를 가지고 있다는 게 아니라 사용자 입장에서 인터페이스를 분리하라는 의미다.


클라이언트에서 인터페이스를 분리해야 한다.

하나의 거대한 구현체(객체)를 클라이언트 입장에서 interface를 분리한다.

User1
  Printer printer = new 복합기();
  printer.print();
  
User2
  Fax fax = new 복합기();
  fax.fax();

그러면 fax 관련 interface에 변경이 생겨도 printer를 사용하는 user1는 아무런 영향을 받지 않는다. :)


[D] Dependency Inversion Principle

의존역전의 원칙

고수준 모듈은 저수준 모듈의 구현에 의존해서는 안 된다. 저수준 모듈이 고수준 모듈에서 정의한 추상타입에 의존해야 한다.
말이 진짜 어렵당

의존관계를 맺을 때 변화하기 쉬운 것 또는 자주 변화하는 것보다는 변화하기 어려운 것, 거의 변화가 없는 것에 의존하라는 원칙

고수준 모듈

class Notification {
	public void send() { 
		_email.SendEmail();
        _sms.SendSMS();
   }
}        

이런 식으로 실제 구현체를 사용하여 의미있는 단일 기능을 제공한다.

저수준 모듈
실제 구현체

public class SMS {

}

저수준 모듈(구현체)를 추상화시켜서, 고수준 모듈 (사용하는 쪽)에는 변경없이 실제 구현체를 변경할 수 있게 한다.

interface Message {}
class SMS implements Message;

class Noticitaion {
	public void send() {
    	for (Messgae message : List<Message>) {
        	message.send();
        }
    }
}

이런 식으로 수정한다는 것이다.

  • 소스 코드에서는 직접적인 의존을 제거했지만 런타임에서는 의존한다.
profile
IT's 호기심 천국

0개의 댓글