객체지향 설계원칙 - SOLID

Chunbae·2024년 12월 2일
0

개발이론

목록 보기
4/6
post-thumbnail

SOLID?

SOLID는 객체 지향 프로그맹 및 설계의 5가지 기본 원칙으로
"로버트 C.마틴"이 2000년대 초반 명명하고 마이클 페더스가 정리한 원칙이다.

SOLID는 유지보수 및 확장성 향상개발 생산성 증대에 초점이 맞춰져 있는 원칙이다.

SOLID의 핵심 가치

추상화다형성을 통한 유연한 설계를 핵심 가치로서 활용하고 있고, OOP의 4가지 특징인 "추상화, 상속, 캡슐화, 다형성"등을 개념을 재정립하여 만든 원칙이다.

5가지의 원칙

  • SRP - Single Reponsibility Principle : 단일 책임 원칙
  • OCP - Open Closed Principle : 개방 폐쇄 원칙
  • LSP - Lisotv Substitution Principle : 리스코프 치환 원칙
  • ISP - Interface Segregation Principle : 인터페이스 분리 원칙
  • DIP - Dependency Inversion Principle : 의존 역전 원칙

SRP - 단일 책임 원칙

SRP는 이름 그대로 단 하나의 책임만 가져야한다는 원칙이다.
책임이란 하나의 기능이나 역할을 의미하며 클래스가 특정한 역할에만 집중하도록 설계해야 한다는 의미이다.
원칙을 잘 따르면 클래스가 단순하고 유지보수가 용이해진다.


맥가이버 칼을 예시로 들어보면 "맥가이버 칼"은 여러 도구를 하나로 합쳐놓은 도구이다. 처음에는 편리해 보이지만 도구의 종류가 많이 질수록 크기와 복잡성이 증가하고 개별 기능의 성능이 떨어지는 경우가 발생한다.

이는 맥가이버 칼이 여러 책임을 동시에 수행하다보니 각 기능이 최적화 되지 못한 상황에 해당한다.
만약 각 도구가 본연의 역할만 수행하도록 분리된다면 각각의 기능을 보다 효과적으로 수행할 수 있을 것이다.

클래스도 마찬가지다. 하나의 클래스가 너무 많은 책임을 갖게되면 클래스의 역할이 불명확해지고 관리가 어려워지며 하나의 책임을 변경할 때마다 다른 책임까지 영향을 받을 가능성이 높아진다.
결국 단일 책임 원칙을 따르지 않는 클래스는 클래스 자체로서의 존재 가치를 잃게 된다.

SRP의 원칙을 준수하면 한개의 책임 변경으로부터 발생할 수 있는 사이드 이펙트에서 자유로워질 수 있고, 책임을 적절히 분배함으로써 코드의 가독성 향상, 유지보수 용이의 이점을 누릴 수 있게 된다.


OCP - 개방 폐쇄 원칙

OCP원칙은 기존 코드를 변경하지 않으면서 기능을 추가할 수 있도록 설계되어야한다는 원칙을 말한다.
확장에 대해서는 개방적(OPEN)이고, 수정에 대해서는 폐쇄적(CLOSED)이어야 한다는 정의이다.

  • 확장에 열려있다.
    • 모듈의 확장성을 보장하는 것을 의미
    • 새로운 변경사항이 발생했을 때 유연하게 코드를 추가함으로써 큰 힘이 들어가지 않고 기능확장 가능
  • 변경에 닫혀있다.
    • 객체를 직접적으로 수정하는건 제한해야 한다는 것을 의미한다.
    • 새로운 변경 사항이 발생했을 때 객체를 직접적으로 수정해야 한다면 새로운 변경사항에 대해 유연하게 대응 할 수 없는 애플리케이션이라고 한다.
    • 따라서 객체를 직접 수정하지 않고도 변경사항을 적용할 수 있도록 설계해야한다.

OPC원칙은 추상화 사용을 통한 구축 권장을 의미한다.
또한 다형성과 확장을 가능케하는 객체지향의 장점을 극대화하는 설계원칙이다.


LSP - 리스코프 치환 원칙

LSP 원칙은 "자식타입은 언제나 부모타입으로 교체할 수 있어야한다"라는 원칙이다.
LSP는 "다형성의 원리"를 얘기하는 것이다.

  • 다형성의 특징을 이용하기 위해 부모 클래스 타입으로 객체를 선언하여 하위 클래스의 인스턴스를 받으면 업캐스팅된 상태에서 부모의 메서드를 사용해도 동작이 의도대로 흘러가는 것을 의미한다.
  • 이를 자바에서 잘 적용된 것이 바로 "컬렉션 프레임워크"이다.
    • Interface.Collection의 추상 메서드를 각기 하위 자료형 클래스에서 Implements하여 인터페이스 구현 규약을 잘 지키도록 설계되어 있다.
public void myData(){
	//Collection타입 변수 선언
    Collection data = new LinkedList();
    data = new HashMap(); //중간에 타입을 변경해도 호환
    
    modify(data); //메소드 실행
}

public void modify(Collection data){
	//인터페이스 구현 구조가 잘 잡혀있기 때문에 add동작이 보장됨.
    list.add(1);
    ...
}

LSP는 한마디로 다형성을 지원하기 위한 원칙.


ISP - 인터페이스 분리 원칙

ISP란 인터페이스를 잘게 분리함으로써, 클라이언트의 목적과 용도에 적합한 인터페이스만을 제공하는 것이다.

  • 인터페이스의 추상 메서드들을 범용적으로 다양하게 구현하다보면, 해당 인터페이스를 상송받는 클래스는 자신이 사용하지 않는 메서드까지 불필요한 구현을 진행하게 되는것을 방지하기 위함이다.
  • 또한 사용하지 않는 인터페이스의 추상 메서드가 변경된다면 클래스의 수정도 불가피하게 이루어진다.

ISP는 클라이언트의 기준으로 목적과 용도에 맞게 분리하여 적합한 인터페이스만 제공하는 것을 원칙으로한다.

이와 비슷한 원칙이 하나 있는데 바로 SRP원칙이다. 복기하자면 SRP원칙은 "단일 책임 원칙"으로 클래스의 단일 책임을 강조한다면, ISP는 인터페이스의 단일 책임을 강조한다.

인터페이스는 다중 상속이 가능하기 때문에 분리할수 있으면 분리하여 원하는 기능을 조립하듯 사용하면 ISP의 원칙을 준수 할 수 있다.


DIP - 의존 역전 원칙

DIP원칙은 구체적인 구현체에 의존하지 말고, 추상화된 상위 요소(인터페이스, 추상클래스)에 의존하라라는 원칙이다.
즉, "구현체가 아닌 추상화에 의존하도록 설계"하라. 라는 의미이다.
이를 통해 코드를 유연하고 확장성이 높도록 설계 가능하다.

왜 사용해야할까?

구체적인 구현체에 의존하게 되면 해당 구현체가 변경될 때마다 코드를 수정해야하며 이는 시스템의 결합도를 높이고 유지보수를 어렵게한다.
하지만 DIP원칙을 준수하여 추상화에 의존하면 구현체가 변경되더라도 기존 코드를 수정하지 않아도되 결합도를 낮추고 시스템 안정성을 향상 시킬 수 있게 된다.


DIP원칙은 흔히 사용하는 Java Collection선언 방식에서 확인 가능하다.

List<String> list = new ArrayList<>();
Map<Long, String> map = new HashMap<>();

추상클래스인 List에 의존하고 구현체인 ArrayList를 생성자로 주입한다.
만약 ArrayListLinkedList로 변경해도 선언부만 수정하면 다른 코드는 수정하지 않아도 된다.

참고자료

SOLID 원칙

SOLID 코드예시

profile
말하는 감자

0개의 댓글