1. 객체 지향이란?
OOP(Object-Oriented Programming): 객체 지향 프로그래밍
컴퓨터 프로그램을 '명령어의 목록'으로 보기 보단, 여러 개의 독립된 단위인 '객체'들의 모임으로 파악하고자 하는 것
- 프로그램에 변화가 생기면 '객체'를 갈아끼우면 되기 때문에 프로그램이 유연하고 변경하기 쉬움
- 주로 대규모 소프트웨어 개발에 많이 사용됨
각 개체는 서로 메시지를 주고 받으며 데이터를 처리하는 방식으로 협력함
- 요청하는 객체는 클라이언트, 응답하는 객체는 서버로 볼 수 있음
- 혼자 있는 객체는 없으며, 이 수 많은 클라이언트와 서버 객체들은 서로 협력 관계를 가짐
- 서버인 객체가 클라이언트가 될 수도 있음
절차 지향 프로그래밍은 데이터 위에서 동작하는 함수를 작성하는 데에 집중하는 반면, 객체 지향 프로그래밍은 데이터와 메서드를 캡슐화한 '객체'를 만드는 데에 집중함
- 코드의 확장성과 유연성, 재사용성 덕분에 코드 구조가 향상되고, 코드를 관리하고 탐색하기가 쉬워짐
- ex. 책과 관련된 메서드는 Book이라는 class에, 회원과 관련된 메서드는 User라는 class에 저장 (기능이 추가/제거된다면 해당 class에 메서드를 추가/제거하면 됨)
객체 지향 특징
a. 추상화(Abstraction)
같은 속성과 기능을 가진 객체들 중 속성과 기능을 추출해 정의한 것
Abstract class
- OOP에서 다른 class의 틀이 되는 class로, 추상 메서드가 하나 이상 포함되거나 abstract 키워드로 정의함
- 인스턴스화(new) 될 수 없음
- 다른 class가 이 추상 class를 상속 받아 안에 있는 모든 추상 메서드를 구현하면 객체로 사용할 수 있음
- 구현 없이 선언만 된 메서드와 구현된 메서드을 가질 수 있음
- 기본적으로 class이며, 이를 상속, 확장해 사용하기 위한 것임 (class 간의 연간 관계 구축 - 부모 & 자식)
Interface
- 자식 class가 반드시 가져야 하는 메서드를 정의하는 틀이 됨
- 모든 메서드가 추상 메서드임
- 자식 class는 interface가 가진 모든 메서드를 구현해야 함 (public)
- 한 class는 하나 이상의 interfaces를 implements할 수 있음
- 해당 interface를 구현한 객체들에 대한 동일한 속성과 기능을 보장하기 위해 사용하는 것임 (형제)
b. 캡슐화(Encapsulation)
서로 관련 있는 속성과 기능을 하나의 단위(class)에 관리하고, 외부에서의 접근을 제한하며 외부에는 필요한 기능만 노출하는 것
- private로 변수를 선언하면 같은 class 안에서만 해당 변수에 접근할 수 있음
- 다른 class에서 private에 접근하려면 같은 class에 선언된 public 메서드를 통해야 함 (ex. getter & setter)
- 다른 class에서 이 class의 내부 동작을 몰라도 되도록 설계하는 것이 좋음
접근 제어자
- private: 변수와 메서드는 같은 class에서만 접근 가능
- protected: 변수와 메서드는 같은 class와 자식 class에서만 접근 가능
- public: 변수와 메서드는 어디서나 접근 가능
c. 상속(Inheritance)
한 class(자식, sub, derived class)가 다른 class(부모, super, base class)의 필드와 메서드를 상속 받는 것 (extends)
- sub class는 super class에 존재하는 메서드를 override하거나, 새로운 변수 또는 메서드를 추가할 수 있음
- 'super' 키워드를 사용해 sub class에서 super class의 변수나 생성자, 메서드를 간편하게 사용할 수 있음
- 다중 상속(extends)은 불가능함
d. 💫다형성(Polymorphism)
객체가 super class의 instance로 취급되고, super class에 정의된 특정 메서드를 기반으로 다른 기능을 하는 메서드를 갖는 것
- 다른 객체들이 같은 interface를 공유하고, 구현한 메서드가 각각 다른 기능을 할 수 있음
- 메서드 Overriding과 메서드 Overloading을 통해 구현됨
- 같은 메서드가 상황에 따라 다른 역할을 수행
- 상위 class 타입의 참조 변수로 하위 class의 객체를 참조할 수 있음 (반대는 불가능)
private Vehicle vehicle = new Car();
e. 메서드 Overriding
부모 class에 이미 정의된, 상속 받은 메서드를 덮어씌우는 메서드를 구현할 수 있음
- Runtime 다형성을 구현할 수 있음
- 상속의 개념에서만 사용 가능
f. 메서드 Overloading
같은 class 안에서 이름은 같지만 파라미터가 다른 메서드를 여러 개 생성할 수 있음
- Compile-time 다형성을 구현할 수 있음
- 메서드 호출 시, 컴파일러는 주어진 인자의 수와 타입을 바탕으로 실행할 메서드를 고름
g. Classes와 Objects
classes: objects를 생성하는 틀이 되며, 데이터와 데이터를 다룰 메서드를 가짐 (= 붕어빵 틀)
objects: classes의 instances (= 붕어빵)
2. 좋은 객체 지향 설계의 5가지 원칙 (SOLID)
SRP(Single Responsibility Principle)
단일 책임 원칙
- 한 class는 하나의 책임만 가지며, 그 크기는 적절해야 함
- ex. 객체의 생성과 사용을 분리 - Config
- class에 변경이 있을 시 cascade(연쇄)가 적어야 함
- ex. UI 변경
💫OCP(Open/Closed Principle)
개방-폐쇄 원칙
- 소프트웨어 요소는 확장에는 열려 있으며, 변경에는 닫혀 있어야 함
- 다형성을 활용해 새로운 class를 생성 (역할과 구현의 분리)
- 클라이언트가 코드를 변경해 구현 객체를 변경해야 하면 이를 어기게 됨
- 설정자(Config; DI 컨테이너)로 해결
LSP(Liskov Substitution Principle)
리스코프 치환 원칙
- 다형성에서 하위 class는 interface 규약을 모두 지켜야 함
- 원칙 -> 기능 보장
ISP(Interface Segregation Principle)
인터페이스 분리 원칙
- 특정 클라이언트를 위한 interface를 여러 개로 만드는 것이 좋음
- 기능의 명확성과 대체 가능성이 향상됨
💫DIP(Dependency Inversion Principle)
의존관계 역전 원칙
- 구현 class가 아닌, interface(역할)에 의존해야 함
- 변경이 편리해짐
3. 객체 지향 설계와 스프링
역할과 구현을 분리
자바 언어의 다형성을 활용해 객체를 역할(interface)과 구현(interface를 구현한 class, 구현 객체)으로 명확히 분리해야 함
- 객체 설계 시 역할을 먼저 부여(설계)하고, 그 역할을 수행하는 구현 객체 만들기
- 이를 통해 interface를 구현한 객체 instance를 실행 시점에 유연하게 바꿀 수 있음
- 메서드 overriding이나 class 상속 관계도 비슷한 경우임
클라이언트를 변경하지 않고, 서버의 구현 기능을 유연하게 변경 가능함
- 따라서, interface를 안정적으로 잘 설계하는 것이 중요함
- 물론 역할이 변하면 클라이언트, 서버 모두에 큰 변경이 발생하게 됨
스프링과 객체 지향
객체 지향의 핵심은 다형성이지만, 다형성만으로는 구현 객체 변경 시 클라이언트의 코드도 함께 변경되기 때문에 OCP와 DIP를 지킬 수 없음
- 스프링은 DI(Dependency Injection)와 IoC(Inversion of Control), DI 컨테이너를 제공해 다형성과 함께 OCP, DIP를 모두 가능하게 도와줌
- 클라이언트 코드의 변경 없이 기능을 확장할 수 있음
구체적으로 정하지 않은 기술도 나중에 확장할 수 있도록 모든 설계에 interface를 부여하는 것이 좋음
- 그러나 구현 class의 내용을 확인해야 하는 '추상화' 비용이 발생함
- 기능 확장 가능성이 없다면 구체 class를 직접 사용하는 것이 좋음 (이후 필요 시 리팩토링해 interface를 도입)