✔ 해당 포스트는 프로그래머스 백엔드 `데브코스` 4기 교육과정을 듣고 정리하였습니다! ✔
OOP ( Object Oriented Programming )
`OOP`란 객체 지향 프로그래밍을 말한다. 여기서 객체 지향 프로그래밍이란 컴퓨터 프로그램을 어떤 데이터를 입력받아 순서대로 처리하고 결과를 도출하는 명령어들의 목록으로 보는 시각에서 벗어나 여러 독립적인 부품들의 조합, 즉 객체들의 유기적인 `협력`과 `결합`으로 파악하고자 하는 컴퓨터 프로그래밍의 패러타임을 의미한다.
이러한 OOP에는 추상화, 상속, 다형성, 캡슐화라는 4가지 특징이 있다.
1. 추상화
추상이라는 용어의 사전적 의미는 "사물이나 표상을 어떤 성질, 공통성, 본질에 착안하여 그것을 추출하여 파악하는 것" 이다. 여기서 핵심이 되는 개념은 "공통성과 본질을 모아 추출" 한다는 것.
추상화는 불필요한 세부 사항들은 제거하고 가장 본질적이고 공통적인 부분만을 추출하여 표현하는 것이다. 글만 봐서는 잘 모르겠으니 간단한 코드 예시를 보도록 하자.
// 추상적인 기능을 가진 클래스
abstract class Login {
abstract void login(); // 정의만을 가지는 추상 클래스
}
class KakaoLogin extends Login{
@Override
void login() {}; // 추상클래스의 기능을 오버라이드 하여 사용
}
class NaverLogin extends Login{
@Override
void login() {}; // 추상클래스의 기능을 오버라이드 하여 사용
}
위 예시는 로그인이라는 공통적인 기능을 추출하여 `Login` 추상 클래스를 만들었고, 이를 상속받는 `KakaoLogin`, `NaverLogin` 클래스가 추상 메서드인 `login`을 오버라이드 하여 사용하는 구조이다. 이로써 각자 login 내부 동작을 자신들의 요구사항에 맞게 구현할 수 있게 된다.
즉 ,어떤 객체가 수행해야 하는 핵심적인 역할만을 규정해두고, 실제적인 구현은 해당 클래스, 인터페이스를 구현하는 각각의 객체들에서 하도록 프로그램을 설계하는 것을 의미한다. 이것을 객체 지향 프로그래밍에서는 역할과 구현의 분리라고 한다.
객체 지향적 설계에 있어서 이러한 추상화는 객체들 간의 관계를 보다 유연하게 연결하는 역할을 담당한다. 추상 클래스뿐만 아니라 인터페이스를 통한 구현도 가능한데, 추상 클래스와 달리 인터페이스는 다중 상속이 가능하다는 특징이 있다.
2. 상속
상속이란 기존의 클래스를 재활용하여 새로운 클래스를 작성하는 Java의 문법 요소이다.
앞서 봤었던 추상화의 연장선에서, 상속은 클래스 간 공유될 수 있는 속성과 기능들을 상위 클래스로 `추상화`시켜 해당 상위 클래스로부터 확장된 여러 개의 하위클래스들이 모두 상위 클래스의 속성과 기능들을 간편하게 사용할 수 있도록 한다.
상위 클래스에서 정의된 필드와 메서드를 하위 클래스가 상속받아 사용할 수 있기 때문에 코드의 재사용성이 높아지고, 오버라이딩을 통한 다양한 구현을 할 수 있기 때문에 다형성의 속성도 갖게 된다.
원자 > 물질 > 생물 > 동물 > 포유류 > 인간 > 남자 > 짱구
위와 같이 추상과 구체 관계에서만 상속 관계를 표현해야 한다.
서브클래싱과 서브타이핑
서브클래싱 : 다른 클래스의 코드를 `재사용할 목적으로` 상속을 사용하는 경우
- 자식 클래스와 부모 클래스의 행동이 호환되지 않기 때문에 자식 클래스의 인스턴스가 부모 클래스의 인스턴스를 대체할 수 없다. 서브클래싱을 구현 상속(implementation inheritance) 또는 클래스 상속이라고 부르기도 한다.
- 재사용을 위해 상속을 사용한다면 부모 클래스와 자식 클래스가 강하게 결합되어 유연하지 못한 구조가 될 수 있다.
서브타이핑 : 타입 계층을 `구성하기 위해` 상속을 사용하는 경우를 가리킨다.
- 서브타이핑에서는 자식 클래스와 부모 클래스의 행동이 호환되기 때문에 자식 클래스의 인스턴스가 부모 클래스의 인스턴스를 대체할 수 있다. 이때 부모는 자식 클래스의 슈퍼타입이 되고 자식 클래스는 부모 클래스의 서브타입이 된다. 서브타이핑을 인터페이스 상속(interface inheritance) 라고 부르기도 한다.
- 정확히 말하면 상속이 서브타이핑을 위해 사용될 경우에만 `is-a` 관계이다, 즉 LIP (리스코프 치환 원칙) 를 준수해야만 서브타이핑 관계라고 말할 수 있다.
3. 다형성
다형성은 이전에 정리했던 글이 있으니 해당 글의 링크로 대체하겠다!
https://weonest.tistory.com/50
4. 캡슐화
캡슐화란 서로 연관있는 속성과 기능들을 하나의 캡슐로 만들어 데이터를 외부로부터 보호하는 것을 말한다.
이렇게 캡슐화를 하는 이유로 크게 두 가지를 언급할 수 있다.
- 데이터 보호 (Data protection) : 외부로부터 클래스에 정의된 속성과 기능들을 보호
- 데이터 은닉 (Data hiding) : 내부의 동작을 감추고 외부에는 필요한 부분만 노출
즉, 각 객체 고유의 독립성과 책임 영역을 안전하게 지키고자 하는 목적이 있다. 또한 캡슐화를 통해 객체는 자율적으로 다른 객체들과 협력할 수 있는 존재가 되는데, 이 덕분에 자연스레 시스템 의 결합도는 떨어지고 응집도는 올라가게 된다.
결합도와 응집도
결합도
결합도란 모듈간의 상호 의존성을 나타냅니다. 결합도가 높으면 변경에 대한 영향력이 크고 유지보수가 어려워집니다. 무분별한 상속을 하게되면 결합도가 높아져 변경에 대한 영향이 커지게 됩니다. 상위 클래스에서 변경이 발생하면 하위클래스까지 영향을 끼치기 때문에 더 많은 코드 수정이 필요하게 될 수 있습니다.
응집도
응집도란 하나의 모듈내에서 요소들이 얼마나 밀접하게 관련되어있는지를 나타냅니다. 응집도가 높으면 내부의 요소들이 밀접하게 연관되어있어 재사용성,유지보수성이 높아지게 됩니다. 응집도가 낮으면 요소들이 서로 느슨하게 연관되어있어 재사용성,유지보수성이 낮아지게됩니다.
응집도는 단일책임원칙과 밀접한 연관관계가 있습니다. 하나의 책임만 가져야 한다는 원칙에 따라 설계되면 서로 밀접한 관련이 있는 기능만 포함하게 되므로 응집도가 올라가게 됩니다.
SOLID
`SOLID`는 위에서 소개한 `OOP`를 잘하기 위한 5가지 원칙이다. 어떻게 하면 객체를 잘 나누고 연관지을 수 있을지에 대한 고민에서 탄생하게 된 원칙인데 이러한 `SOLID` 원칙을 지키며 코드를 작성하다보니 어떠한 패턴들이 보이기 시작했고 여기서 탄생한 것이 `디자인 패턴`이다.
1. 단일 책임 원칙 (Single Responsibility Principle, SRP)
객체는 단 하나의 책임만을 가져야 한다는 원칙을 말한다. 여기서 `책임` 이라는 의미는 하나의 `기능 담당` 으로 볼 수 있다. 한 클래스에 너무 많은 책임이 있으면 코드 변경 시 다른 책임도 함께 변경될 가능성이 높아지기에 유지보수가 어려워진다.
2. 개방 폐쇄 원칙 (Open Closed Principle, OCP)
확장에는 열려 있고, 수정에는 닫혀 있어야 한다. 즉, 기존의 코드를 변경하지 않으면서, 기능을 추가할 수 있도록 설계가 되어야 한다는 원칙을 말한다.
쉽게 생각하면 OCP는 추상화를 의미한다고 볼 수 있다.
3. 리스코프 치환 원칙 (Liskov Substitution Principle, LSP)
리스코프 치환 원칙은 부모 객체와 이를 상속한 자식 객체가 있을 때, 부모 객체를 호출하는 동작에서 자식 객체가 부모 객체를 완전히 대체할 수 있다는 원칙이다.리스코프 치환 원칙은 옳바른 상속을 위해 자식 객체의 확장이 부모 객체의 방향을 온전히 따르도록 권고하는 원칙이다.
객체지향 언어에서는 객체의 상속이 일어난다. 이 과정에서 부모/자식 관계가 정의된다. 자식 객체는 부모 객체의 특성을 가지며, 이를 토대로 확장할 수 있다. 하지만 이 과정에서 무리하거나 객체의 의의와 어긋나는 확장으로 인해 잘못된 방향으로 상속되는 경우가 생긴다.
4. 인터페이스 분리 원칙 (Interface Segregation Principle, ISP)
ISP 원칙이란 범용적인 인터페이스 보다는 클라이언트가 실제로 사용하는 인터페이스를 만들어야 한다는 의미로, 인터페이스를 사용에 맞게 끔 각기 분리해야한다는 설계 원칙이다. 쓰지 않는 메서드를 구현하고 메서드 내부를 빈 공간으로 두거나 예외 처리를 하는 것은 상당히 낭비이다!
또한, 사용하지도 않는 인터페이스의 추상 메서드가 변경된다면 클래스에서도 수정이 필요하게 된다. 즉, 인터페이스 분리 원칙이란 인터페이스를 잘게 분리함으로써, 클라이언트의 목적과 용도에 적합한 인터페이스 만을 제공하는 것이다. 만약 인터페이스의 추상 메서드들을 범용적으로 이것저것 구현한다면, 그 인터페이스를 상속받은 클래스는 자신이 사용하지 않는 인터페이스 마저 억지로 구현해야 하는 상황이 올 수도 있다.
5. 의존성 역전 원칙 (Dependency Inversion Principle, DIP)
객체에서 어떤 클래스를 참조해서 사용해야하는 상황이 생긴다면, 그 클래스를 직접 참조하는 것이 아니라 그 대상의 상위 요소 (추상 클래스 or 인터페이스) 를 참조하라는 원칙이다.
하위 모듈의 구체적인 내용에 의존하게 된다면 하위 모듈에 변화가 있을 때마다 클라이언트나 상위 모듈의 코드를 자주 수정해야 하기 때문이다.
UML (Class Diagram)
https://www.nextree.co.kr/p6753/
Reference
'데브코스' 카테고리의 다른 글
[6/2 복습] JDK (Java Development Kit) (0) | 2023.07.06 |
---|---|
[2023년 5월 23일] 프로그래머스 데브코스 4기 합격 후기 (2) | 2023.07.06 |