객체지향 설계 원칙 SOLID 원칙에 대해 알아보자.

객체지향 설계 원칙 SOLID 원칙에 대해 알아보자.

February 25, 2022

오늘의 포스팅은 정보처리기사 공부 중 포스팅 하면 좋을 것 같은 주제가 있어 가져왔어요.

바로 객체지향 설계 원칙입니다. 각 원칙의 앞글자를 따서 SOLID 라고도 불립니다.

해당 원칙은 여러 객체지향 언어 뿐만 아니라 웹 프레임워크 Spring에서도 핵심원리로써 작용하기 때문에 프로그래머가 알아두면 매우 좋습니다.

면접 때도 좋죠

1. 단일 책임원칙 (SRP, Single Responsiblity Principle)

클래스를 설계할 때 하나의 클래스는 단 하나의 책임만 가져야 한다는 원칙입니다.

책임이라는 말이 조금 모호할 수 있지만, 단순하게 설명하면, 기능의 변경이나, 수정사항이 생겼을 때 영향을 받는 범위라고 생각하면 쉬워요.

일반적으로 책임이라는 말은 누가 잘못된 행위를 했을 때 이를 바로잡기 위해 “누가 책임을 져야지!” 라고 자주 쓰잖아요. 그런거랑 같다고 보면 됩니다.

단일 책임 원칙은 해당 책임에 있어 하나의 사물을 담당하는 클래스는 하나의 책임을 져야한다는 것이에요.

프로그래머라면 당연지사 클래스라는 말은 한번쯤 들어보셨죠? 클래스란 하나의 사물의 동일한 속성이나 행위의 집합을 말해요.

이는 특정 클래스가 두 개 이상의 책임을 지고 있다면 문제가 되기 떄문이죠.

예를 들어볼까요?

차량 클래스에 차량 판매정보와, 차량의 동작 원리를 넣었다고 했을 때, 차량 가격을 변경하게 되면 차량 동작원리를 참조하는 클래스에게도 필연적으로 영향을 미치게 됩니다.

또한 차량 동작원리를 사용하는 클래스가 있다고 했을 때 차량의 동작에 쓸모 없는 정보(판매정보)가 부가적으로 포함되어있기 때문에 불필요한 요소를 떠안게 되겠죠?

이는 누가 보더라도 좋은 설계라 할 수 없습니다.

이런 불상사를 방지하기 위해 SRP는 한 클래스는 하나의 책임만을 갖도록 명시하고 있는 것이죠.

2. 개방 폐쇄원칙(OCP, Open Close Principle)

개방 폐쇄원칙(이하, OCP)는 소프트웨어의 클래스, 모듈, 메소드 등의 확장에는 열려있어야 하며, 수정에는 닫혀있어야 한다는 원칙입니다.

해당 원칙에 가장 부합하기 위해 파일(file)에 특정 내용(content)을 쓰는 작업을 예로 들어 설명해보겠습니다.

아래 코드는 실제로 동작하는 코드가 아닌 참고용입니다.

void fileOneWrite(String content){
  File file = new File("./one.txt");
  file.write(content);
}

File클래스의 멤버함수에 매개변수로 받은 문자열을 쓰는 함수가 있다고 가정하였을 때 해당 함수처럼 설계하면 one.txt파일에 매개변수 content에 대한 내용을 쓰게 됩니다. 근데 만약 two.txt파일에도 쓰고 싶으면 어떨까요?

void fileOneWrite(String content){
  File file = new File("./one.txt");
  file.write(content);
}
void fileTwoWrite(String content){
  File file = new File("./two.txt");
  file.write(content);
}
void fileThreeWrite(String content){
  File file = new File("./three.txt");
  file.write(content);
}

위처럼 메서드를 추가하여 새로운 함수를 정의하거나, 기존의 함수를 바꿔야 합니다.

하지만 개방 폐쇄원칙을 따라 설계 시 확장을 고려했다면 이런식으로 설계할 수 있었겠죠

void fileWrite(String filePath, String content){
  File file = new File(filePath);
  file.write(content);
}

이처럼 인터페이스나, 상속관계, 예시에서 나온 매개변수 등을 활용하여 확장성을 열어두고 변경의 가능성을 닫는 것, 이것이 OCP입니다.

3. 리스코프 치환 원칙 (LSP, Liskov Substitution Principle)

프로그램의 객체는 프로그램의 정확성을 깨뜨리지 않으면서 하위타입의 인스턴트로 바꿀 수 있어야 한다는 원칙입니다.

이는 특히 다형성과 관련이 있습니다.

void sell(Item item){
  //판매하는 로직
}

이런 함수가 있을 때 Item 인터페이스나, 클래스를 상속받는 하위타입의 인스턴스라면 sell(apple) 와 같이 호출했을 때 문제없이 바꾸어져야 한다는 말인 것이죠!

4. 인터페이스 분리원칙 (ISP, Interface Segregation Principle)

클라이언트가 자신이 이용하지 않는 메서드에 의존하지 않아야 한다는 원칙입니다.

의존성은 저번 포스팅에 의존성 주입 파트에서도 알아보았는데, 그 의존이 맞습니다.

달리 말하면 클라이언트는 자신이 이용하는 메서드에만 의존해야 한다는 것이겠죠.

이를 조금 돌려서 생각하면 여러 클라이언트가 사용하는 인터페이스가 있을 때, 사용하는 인터페이스의 메서드 중, 일부 클라이언트들이 사용하지 않는 메서드가 존재할 경우, 해당 메서드를 포함한 인터페이스를 새롭게 만들어 인터페이스를 분리하는 것을 고려해야 한다는 것입니다.

예를 들어 팬더 일랙기타삼익 통기타 모두 기타 인터페이스에 있지만, 팬더 일렉기타엠프연결() 메서드로 인해 기타 인터페이스에 엠프연결() 메서드가 존재한다면, 삼익 기타 클라이언트는 기타 인터페이스를 상속받고 있기 때문에 자신과 상관없는 앰프 연결() 메서드를 구현하여야 합니다.

이런 비효율 적인 부분을 방지하기 위해서 기타 인터페이스를 통기타 인터페이스, 일렉기타 인터페이스로 나누어 각자 상속받게 하는 것이 좀 더 맞는 방법이라 할 수 있습니다.

5. 의존관계 역전 원칙(DIP, Dependency Inversion Principle)

소프트웨어 모듈들을 분리하는 특정 형식을 지칭하는 원칙입니다.

해당 원칙은 다음과 같은 내용을 담고 있습니다.

  1. 상위 모듈은 하위 모듈에 의존해선 안된다. 상위 모듈과 하위 모듈 모두 추상화에 의존하여야 한다.
  2. 추상화는 세부사항에 의존해서는 안된다. 세부 사항이 추상화에 의존하여야 한다.

해당 원칙은 상위와 하위 객체가 모두 동일한 추상화에 의존해야 한다는 객체지향 설계의 대원칙을 제공합니다.

이렇게 말하면 잘 이해가 되지 않죠? 예를 들어 좀 더 쉽게 생각해보죠.

image

저는 기계식 키보드를 사용하고 있으니 저라는 객체는 기계식 키보드를 포함하고 있는 객체(의존하는 객체)라고 볼 수 있습니다. 하지만, 저는 항상 기계식 키보드를 사용할까요? 아니죠. 상황에 따라서 노트북 키보드를, 멤브레인 키보드를 사용할 수도 있을 것입니다. 따라서 해당 객체들을 일반화한 키보드라는 인터페이스를 중간에 만들어서 이를 의존하는 것이 더 좋을 것입니다. 아래 그림처럼요!

image

마치며

  • 오늘은 이렇게 객체지향 설계의 5가지 원칙 SOLID에 대해 알아보았습니다.
  • 이해하기 조금 난해할 수 있지만, 코딩을 하면서 분명 필요한 부분이니, 무리하게 외우기보다는 실제 코딩을 하시면서 몸에 익히시는 것을 권장합니다.
  • 그럼 다음 포스팅에서 만나요!