객체지향 설계의 원칙 SOLID

jjong_gang·2022년 3월 6일
1

시작

컴퓨터 프로그래밍을 할 때 지켜야 할 객체지향 설계의 5가지 원칙, SOLID에 대해 공부한 내용을 기록합니다.

정의

SOLID란, 클린코드로 유명한 로버트마틴이 2000년대 초반에 명명한 원칙으로, 각각의 문자는 하나의 원칙을 가리킵니다.

세부

SRP : 단일 책임 원칙 (Single Responsibility Principle)
OCP : 개방-폐쇄 원칙 (Open/Closed Principle)
LSP : 리스코프 치환 원칙 (Liskov Substitution Principle)
ISP : 인터페이스 분리 원칙 (Interface Segragation Principle)
DIP : 의존관계 역전 원칙 (Dependency Inversion Principle)

각각의 원칙에 대해 좀더 자세하게 알아보겠습니다!

SRP (Single Responsibility Principle)

연필촉으로 쓸 수 있지만, 지울 수 없습니다.

첫 번째로 SOLID 중 S에 해당하는 SRP 단일 책임 원칙입니다.
객체지향 프로그래밍에서 한 클래스는 하나의 책임만 가져야 합니다.그리고, 그 클래스는 책임을 완전히 캡슐화해야 합니댜.
여기서 하나의 책임이란 무엇을 의미하는 걸까요?
객체지향 프로그래밍에서 책임의 기준은 변경입니다. 어떤 클래스나 모듈은 변경하려는 단 하나 이유만을 가져야 합니다.
보고서를 편집하고 출력하는 경우를 예로 들었을 때, 이 보고서는 두 가지 이유로 변경되는 상황이 발생할 수 있습니다. 첫째로 보고서 내용의 변경에 의한 것일 수 있고, 두 번째로는 보고서의 형식 때문에 변경에 의해 변경될 수 있습니다. 이렇게 두 가지 이유가 하나의 보고서에 대해 변경할 이유로 존재하는 경우가 SRP를 위반하는 상황이 될 수 있습니다. SRP를 충족시키기 위해선 이 두 책임을 명확하게 분리해야합니다.

OCP (Open/Closed Principle)

카드지갑에 3개의 카드가 들어가도록 확장하면서 지갑의 디자인에 불필요한 변경은 없어야합니다.

두 번째로 SOLID에서 O에 해당하는 개방-폐쇄 원칙입니다.
소프트웨어 개체(클래스, 모듈, 함수 등)는 확장에 대해 열려있어야 하고, 변경에 대해서는 닫혀 있어야합니다. 소프트웨어의 개발 과정에서 사용되는 여러개의 모듈 중 하나의 모듈에 변경을 해야하는 경우 그 모듈과 연결된 다른 모든 모듈을 하나하나 수정해야한다면, 번거로운 일일 것입니다.
각각의 모듈에 개방-폐쇄 원칙이 잘 적용된다면, 기능을 추가하거나 변경해야 할 때 이미 제대로 동작하고 있던 원래 코드를 변경하지 않아도, 기존의 코드에 새로운 코드를 추가하여 기능의 추가나 변경이 가능합니다.
그런데 변경에 대해 닫혀 있는 상황에, 확장에는 열려 있을 수 있는 걸까? 라는 생각이 들 수 있습니다. 확장을 위해서는 기존의 코드를 변경하는 것이 당연한 것이기 때문이죠. 이럴 때는 객체지향의 특징 중 하나인 다형성을 이용합니다. 인터페이스를 구현하는 새로운 클래스를 하나 만들어서 새로운 기능을 구현할 수 있게 됩니다.
이를 자바 코드로 구현하면 다음과 같습니다.

public class MemberService{
	private MemberRepository memberRepository = new MemoryMemberRepository();
}

위의 코드를 보면, MemberRepository라는 인터페이스를 사용하여 구현해놓은 MemoryMemberRepository 클래스가 있습니다. 그런데, 프로그램을 운영하는 도중에 MemoryMemberRepository를 다른 클래스로 대체해야 하는 상황이 생길 수 있습니다.
이러한 경우에는 단순하게 MemberRepository를 구현하는 클래스를 하나 더 만들어서 원하는 기능을 적용하고, MemberService 클래스 내에 MemberRepository로 다시 선언하면 되는 것입니다.

public class MemberService{
//	private MemberRepository memberRepository = new MemoryMemberRepository();
	private MemberRepository memberRepository = new JdbcMemberRepository();
}

이렇게 되면 MemberService라는 클래스는 기존에 사용하던 memberRepository를 코드의 변형 없이 그대로 사용할 수 있게 되는 것입니다.
이렇게 하여, MemoryMemberRepository에서 JdbcMemberRepository로의 변경이 MemberRepository를 선언하여 사용중인 MemberService 클래스 내의 코드에 어떤 변경도 주지 않으면서, 동시에 기능의 확장까지 이룰 수 있게 됩니다.

하지만, 이러한 방식의 memberRepository 대체는 MemberService라는 클라이언트 코드 내에서 기존 코드를 주석처리하고, 새로운 코드 한줄을 넣어야한다는 점에서는 명확하게 집어 말하면 OCP원칙이 지켜지지 않았다고 할 수 있습니다.
그렇다면 이러한 문제를 어떻게 해결해야 할까요?
이를 해결하기 위해서는 객체를 생성하고, 연관관계를 맺어주는 별도의 조립, 설정자 즉 MemberRepository와 MemberService의 중간에 새로운 클래스가 위치하여 두 클래스의 사이를 관리해줘야합니다. 이를 스프링에서는 Bean을 생성하고 주입하는 방식으로 간단하게 해결합니다.

LSP (Liskov Substitution Principle)

우리는 엑셀을 밟으면 앞으로 나가기를 바랍니다.

세 번째로 SOLID의 L에 해당하는 리스코프 치환 원칙입니다.
리스코프 치환 원칙은 바바라 리스코프가 자료 추상화와 계층 (Data abstraction and hierarchy)이라는 제목으로 기조연설을 한 1987년 컨퍼런스에서 처음 소개한 내용입니다.

컴퓨터 프로그램에서 자료형 S가 자료형 T의 하위형이라면 필요한 프로그램의 속성(정확성, 수행하는 업무 등)의 변경 없이 자료형 T의 객체를 자료형 S의 객체로 교체(치환)할 수 있어야 합니다.
쉽게 말하면 프로그램의 객체는 프로그램의 정확성을 깨뜨리지 않으면서 하위 타입의 인스턴스로 바꿀 수 있어야 한다는 것입니다.

다형성에서 하위 클래스는 인터페이스에서 정해놓은 규약을 모두 지켜야합니다. 인터페이스를 구현한 구현체를 믿고 사용하려면 이 원칙이 반드시 필요합니다.

실생활의 예로, 자동차 인터페이스의 엑셀은 앞으로 가야만 합니다. 자동차의 엑셀이라는 물건에 대한 사람들의 합의는 너무나 당연하게도 앞으로 가는 것이기 때문입니다.
인터페이스를 사람들 간의 엑셀에 대한 약속, 인터페이스를 구현한 클래스를 실제로 만들어진 자동차의 엑셀이라고 생각하면 이해하기 쉬울 것 같습니다.

ISP (Interface Segregation Principle)

자동차에는 리프팅 기능이 필요하지 않습니다. 자동차 정비소에서는 필요합니다.

네 번째로 SOLID의 I에 해당하는 인터페이스 분리 원칙입니다.
클라이언트가 자신이 이용하지 않는 메서드에 의존하지 않아야 한다는 원칙입니다.
특정 클라이언트를 위한 인터페이스 여러 개가 범용 인터페이스 하나보다 낫습니다.
자동차의 예로, 자동차 인터페이스는 운전 인터페이스와 정비 인터페이스로 분리할 수 있고.
사용자의 예로, 사용자 클라이언트는 운전자 클라이언트, 정비사 클라이언트로 분리할 수 있습니다. 이렇게 분리를 하게되면, 정비 인터페이스가 변해도 운전자 클라이언트에는 영향을 주지 않습니다. 결론적으로는 인터페이스가 명확해지고, 대체 가능성이 높아집니다.

DIP (Dependency Inversion Principle)

택시가 꼭 소나타일 필요는 없다.

마지막으로 SOLID의 D에 해당하는 의존관계 역전 원칙입니다.
의존관계 역전 원칙은 다음의 두 원칙을 따릅니다.
첫째, 상위 모듈은 하위 모듈에 의존해서는 안된다. 상위 모듈과 하위 모듈 모두 추상화에 의존해야 한다.
둘째, 추상화는 세부 사항에 의존해서는 안된다. 세부사항이 추상화에 의존해야 한다.
쉽게 이야기해서 구현 클래스에 의존하지 말고 인터페이스에 의존하라는 뜻입니다.
역할에 의존해야합니다. 택시를 불렀는데 소나타면 타고 그랜저가 오면 타지 않는 경우는 없습니다. 학교에 지각할 위험이라면, 택시라는 역할에 의존해야지 차종이라는 구체적인 사항을 따질 여유는 없습니다.

결론

이렇게 객체지향 프로그래밍에서 중요하게 지켜져야할 원칙 5가지에 대해서 알아보았습니다. 객체지향의 핵심인 다형성을 잘 활용하여 이러한 원칙을 지켜가며 좋은 프로그래밍을 하는 것은 중요할 것입니다!

참고문헌

스프링 핵심원리 강의
https://inf.run/sZLP

위키백과
https://ko.wikipedia.org/wiki/SOLID_(객체_지향_설계)

0개의 댓글