객체 지향 프로그래밍 입문
8 min readJul 10, 2022
안녕하세요. 배민혁 입니다.
스프링에 입문하면서 객체지향의 개념을 정리하지 않을 수 없습니다. 스프링의 핵심이 객체지향과 DI(Dependency Injection)에 있기 때문입니다.
아래 내용은 [객체 지향 프로그래밍 입문 | 인프런] 강의를 정리한 내용입니다.
0 객체지향은 왜 필요한가
- 소프트웨어의 가치: 변화
소프트웨어를 유지보수 한다는 것은 “이전처럼 동작하게 하는 것”이 아니라, “변하는 세상에서 여전히 유용한 소프트웨어가 되는 것”이다.
> Jessica Kerr
- 하지만, 코드 1줄을 만드는데 시스템이 변화할 수록 비용이 커짐
- 주요원인은 코드 분석 시간 증가, 코드 변경 시간 증가
- 낮은 비용으로 변화할 수 있는 방법
> 패러다임, 설계, 아키텍처, 업무 프로세스/문화
소프트웨어의 핵심 가치는 변화인데, 변화를 낮은 비용으로 할 수 있게 도와주는 패러다임중 하나가 오늘 글의 주제인 객체지향입니다.
1 객체
1.1 객체 지향
- 데이터와 프로시저를 객체라는 단위로 묶는다.
- 특정 객체가 가지고 있는 데이터는 그 객체의 프로시저만 접근할 수 있다.(프로시저는 클래스 내부 함수, 즉, 메서드라고 생각하면 이해가 쉽다.)
- 특정 객체는 다른 객체의 데이터에 바로 접근할 수 없고, 다른 객체의 프로시저를 호출하는 방식으로 연결된다. (의존한다고 볼 수도 있는 것 같다.)
1.2 객체
- 객체는 기능을 제공한다.
> 기능이 없는 단순 클래스는 데이터에 가깝다.
- 메서드를 이용해 기능을 명세한다.
- 객체와 객체 상호 작용을 “메세지를 주고 받는다”고 표현한다.
> ex. 메서드를 호출하는 메세지, 리턴하는 메세지, 익셉션 메세지 등
1.3 캡슐화
- (데이터 + 관련 기능 묶기)의 형태로 객체가 기능을 어떻게 구현했는지 외부에 감추는 것을 캡슐화라고 한다.
- 또한 외부에 영향 없이 객체 내부 구현 변경이 가능하다.
1.3.1 캡슐화를 하지 않으면
- 요구사항의 변화가 데이터 구조/사용에 변화를 발생시킨다. 그리고 변경되는 코드가 연쇄적으로 발생한다.
1.3.2 캡슐화를 하면
- 기능을 제공하고 구현상세를 감춘다.
- 내부 구현만 바뀔뿐 사용처는 수정하지 않아도 된다.
- 캡슐화를 하면 의도를 쉽게 파악할 수 있어 기능에 대한 이해도를 높일 수 있다.
⭐️ 캡슐화를 통해서 연쇄적인 변경 전파를 최소화 할 수 있다.
1.3.3 캡슐화를 위한 규칙
[Tell, Don’t Ask]
- 데이터를 달라고 하지 말고 해달라고 하기
- 데이터를 가지고 와서 확인하지 말고 그 자체를 확인해달라고 하기
[Demeter’s Law]
- 메서드에서 생성한 객체의 메서드만 호출
- 파라미터로 받은 객체의 메서드만 호출
- 필드로 참조하는 객체의 메서드만 호출
2 추상화와 다형성
[다형성]
- 여러 모습을 갖는 것
- 상속을 통해서 구현한다.
> 자식, 하위: 상속받아 만들어지는 새로운 클래스
> 부모, 상위: 상속의 대상
[추상화]
- 데이터나 프로세스 등을 의미가 비슷한 개념이나 의미있는 표현으로 정의하는 과정
- 방법
> 특징을 뽑아서 클래스를 만든다. ex. User 테이블, Money 클래스
> 공통성질을 추출한다. ex. 프린터: HP, 삼성 / GPU: 지포스, 라데온
[서로 다른 구현 추상화]
- SCP 파일 업로드, HTTP 데이터 전송, DB 테이블 삽입 모두 푸쉬를 모두 구현이었다면, 이를 추상화하면 푸쉬 발송 요청이라 표현할 수 있다.
[타입 추상화]
- 여러 구현 클래스를 태표하는 상위 타입을 도출한다.
> 주로 인터페이스 타입으로 추상화한다. 인터페이스는 기능에 대한 의미를 제공할 뿐, 구현은 제공하지 않는다.
> 인터페이스는 concrete 클래스들이 어떻게 구현할지 알 수 없다. - concrete 클래스에 실제 구현이 있다.
⭐️ 추상화를 통해 구현을 감추고 의도를 더 잘 드러낼 수 있다.
⭐️ 유연함에 장점이 있다.
2.1 예시
2.1.1 Notifier
OrderCancleService
는NotifierFactory
,Notifier
인터페이스를 의존하고 있다.- 그렇기때문에 기능이 확장되더라도(=구현 클래스를 새롭게 추가해도)
OrderCancleService(=사용처,클라이언트)
는 변경이 없다.
2.2.2 클라우드 파일 시스템
- 사용처(=클라이언트)인
CloudFileManager
는 수정에는 닫혀있고, 실제 구현은 확장에 열려있다.
⭐️ OCP(Open-Closed Principle)를 준수한다!
3 상속보다는 조립
- 상속은 주로 상위 클래스의 기능을 재사용, 확장하는 방법으로 활용한다.
3.1 상속의 단점
- 상위 클래스(부모 클래스)의 변경이 어려움
> 상위 클래스를 조금만 변경해도 하위 클래스 모두가 영향을 받아 변경이 전파된다. - 새로운 조합에 따른 클래스 증가
> 조합이 증가할 때마다 하위 클래스가 늘어나는 문제가 있다. 또한, 어떤 것을 상속해야할 지 애매하다. - 상속오용
> 불필요한 기능까지 모두 상속된다. 이는 다른 사람에게 혼란을 줄 수 있다.
3.2 조립
위의 단점을 해결하는 방법이 조립입니다.
- 여러 객체를 묶어서 더 복잡한 기능을 제공한다.
- 보통 필드로 다른 객체를 참조하는 방식으로 조립하거나, 객체를 필요 시점에 생성/구함
- 하위 클래스를 변경하는데 있어서 상속에 비해 자유로워진다.
3.2.1 언제 조립, 상속을 해야할까?
- 기능 재사용을 위해서는 조립이 상속보다 장점이 많다.
- 그렇기때문에 상속하기에 앞서 조립으로 풀 수 없는지 검토해야한다.
- 진짜 하위 타입인 경우에만 상속을 사용한다.
4 기능과 책임 분리
4.1 기능 분해와 기능 제공
- 기능은 하위 기능으로 분해한다.
- 기능은 곧 책임이다. 그렇기 때문에 분리한 각 기능을 알맞게 분배한다.
4.2 큰 클래스, 큰 메서드
- 큰 클래스나 메서드가 커지면 절차 지향의 문제가 발생한다.
- 큰 클래스: 많은 필드를 많은 메서드가 공유한다.
- 큰 메서드: 많은 변수를 많은 코드가 공유한다.
- 즉, 여러 기능이 한 클래스/메서드에 섞여 있을 가능성이 높아진다.
⭐️ 책임에 따라 알맞게 코드를 분리할 필요가 있다.
5 의존
- 기능 구현을 위해 다른 구성 요소를 사용하는 것을 의미한다.
> ex. 객체 생성, 메서드 호출, 데이터 사용 - 의존은 변경이 전파될 가능성을 의미한다.
> 의존하는 대상이 바뀌면 바뀔 가능성이 높아진다.
> ex. 호출하는 메서드의 파라미터가 변경, 호출하는 메서드가 발생할 수 있는 익셉션 타입이 추가
5.1 의존하는 대상은 적을수록 좋다.
- 위에서 이야기 했듯이 의존은 변경이 전파될 가능성을 말한다.
- 의존하는 대상이 많다는 것은 “변경이 전파될 가능성을 더욱 높인다.”라고 말할 수 있다.
5.1.1 기능이 많아 의존하는 대상이 많은 경우
- 한 클래스에서 많은 기능을 제공하는 경우
- 각 기능마다 의존하는 대상이 다를 수 있다.
- 한 기능 변경이 다른 기능에 영향을 줄 수 있다.
👉 기능별로 분리를 고려해야한다. 이를 통해 각 클래스가 의존하는 대상을 줄일 수 있다.
5.1.2 추상화가 안되어져 있어서 의존하는 대상이 많은 경우
- 몇 가지 의존 대상을 단일 기능으로 묶어서 생각하면 의존 대상을 줄일 수 있다.
MinwonFactory, MinwonRepository
가 민원 등록을 위한 의존이었다면, 이를 하나의AutoDebitMinwonRegister
로 기능을 추상화하여 의존하는 대상을 줄일 수 있다.
5.2 의존성 주입(Dependency Injection)
- 사용처(=클라이언트)가 추상타입 없이 구현을 직접 사용(=의존)하면, 구현이 바뀌면 사용처코드도 변경되어야 한다.
- 구현(의존 대상)을 직접 의존하지 않는 방법 중 하나가 의존성주입이다.
- 외부에서 의존객체를 주입한다. 이때, 생성자나 메서드를 이용해 주입한다.
- 개인적으로 주로 사용하는 스프링 프레임워크는 조립기를 통해서 의존 주입을 처리한다.
> 조립기는 객체를 생성하고, 의존 주입을 처리한다.
5.2.1 의존성 주입의 장점
- 상위 타입을 사용할 경우 의존 대상이 바뀌면 조립기(설정)만 변경하면 된다.
- 의존하는 객체 없이 대역 객체를 사용해서 테스트 가능