SOLID란 좋은 객체지향 설계를 위한 5가지 원칙을 의미한다. 지금부터 그 5가지에 대해 알아보도록하자.
SRP (Single Responsible Principle) : 단일 책임 원칙
한 클래스는 단 하나의 기능,책임만을 가지고 있어야한다는 원칙이다. 한 클래스의 책임 영역을 확실하게 해야한다느 의미이다. 그렇다면 책임의 범위가 무엇일까? 이는 상황에 따라 다르지만 변경을 기준으로 한다. 한 클래스의 기능을 변경하거나 코드를 수정할때, 이에 따른 다른 클래스의 파급효과가 적을수록 srp를 잘 따르고 있는 것이다.
OCP (Open / Close Principle) : 개방 - 폐쇄 원칙
소프트웨어는 확장에는 열려있어야하나 변경에는 닫혀있어야한다. 즉, 새로운 기능을 추가하는 것과 같은 확장이 일어나도 기존 코드를 변경하는 것은 안된다는 말이다. 상당히 이해하기 어렵다. 기능을 추가하는데 어떻게 코드를 수정하지 않을 수 있지?라는 생각이 들겠지만 객체지향 프로그래밍에서는 이를 다형성을 통해 해결하고 있다. 새로운 기능을 추가하고 싶으면 해당 기능을 수행하는 인터페이스를 구현하는 구체 클래스를 새로 정의하는 방식으로 확장을 해야한다.
이를 지키기 위해선 변경할 것과 변경하지 않을것을 잘 구분해야한다. 즉, 인터페이스를 잘 정의하고 다형성을 잘 활용해야한다.
LSP (Liskov substitution Principle) : 리스코프 치환 원칙
프로그램 객체는 프로그램의 정확성을 깨지 않으면서 하위 타입의 인스턴스로 바꿀수 있어야한다.
인터페이스의 구현체를 믿고 사용하려면 구현체의 단순 컴파일의 성공이 아닌 인터페이스의 규약을 다 지켜야한다는 의미로, 상위 클래스의 인터페이스에서 정한 규약을 따르는 하위 클래스 구현체가 언제나 완벽하게 대체할 수 있어야한다는 의미이다.
객체지향 프로그래밍에선 다형성을 지키기 위해 주로 변수의 타입은 상위클래스(인터페이스)로 하고 인스턴스는 서브클래스(구현체)로 한다. 이를 잘 적용하는 것이 LSP를 잘 지키는 것이다.
private Car car = new K3;
ISP (Interface Segmentation Principle) : 인터페이스 분리 원칙
인터페이스를 하나로 통합하기보단, 기능에 따라 여러개를 만들고 구현 클래스는 자신이 사용하지 않는 인터페이스는 구현하지 않는다는 원칙이다. 이렇게 인터페이스를 분리하게 될 경우 다른 인터페이스 자체가 변해도 그 외의 클라이언트에 영향을 주지 않는다.
예를 들어보자. 자동차를 만든다고할때 자동차 인터페이스를 만드는 것이 아니라, 운전 관련 인터페이스와 정비 관련 인터페이스로 여러개의 인터페이스를 만들고 클라이언트를 운전자와 정비사로 분리하자. 이때, 정비 인터페이스와 관련된 내용이 변경된다면 정비사에게만 코드의 영향이 미치고 운전자에겐 영향이 미치지 않는다. 이렇게 인터페이스를 분리하고 인터페이스의 책임을 갖는 원칙을 ISP라고한다.
DIP (Dependency Inversion Principle) : 의존관계 역전 원칙
프로그래머는 추상화에 의존해야지, 구체화에 의존하면 안된다. 즉, 추상화 객체는 상위클래스인 변함이 없는 인터페이스를 의미하고 구체화는 하위 클래스인 상대적으로 변화가 잦은 구현체를 의미한다. 즉, 개발자는 인터페이스에 의존해 프로그래밍을 해야지 구현체에 의존해서 프로그래밍을 하면 안된다는 이야기이다. 자동차 인터페이스가 있고 자동차의 부품인 타이어가 있다고 생각해보자. 이때 올바른 설계 방향은 '자동차는 - 타이어를 필요로한다. '이다. 만약 타이어가 아닌 타이어의 기능을 구현한 것 중 하나인 스노우 타이어에 의존한다고 생각해보자. '자동차는 스노우 타이어를 필요로한다.'가 된다. 자동차를 만들때마다 스노우타이어로만 만든다면..? 다양한 용도에 맞게 활용할 수 없을 것이다. 또한, 스노우 타이어 시장이 문제가 생긴다면 자동차 자체를 만들 수 없게 된다. 이처럼, 객체간의 의존관계는 추상화 객체들간의 의존관계가 형성되어야한다는 의미이다.