객체지향 프로그래밍(Object Oriented Programming)은 문제를 여러 개의 객체 단위로 나눠 작업하는 방식을 말한다. 클래스를 이용해 데이터 부분(Property
)과 데이터 처리 부분(Method
)을 하나로 묶어 인스턴스를 생성해 사용한다. 클래스, 객체, 인스턴스, 프로퍼티, 메서드 등 용어적인 부분은 이미 알고 있다고 가정하고, OOP의 컨셉적인 부분 위주로 정리해보았다.
참고로 5가지 개념을 보면서 서로 비슷하다고 느껴지는 부분도 있을 텐데, 이 5가지가 개념적으로 완전히 분리된 것은 아니기 때문이다.
1. 추상화(Abstraciton)
- 불필요한 정보는 숨기고 중요한 정보만을 표현함으로써 공통의 속성이나 기능을 묶어 이름을 붙이는 것
- 즉, 객체들의 공통된 특징을 파악해 정의해놓는 것이다. Class를 정의하는 것 자체가 바로 추상화라고 볼 수 있다.
2. 캡슐화(Encapsulation)
- 객체가 맡은 역할을 수행하기 위한 하나의 목적을 기준으로, 프로퍼티와 메서드를 묶는 것
3. 은닉화(hiding)
- 객체 내부의 변수에 접근, 제어를 외부에서 하지 못하도록 격리하는 것
- 변수에 접근지정자를
private
로 지정하여 구현 가능하며, 통상적으로getter
,setter
를 사용해 변수에 접근하고 제어한다.
4. 상속(Inheritance)
- 부모 클래스의 프로퍼티, 메서드를 하위 클래스가 물려받는 것
- 코드 재사용 및 유지보수 향상에 목적이 있다.
5. 다형성(Polymorphism)
- 부모 클래스에서 물려받은 메서드를 자식 클래스에서 다시 정의(= Overriding)함에 따라, 동일한 이름임에도 다른 기능을 할 수 있는 것
- 이에 따라, 메서드 이름을 제각각 다르게 지어줄 필요없어지게 되어 여러 객체를 다루더라도 심플하게 개발할 수 있게 된다.
OOP의 5대 원칙
앞글자를 따서 SOLID 원칙
이라고도 한다.
1. 단일 책임 원칙(Single Responsibility Principle, SRP)
하나의 클래스는 논리/개념적으로 하나의 기능만 가져야 한다.
- 한 클래스는 하나의 책임만 갖도록 하자.
-
즉, 뭔가가 변경되었을 때 파급효과가 적도록 하는 것이다.
- 여기서 파급효과라 함은 side effect라고 보면 될 것 같다.
2. 개방/폐쇄 원칙(Open/Closed Principle, OCP)
객체는 자신의 확장에 대해 열려있어야 하고, 주변의 변경에 대해서는 닫혀 있어야 한다. 한 객체의 변경이 의존관계를 가지는 다른 객체의 변경으로 이어지지 말아야 한다.
- 소프트웨어 요소는 확장에는 열려있으나 변경에는 닫혀 있어야 한다.
- Spring Framework에서 이게 잘 지켜질 수 있는 이유는, DI가 되는 덕분이다. Service와 Repository가 각각 클라이언트, 서버 역할인 경우가 많은데 이 때 Service 내에서 Repository를 직접 선택해 넣는 게 아니기 때문이다.
3. 리스코프 치환 원칙(Liskov Substitutions Principle, LSP)
서브타입은 언제나 자신의 기반타입으로 교체할 수 있어야 한다. 즉, 하위 클래스(ex: 정사각형)의 인스턴스는 상위형 객체 참조 변수에 대입해 상위 클래스(ex: 직사각형)의 인스턴스 역할을 수행하는 데 문제가 없어야 한다.
- 다형성에서 하위 클래스는 인터페이스 규약을 다 지켜야 한다는 것이다.
- 다형성이 있더라도 기능상 일관성이 흐트러지면 안된다는 의미로 해석된다.
4. 인터페이스 분리 원칙(Interface Segregation Principle, ISP)
큰 덩어리의 인터페이스들을 구체적으로 작은 단위들로 분리시킴으로써 꼭 필요한 메서드들만 이용할 수 있게 한다. 시스템의 내부 의존성 관계를 느슨하게 하여 리팩토링, 수정, 재배포를 쉽게 할 수 있도록 한다.
- 인터페이스를 작은 단위로 쪼개는 게 좋다는 것이다.
5. 의존성 역전 원칙(Dependency Inversion Principle, DIP)
추상 클래스는 파생 클래스를 참조해서는 안되며, 파생 클래스나 추상 클래스는 오직 추상 클래스만을 참조해야한다. 한마디로, 추상화된 것은 구체적인 것에 의존하면 안된다는 뜻이다.
- 추상화(인터페이스, 역할)에 의존해야하지 구체화(특정 클래스, 구현)에 의존하면 안된다.
- 즉, Spring Framework에서 Service가 RepositoryImpl이 아니라 Repository를 바라봐야 한다는 것이다. (RepositoryImpl을 몰라야 한다!)
- 구현체에 의존하면 변경이 어려워진다. 인터페이스에 의존하면, 다형성에 의해 구현체가 다른 걸로 바뀌어도 문제가 없다.
그밖에…
다형성만으로는 OCP와 DIP를 지킬 수 없다. 따라서 Spring Framework가 여기서 필요하게 된다.
[참고자료]
https://victorydntmd.tistory.com/117
인프런 - 스프링 핵심 원리(기본편) by 김영한님