객체 지향 프로그래밍 입문

bae.200.ok
8 min readJul 10, 2022

안녕하세요. 배민혁 입니다.

스프링에 입문하면서 객체지향의 개념을 정리하지 않을 수 없습니다. 스프링의 핵심이 객체지향과 DI(Dependency Injection)에 있기 때문입니다.

아래 내용은 [객체 지향 프로그래밍 입문 | 인프런] 강의를 정리한 내용입니다.

0 객체지향은 왜 필요한가

  • 소프트웨어의 가치: 변화

소프트웨어를 유지보수 한다는 것은 “이전처럼 동작하게 하는 것”이 아니라, “변하는 세상에서 여전히 유용한 소프트웨어가 되는 것”이다.
> Jessica Kerr

  • 하지만, 코드 1줄을 만드는데 시스템이 변화할 수록 비용이 커짐
  • 주요원인은 코드 분석 시간 증가, 코드 변경 시간 증가
  • 낮은 비용으로 변화할 수 있는 방법
    > 패러다임, 설계, 아키텍처, 업무 프로세스/문화

소프트웨어의 핵심 가치는 변화인데, 변화를 낮은 비용으로 할 수 있게 도와주는 패러다임중 하나가 오늘 글의 주제인 객체지향입니다.

1 객체

1.1 객체 지향

  • 데이터와 프로시저를 객체라는 단위로 묶는다.
  • 특정 객체가 가지고 있는 데이터는 그 객체의 프로시저만 접근할 수 있다.(프로시저는 클래스 내부 함수, 즉, 메서드라고 생각하면 이해가 쉽다.)
  • 특정 객체는 다른 객체의 데이터에 바로 접근할 수 없고, 다른 객체의 프로시저를 호출하는 방식으로 연결된다. (의존한다고 볼 수도 있는 것 같다.)

1.2 객체

  • 객체는 기능을 제공한다.
    > 기능이 없는 단순 클래스는 데이터에 가깝다.
Member Class는 객체가 아니다.
  • 메서드를 이용해 기능을 명세한다.
  • 객체와 객체 상호 작용을 “메세지를 주고 받는다”고 표현한다.
    > ex. 메서드를 호출하는 메세지, 리턴하는 메세지, 익셉션 메세지 등

1.3 캡슐화

  • (데이터 + 관련 기능 묶기)의 형태로 객체가 기능을 어떻게 구현했는지 외부에 감추는 것을 캡슐화라고 한다.
  • 또한 외부에 영향 없이 객체 내부 구현 변경이 가능하다.

1.3.1 캡슐화를 하지 않으면

  • 요구사항의 변화가 데이터 구조/사용에 변화를 발생시킨다. 그리고 변경되는 코드가 연쇄적으로 발생한다.

1.3.2 캡슐화를 하면

  • 기능을 제공하고 구현상세를 감춘다.
  • 내부 구현만 바뀔뿐 사용처는 수정하지 않아도 된다.
  • 캡슐화를 하면 의도를 쉽게 파악할 수 있어 기능에 대한 이해도를 높일 수 있다.

⭐️ 캡슐화를 통해서 연쇄적인 변경 전파를 최소화 할 수 있다.

1.3.3 캡슐화를 위한 규칙

[Tell, Don’t Ask]

  • 데이터를 달라고 하지 말고 해달라고 하기
  • 데이터를 가지고 와서 확인하지 말고 그 자체를 확인해달라고 하기

[Demeter’s Law]

  • 메서드에서 생성한 객체의 메서드만 호출
  • 파라미터로 받은 객체의 메서드만 호출
  • 필드로 참조하는 객체의 메서드만 호출

2 추상화와 다형성

[다형성]

  • 여러 모습을 갖는 것
  • 상속을 통해서 구현한다.
    > 자식, 하위: 상속받아 만들어지는 새로운 클래스
    > 부모, 상위: 상속의 대상
다형성 예시, Timer, Rechargeable을 상속

[추상화]

  • 데이터나 프로세스 등을 의미가 비슷한 개념이나 의미있는 표현으로 정의하는 과정
  • 방법
    > 특징을 뽑아서 클래스를 만든다. ex. User 테이블, Money 클래스
    > 공통성질을 추출한다. ex. 프린터: HP, 삼성 / GPU: 지포스, 라데온

[서로 다른 구현 추상화]

  • SCP 파일 업로드, HTTP 데이터 전송, DB 테이블 삽입 모두 푸쉬를 모두 구현이었다면, 이를 추상화하면 푸쉬 발송 요청이라 표현할 수 있다.

[타입 추상화]

  • 여러 구현 클래스를 태표하는 상위 타입을 도출한다.
    > 주로 인터페이스 타입으로 추상화한다. 인터페이스는 기능에 대한 의미를 제공할 뿐, 구현은 제공하지 않는다.
    > 인터페이스는 concrete 클래스들이 어떻게 구현할지 알 수 없다.
  • concrete 클래스에 실제 구현이 있다.

⭐️ 추상화를 통해 구현을 감추고 의도를 더 잘 드러낼 수 있다.
⭐️ 유연함에 장점이 있다.

2.1 예시

2.1.1 Notifier

  • OrderCancleServiceNotifierFactory, Notifier인터페이스를 의존하고 있다.
  • 그렇기때문에 기능이 확장되더라도(=구현 클래스를 새롭게 추가해도) OrderCancleService(=사용처,클라이언트)는 변경이 없다.

2.2.2 클라우드 파일 시스템

  • 사용처(=클라이언트)인CloudFileManager는 수정에는 닫혀있고, 실제 구현은 확장에 열려있다.

⭐️ OCP(Open-Closed Principle)를 준수한다!

3 상속보다는 조립

  • 상속은 주로 상위 클래스의 기능을 재사용, 확장하는 방법으로 활용한다.

3.1 상속의 단점

  • 상위 클래스(부모 클래스)의 변경이 어려움
    > 상위 클래스를 조금만 변경해도 하위 클래스 모두가 영향을 받아 변경이 전파된다.
  • 새로운 조합에 따른 클래스 증가
    > 조합이 증가할 때마다 하위 클래스가 늘어나는 문제가 있다. 또한, 어떤 것을 상속해야할 지 애매하다.
  • 상속오용
    > 불필요한 기능까지 모두 상속된다. 이는 다른 사람에게 혼란을 줄 수 있다.

3.2 조립

위의 단점을 해결하는 방법이 조립입니다.

  • 여러 객체를 묶어서 더 복잡한 기능을 제공한다.
  • 보통 필드로 다른 객체를 참조하는 방식으로 조립하거나, 객체를 필요 시점에 생성/구함
  • 하위 클래스를 변경하는데 있어서 상속에 비해 자유로워진다.
왼쪽은 상속, 오른쪽이 조립
luggages를 표현하기 위해 조립을 사용한다.

3.2.1 언제 조립, 상속을 해야할까?

  • 기능 재사용을 위해서는 조립이 상속보다 장점이 많다.
  • 그렇기때문에 상속하기에 앞서 조립으로 풀 수 없는지 검토해야한다.
  • 진짜 하위 타입인 경우에만 상속을 사용한다.

4 기능과 책임 분리

4.1 기능 분해와 기능 제공

  • 기능은 하위 기능으로 분해한다.
  • 기능은 곧 책임이다. 그렇기 때문에 분리한 각 기능을 알맞게 분배한다.
참고로 Service가 주로 전체 흐름을 제공한다.

4.2 큰 클래스, 큰 메서드

  • 큰 클래스나 메서드가 커지면 절차 지향의 문제가 발생한다.
  • 큰 클래스: 많은 필드를 많은 메서드가 공유한다.
  • 큰 메서드: 많은 변수를 많은 코드가 공유한다.
  • 즉, 여러 기능이 한 클래스/메서드에 섞여 있을 가능성이 높아진다.

⭐️ 책임에 따라 알맞게 코드를 분리할 필요가 있다.

5 의존

  • 기능 구현을 위해 다른 구성 요소를 사용하는 것을 의미한다.
    > ex. 객체 생성, 메서드 호출, 데이터 사용
  • 의존은 변경이 전파될 가능성을 의미한다.
    > 의존하는 대상이 바뀌면 바뀔 가능성이 높아진다.
    > ex. 호출하는 메서드의 파라미터가 변경, 호출하는 메서드가 발생할 수 있는 익셉션 타입이 추가

5.1 의존하는 대상은 적을수록 좋다.

  • 위에서 이야기 했듯이 의존은 변경이 전파될 가능성을 말한다.
  • 의존하는 대상이 많다는 것은 “변경이 전파될 가능성을 더욱 높인다.”라고 말할 수 있다.

5.1.1 기능이 많아 의존하는 대상이 많은 경우

  • 한 클래스에서 많은 기능을 제공하는 경우
  • 각 기능마다 의존하는 대상이 다를 수 있다.
  • 한 기능 변경이 다른 기능에 영향을 줄 수 있다.

👉 기능별로 분리를 고려해야한다. 이를 통해 각 클래스가 의존하는 대상을 줄일 수 있다.

5.1.2 추상화가 안되어져 있어서 의존하는 대상이 많은 경우

  • 몇 가지 의존 대상을 단일 기능으로 묶어서 생각하면 의존 대상을 줄일 수 있다.
Minwon은 우리가 아는 민원이다;
  • MinwonFactory, MinwonRepository 가 민원 등록을 위한 의존이었다면, 이를 하나의 AutoDebitMinwonRegister 로 기능을 추상화하여 의존하는 대상을 줄일 수 있다.

5.2 의존성 주입(Dependency Injection)

  • 사용처(=클라이언트)가 추상타입 없이 구현을 직접 사용(=의존)하면, 구현이 바뀌면 사용처코드도 변경되어야 한다.
  • 구현(의존 대상)을 직접 의존하지 않는 방법 중 하나가 의존성주입이다.
  • 외부에서 의존객체를 주입한다. 이때, 생성자나 메서드를 이용해 주입한다.
  • 개인적으로 주로 사용하는 스프링 프레임워크는 조립기를 통해서 의존 주입을 처리한다.
    > 조립기는 객체를 생성하고, 의존 주입을 처리한다.

5.2.1 의존성 주입의 장점

  • 상위 타입을 사용할 경우 의존 대상이 바뀌면 조립기(설정)만 변경하면 된다.
  • 의존하는 객체 없이 대역 객체를 사용해서 테스트 가능

--

--