객체 지향 프로그래밍의 5가지 원칙인 유명한 SOLID 원칙에 대해 간단히 알아보려 한다.
🎊 [OOP] 객체지향 프로그래밍의 5가지 설계 원칙, 실무 코드로 살펴보는 SOLID
이 원칙에서 중요한 것은 '변경되는 이유가 하나여야 한다'는 것이다. 여태 '하나의 모듈은 하나의 책임만 가진다' 는 것은 애매한 정의다. 모듈은 여러 대상(액터)에 대해 책임을 가지는 게 아니라 오직 하나의 액터에 대해서만 책임을 져야 한다.
SRP 원칙을 지키면 여러 액터에게서 변경 요구가 오는 것을 막을 수 있을 뿐만 아니라, 변경 시의 이유와 시점까지 명확해진다. 시스템이 커져서 모듈 간에 의존 사항이 많아져도 변경 요청이 왔을 때 한 군데만 수정하면 되기 때문이다.
확장에 대해서는 열려있고 수정에 대해서는 닫혀있어야 한다는 원칙이다.
예를 들어, 새로운 암호화 정책(변경 사항)으로 인해 코드를 수정하게 되었는데 암호화 정책과는 무관한 기존 코드(예: UserService)를 같이 수정해야 한다면? 나중에 다시 암호화 정책을 변경해야 할 경우 또 다시 UserService를 변경해야 할 것이다.
개방 폐쇄 원칙을 준수하려면 추상화라는 개념이 필요하다.
추상화란?
핵심적인 부분은 남기고 불필요한 부분은 제거하는 것, 복잡한 것을 간단히 만드는 것이다.
변하지 않는 부분은 남기고 변하는 부분은 생략하여 추상화하면, 변경이 필요할 때 생략된 부분(변하는 부분)만 수정하면 된다.
위의 암호화 정책 예시를 추상화해보자.
UserService 모듈은 어떤 암호화 정책이 사용되는지 알 필요 없이, passwordEncoder 객체를 통해 암호화된 비밀번호를 받는 역할만 수행하면 된다. 그래서 UserService가 암호화 클래스가 아닌 PasswordEncoder 인터페이스에만 의존하도록 추상화하면 OCP 원칙에 맞춘 개발이 가능하다.
하나의 객체가 알아야 하는 것이 많아지면 결합도가 높아진다. 이를 해결하는 것이 추상화이다. 즉, 추상화가 잘 이루어진 객체는 OCP 원칙을 잘 준수했다고 볼 수 있다.
클라이언트의 목적과 용도에 적합한 인터페이스를 제공하는 것이다. 이를 통해 불필요한 간섭을 최소화하고, 기존 클라이언트에 영향을 주지 않고 유연하게 기능을 확장하거나 수정할 수 있다.
어떤 구현체에 부가 기능이 필요하다면 이 인터페이스를 구현하는 다른 인터페이스를 만들어서 해결한다. 예를 들어, 파일 읽기/쓰기 기능을 갖는 구현 클래스가 있을 때 어떤 클라이언트는 읽기 작업만을 필요로 한다면 별도의 읽기 인터페이스를 만들어 제공한다.
1988년 바바라 리스코프가 올바른 상속 관계의 특징을 정의하기 위해 발표했다. 하위 타입은 상위 타입을 대체할 수 있어야 한다는 원칙이다. 즉, 상위 타입이 하위 타입으로 변경되어도 객체를 사용하는 클라이언트는 차이점을 인식하지 못하고 상위 타입의 인터페이스를 통해 서브 클래스를 사용할 수 있어야 한다.
예를 들어, 정사각형은 직사각형이다(Square is a Rectangle) 예시를 보자.
클라이언트는 직사각형의 너비와 높이는 다를 것이라고 가정하나 정사각형은 이를 준수하지 못한다.
이 문제를 해결하기 위해 빈 메소드를 호출하도록 하거나 호출 시에 에러를 던지는 등의 조치를 취할 수 있다. 또는 추상화 레벨을 맞춰서 메소드 호출이 불가능하도록 하거나(Square은 resize를 호출하지 못하게 하는 등) 해당 추상화 레벨에 맞게 메소드를 오버라이딩할 수 있다.
고수준 모듈은 저수준 모듈의 구현에 의존해서는 안 되며, 저수준 모듈이 고수준 모듈에 의존해야 한다는 원칙이다.
결국 비즈니스 관련 부분이 세부 사항에 의존하지 않는 설계 원칙을 의미한다.
의존 역전 원칙은 개방 폐쇄 원칙과 밀접한 관련이 있으며, 의존 역전 원칙이 위배되면 개방 폐쇄 원칙 역시 위배될 가능성이 높다.
객체 지향 설계 원칙인 SOLID가 얘기하는 핵심은 결국 추상화와 다형성이다. 5가지 원칙을 지켜 유지 보수가 쉽고 확장성 있는 유연한 소프트웨어를 만들자!