본문 바로가기

Spring Boot

객체 지향 프로그래밍을 해야하는 이유와 SOLID 원칙

우리가 객체 지향 프로그래밍을 해야하는 이유는 뭘까요?

바로 소프트웨어를 '잘 관리'하기 위해서입니다.
소프트웨어와 하드웨어의 가장 큰 특징은 '소프트하다' 즉, 변화한다는 것이라 생각합니다.
시중에 나와있는 서비스를 보더라도 끊임업이 업데이트가 이루어지고 있습니다.
이 과정에서 기존 코드가 제대로 정리되어있지 않다면, 스파게티같은 코드를 뜯어고치느라 많은 비용이 들 수 있습니다.
이렇듯 소프트웨어에서는 기능을 만드는 것 만큼이나 유지 보수가 중요한데요,
객체 지향 프로그래밍을 통해서 유지보수를 간단하게 할 수 있기 때문에 객제 지향적으로 코드를 짜는게 중요합니다.

특히 소프트웨어를 유지보수하기 위해서는 분류와 교체가 필수적입니다.

분류는 비슷한 기능을 하는 것끼리 묶는 것인데, 이를 객체를 통해 할 수 있습니다.
객체를 정의할 때 사용하는 키워드인 class는 classification(분류)에서 가져온 단어입니다.
이처럼 객체 지향 프로그래밍은 코드를 잘 분류할 수 있게 해줍니다.
또 어떤 기능을 고치고 싶을 때, 그것이 어떤 코드에 있는지를 더 찾기 쉽게 만들기도 합니다.
도서관에서 책을 찾고 싶을 때 '문학' > '소설' > '국내 소설' 이런 식으로 분류가 되어있으므로 더 찾기 쉬운 것처럼
분류를 통해 코드의 구성을 잘 정리할 수 있습니다.

교체는 기존 기능을 새로운 기능으로 바꾸는 것을 말하는데요, 소프트웨어를 업데이트 하기 위해선 교체가 필수적입니다.
하지만 하나의 기능을 교체를 할 때 다른 기능까지 바뀌는 side effect가 발생하기도 합니다.
객체지향 프로그래밍은 이런 상황을 예방하게 해줍니다.

정리하자면, 소프트웨어는 계속해서 말랑말랑한 상태 즉, 보수가 용이한 상태를 유지해야 합니다.
이를 가능하게 해주는 것이 객체지향 프로그래밍이므로 우리는 객체 지향적으로 프로그래밍을 해야 하는 것입니다.

소프트웨어는 soft하게 유지되어야 합니다!

 


SOLID 원칙이란 무엇인가요?

객체 지향을 잘 하기 위해서 지켜야 하는 원칙입니다.
SOLID 원칙은 다음과 같습니다.

1. 단일 책임 원칙 (Single Responsibility principle)
2. 개방 폐쇄 원칙 (Open Closed Principle)
3. 리스코프 치환 원칙 (Liscov Substitution Principle)
4. 인터페이스 분리 원칙 (Interface Sergregation Principle)
5. 의존성 역전 원칙 (Dependency Inversion Principle)

 

📌 단일 책임 원칙 (SRP - Single Responsibility Principle)

단일 책임 원칙은 하나의 클래스는 하나의 책임(=역할)을 가져야 한다는 것입니다.
단일 책임 원칙에 따라 코드를 분류해놓으면 에러가 발생했을 때 추적을 하기도 쉽고, 
코드를 수정할 때 어느 부분을 수정해야 하는지 쉽게 찾을 수 있습니다.
아무리 작은 기능을 수행하는 코드이더라도, 추후 어떻게 수정될지 모르기 때문에 단일 책임 원칙을 지키는 것이 좋습니다.

 

📌 개방 폐쇄 원칙 (OCP - Open/Closed Principle)

개방 폐쇄 원칙은 확장에는 열려있고, 변경에는 닫혀있다는 의미를 갖습니다.
해석하자면, 새로운 기능을 추가할 수 있으면서 기존 코드는 변경하지 않게 설계해야 한다는 원칙입니다.
OCP를 지키기 위해서 추상화를 이용할 수 있는데요,
변경되지 않을 부분은 인터페이스로, 변경되는 부분은 구현 클래스로 만들어주어서
새로운 기능이 추가될 때는 인터페이스를 상속하고 함수를 오버라이딩해서 만들어주면 됩니다.
이렇게 되면 새로운 기능 추가에는 열려있지만, 기존 코드는 변경하지 않는다는 원칙을 지킬 수 있습니다.
OCP를 지키지 않으면 새로운 기능을 추가하기 위해 기존 코드를 건들여야 하는데
이 과정에서 기존 코드가 점점 더 복잡해질 수 있기 때문에 OCP를 지키는 것이 중요합니다.



📌 리스코프 치환 원칙 (LSP - Liscov Substitution Principle)

리스코프 치환 원칙이란, 자식 클래스를 부모 클래스로 바꾸더라도 코드가 의도대로 작동할 수 있어야 한다는 원칙입니다. 
즉, 부모 클래스가 자식 클래스를 참조하도록 업캐스팅 했을 때 문제없이 작동하도록 설계해야 합니다. 
예를들어 Parent parent = new Child(); 라는 코드에서 
parent 인스턴스에 대해 자식 클래스에서 오버라이딩한 메소드를 호출하는 경우를 생각해봅시다. 
이때 오버라이딩된 메소드는 parent의 메소드가 의도한 바와 동일하게 작동해야 합니다.
이를 위반하는 예시로는 부모 메소드를 잘못 오버라이딩하거나,
부모 메소드의 의도와 다르게 오버라이딩하는 경우가 있습니다. 

static class Animal {
	void eat(){
    	System.out.println("배가 고프면 무조건 먹이를 먹는다.");
    }
}

static class Human extends Animal {
	@Override
	void eat(){
    	System.out.println("배가 고파도 체중 유지를 위해 참는다. ");
    }
}

public static void main(String[] args){
	Animal animal = new Human();
    animal.eat();
    // 먹는 것을 기대했는데, 참게 됨
}


이럴 경우 기존 부모 클래스를 사용하는 코드에서 예상하지 못한 오류가 발생할 수 있습니다. 
또한 상속은 부모 클래스의 동작이 자식 클래스에서도 일관되게 유지되도록 하는 것인데, 
이 일관성이 깨진다는 문제도 발생합니다.
이런 상황을 방지하기 위해 LSP 원칙을 지켜서 부모 객체가 자식 객체를 대신하여도 문제가 발생하지 않게 해야 합니다.
LSP 원칙에서 알 수 있는 또 다른 교훈은 '무분별한 상속을 지양하라'는 것입니다. 
어떤 메소드는 오버라이딩을 하고, 어떤 메소드는 오버라이딩을 안하면,
부모 클래스를 사용할 때 어떤 결과가 나올지 예측하기 힘들어 혼란이 올 수 있습니다.
따라서 상속보다는 인터페이스를 활용해서 기능을 구현하면 LSP원칙을 더 잘 지킬 수 있습니다. 
(실제로 모던 개발 언어들에서는 상속을 막는 경우도 있다고 합니다. 무분별한 상속 남용은 지양합시다.)

 

📌 인터페이스 분리 원칙 (ISP - Interface Segregation Principle)

ISP는 인터페이스도 단일 책임을 갖도록 분리해야한다는 원칙입니다. (단일 책임 원칙의 인터페이스 버전)
하나의 인터페이스를 구현하는 클래스는 인터페이스 안의 모든 메소드를 구현해야 합니다.
이때 인터페이스의 크기가 크면, 사용하지 않는 메소드는 빈 메소드로 남겨두는 문제, 
각 구현체의 크기도 너무 커진다는 문제가 발생할 수 있습니다.
따라서 인터페이스도 클래스처럼 하나의 기능만 제공하도록 설계하여
구현체에서 필요한 인터페이스만 implement 할 수 있게 해야 합니다.

 

📌 의존성 역전 원칙 (DIP - Dependency Inversion Principle)

의존성 역전 원칙에 대해 이야기하기 전, 저수준 모듈과 고수준 모듈에 대해 알아봅시다. 
- 고수준 모듈 : 어떤 의미 있는 단일 기능을 제공하는 모듈
- 저수준 모듈 : 고수준 모듈의 기능을 구현하기 위해 필요한 하위 기능의 실제 구현 
예를들어 아이가 장난감으로 노는 어플리케이션에서 
아이는 '논다'는 기능을 제공하는 고수준 모듈이고, 장난감은 이를 위해 필요한 저수준 모듈입니다.
이때 아이가 저수준 모듈의 구현체에 의존한다면, (e.g 레고나 자동차 등)
구현제가 바뀔 때마다 아이의 내부 코드도 바꿔야 한다는 문제가 발생합니다. 
이를 해결하기 위해서는 '장난감'이라는 추상화 계층을 만들고, 
추상화 객체의 구현체들이 추상화계층을 의존하게 하면 됩니다.
이렇게 저수준 모듈의 변경이 고수준 모듈의 변경을 요구하는 의존성을 끊어내는 것이 의존성 역전의 원칙입니다.




OCP 참고 : https://inpa.tistory.com/entry/OOP-%F0%9F%92%A0-%EC%95%84%EC%A3%BC-%EC%89%BD%EA%B2%8C-%EC%9D%B4%ED%95%B4%ED%95%98%EB%8A%94-OCP-%EA%B0%9C%EB%B0%A9-%ED%8F%90%EC%87%84-%EC%9B%90%EC%B9%99

LSP 참고 : https://inpa.tistory.com/entry/OOP-%F0%9F%92%A0-%EC%95%84%EC%A3%BC-%EC%89%BD%EA%B2%8C-%EC%9D%B4%ED%95%B4%ED%95%98%EB%8A%94-LSP-%EB%A6%AC%EC%8A%A4%EC%BD%94%ED%94%84-%EC%B9%98%ED%99%98-%EC%9B%90%EC%B9%99?category=967430

DIP 참고 : [OOP] 객체지향 5대 원칙(SOLID) - 의존성 역전 원칙 (DIP) (velog.io)