객체지향, 오브젝트

dogyeong·2022년 10월 6일
1

오브젝트를 읽고 정리한 글

응집도

밀접하게 관련된 일만 하고, 다른 일은 다른 객체에게 위임하면 응집도가 높다.

즉, 자기 일만 해야 한다

결합도

다른 모듈에 의존하면 결합도가 높아진다.

캡슐화를 통해 의존성을 낮춰서 결합도를 낮게 만드는 것이 중요

절차적 프로그래밍

데이터(객체)와 프로세스(로직)을 다른 모듈에 위치하는 것

객체지향 프로그래밍

데이터(객체)와 프로세스(로직)을 같은 모듈에 위치하는 것

다형성

동일한 메세지(인터페이스)를 전송하지만, 실제로 수신하는 클래스에 따라 다른 메서드가 실행되는 것

상속을 코드 재사용을 위해 주로 사용한다는 생각은 오해다. 코드(구현)을 재사용하는 것이 아니라 인터페이스(API)를 재사용하기 위해 상속을 사용해야 한다.

상속, 합성

상속은 인터페이스를 재사용하는 것이다.

합성은 로직을 재사용하는 것이다.

상속은 합성보다 훨씬 더 결합도를 높인다. ⇒ 변경하기 힘들게 한다

그러므로 로직을 재사용하기 위해서는 합성을 활용해야 한다.

상속은 다형성을 활용할 때 좋다.

가장 중요한 것

역할, 책임, 협력

협력

어떤 객체의 존재이유를 나타내는 컨텍스트.

협력이 행동(책임)을 결정하고, 행동이 상태를 결정한다.

예를 들면, Movie 클래스가 영화예매라는 협력을 할 때는...

  1. 영화예매(협력)
  2. 영화예매할 때 필요한 요금계산 로직(행동, 책임)
  3. 요금계산 로직에 필요한 기본 요금, 할인 정책(상태)

책임

객체가 아는 것 + 객체가 하는 것

객체에게 적절한 책임을 할당하는 것이 객체지향에서 가장 중요

객체지향이 추구하는 것

쉽게 변경할 수 있는 소프트웨어를 만드는 것

→ 그러기 위해서는 응집도가 높고 결합도가 낮아야 한다

→ 그러기 위해서는 캡슐화를 잘 해야 한다

→ 그러기 위해서는 역할, 책임, 협력의 관점에서 설계해야 한다 (책임 주도 설계)

  • 데이터 관점에서 설계하면 캡슐화에 실패하게 된다

GRASP 패턴

책임 주도 설계를 위한 방법론 

책임 할당 과정 (Information expert 패턴)

  1. 도메인을 모델링한다. 정확할 필요없이 시작하는데 도움을 주는 수준으로 모델링한다
  2. 사용자에게 제공하는 소프트웨어의 기능을 메세지로 만든다
    • 메세지를 전송하는 객체가 원하는 것 = 메세지
      (ex. 영화예매 프로그램 : 예매하라)
  3. 메세지를 수신할 객체를 선택한다
    • 해당 메세지를 수행하기 위해 필요한 정보를 알고있는 객체를 선택한다
      (ex. 예매하라 메세지 → 상영정보를 나타내는 Screening 객체로 전달)
    • 수신받은 객체가 모든 정보를 저장하고 있을 필요는 없다. 그 정보를 가지고 있는 다른 객체를 알아도 된다
  4. 메세지를 수신받은 객체가 처리할 수 없는 작업이 있을 때, 그 작업을 처리할 새로운 메세지를 만들어 다른 객체에 책임을 할당한다
    (ex. Screening객체는 영화가격을 모르기 때문에 계산하라라는 메세지를 만들어 Movie객체로 전달)

응집도, 결합도

  • 객체 사이에 메세지를 주고받는 설계는 여러가지가 될 수 있다
  • 이 때 응집도와 결합도를 고려해야한다

객체를 생성해야 하는 경우(Creator 패턴)

어떤 객체 A를 생성해야 할 때 아래 조건을 최대한 많이 만족하는 객체 B에게 객체 생성 책임을 할당한다

  • B가 A 객체를 포함하거나 참조한다
  • B가 A 객체를 기록한다
  • B가 A 객체를 긴밀하게 사용한다
  • B가 A 객체를 초기화하는데 필요한 데이터를 가지고 있다

설계 개선하기

응집도가 낮은 클래스 찾기

  • 변경 이유가 두 개 이상인 클래스 찾기
  • 클래스의 속성들이 한꺼번에 초기화되는지 확인. 초기화되는 속성 그룹을 기준으로 클래스를 분리
  • 메소드들이 어떤 속성들을 사용하는지 확인. 사용되는 속성을 기준으로 클래스를 분리

다른 설계 방법론: TDD

  • 먼저 절차지향적으로 코드를 작성
  • 작성된 코드를 리팩토링하면서 객체지향으로 변경해 나가는 방식
    1. 메소드들을 잘게 쪼갠다
    2. 쪼개진 메소드들을 적절한 클래스로 나눈다. 메소드에서 주로 접근하는 속성(데이터)에 따라 나눈다

객체지향 프로그래밍의 여러가지 팁

  • 디미터 법칙
    • 가장 직설적으로 말하면 닷(.)을 하나만 사용해야 한다는 것
    • 객체가 가지고 있거나, 파라미터로 넘겨받은 객체에만 접근해야 한다
  • 묻지 말고 시켜라 법칙(Tell, Don’t Ask) : 다른 객체의 상태를 묻지 말고 알아서 하게 시켜라
  • 의도를 드러내는 인터페이스

7장. 객체분해

추상화 관점에서의 프로그래밍 패러다임

  • 모든 프로그래밍 패러다임은 추상화와 분해의 관점에서 설명할 수 있다
  • 추상화 매커니즘은 프로시저 추상화와 데이터 추상화로 나뉜다.
    • 프로시저 추상화 중심 ⇒ 기능 분해 또는 알고리즘 분해
    • 데이터 추상화 중심
      • 타입 추상화 ⇒ 추상 데이터 타입 (ADT)
      • 데이터 중심으로 프로시저 추상화 ⇒ 객체지향 (OOP)

기능분해 vs 객체지향

  • 일반적으로 객체지향이 전통적인 기능분해보다 효과적이라고 평가받음
  • 기능 분해 방식은 하나의 시스템을 탑-다운 방식으로 여러 프로시저로 잘게 쪼개는 방식
    • 최상위의 추상적인 함수를 정의하고 그 함수를 구체적인 여러 함수로 분해하여 구현
  • 하지만 기능분해가 가지는 하향식 접근법은 변경에 취약한 설계를 낳는다
  • 하향식 접근법은 비즈니스 로직과 사용자 인터페이스의 결합을 강요한다

모듈

  • 하향식 접근법(기능 분해)는 데이터 변경에 취약하다
  • 변경에 대한 영향을 최소화하기 위해 영향을 받는 부분과 아닌 부분을 분리하고, 퍼블릭 인터페이스를 통해 접근을 통제하는 방식이 필요
  • 이와 같은 필요성에 의해 정보 은닉모듈 개념이 제시됨
  • 모듈을 통해 얻는 장점
    • 변경이 외부에 영향을 끼치는 것을 차단
    • 로직과 인터페이스의 관심사 분리
    • 네임스페이스 오염 방지

추상 데이터 타입

  • 프로시저 추상화의 한계를 극복하기 위해 제안된 데이터 추상화의 방법
  • 데이터와 오퍼레이션의 집합인 타입을 정의하고, 여러개의 타입 인스턴스를 생성하는 방식
  • 제공된 오퍼레이션을 통해서만 조작할 수 있도록 하여 데이터를 외부로부터 보호한다

클래스

  • 추상 데이터 타입은 상속과 다형성을 지원하지 못하기 때문에, 클래스는 추상 데이터 타입이 아니다
  • 추상 데이터 타입은 오퍼레이션을 기준으로 타입을 묶는 타입 추상화 기법
  • 클래스는 타입을 기준으로 오퍼레이션을 묶는 절차 추상화 기법

8장. 의존성 관리하기

의존성이란

  • 구현 시점 : 의존 대상 객체가 변경될 경우 의존하는 객체도 함께 변경된다.
  • 실행 시점 : 의존하는 객체가 정상적으로 동작하기 위해서는 실행 시에 의존 대상 객체가 반드시 존재해야 한다.

의존성 전이

  • 의존하고 있는 대상이 의존하는 것에 간접적으로 의존하게 되는 것을 의존성 전이라고 한다
  • 의존성이 실제로 전이될지 여부는 변경의 방향과 캡슐화의 정도에 따라 달라진다
  • 의존성 전이 때문에 의존성의 종류를 직접 의존성과 간접 의존성으로 나누기도 한다

런타임 의존성과 컴파일타임 의존성

  • 런타임 : 코드가 실행되는시점
  • 컴파일타임 : 코드를 컴파일하는 시점. 문맥에 따라서는 코드 그 자체를 가리키기도 한다
  • 런타임 의존성은 객체 사이의 의존성을 다루고, 컴파일타임 의존성은 클래스 사이의 의존성을 다룬다
  • 런타임 의존성과 컴파일타임 의존성이 서로 다를 수 있다
  • 유연한 설계를 위해서는, 동일한 소스코드 구조를 가지고 다양한 실행 구조를 만들 수 있어야 한다

의존성 해결

  • 컴파일타임 의존성을 실행 컨텍스트에 맞는 적절한 런타임 의존성으로 교체하는 것
  • 일반적으로 사용되는 방법
    • 객체를 생성하는 시점에 생성자를 통해 의존성 해결
    • 객체 생성 후 setter 메서드를 통해 의존성 해결
    • 메서드 실행 시 인자를 이용해 의존성 해결

의존성과 결합도

  • 다양한 환경에서 재사용을 쉽게 할 수 있는 의존성이 바람직하다
  • 그러한 의존성을 가질 때, 느슨한 결합도를 가진다
  • 느슨한 결합도를 위해서는 의존하는 대상의 필요한 정보 외에는 감추는 것이 중요하다
  • 그 방법이 추상화

9장. 유연한 설계

  • 개방-폐쇄 원칙은 런타임 의존성과 컴파일타임 의존성에 관한 이야기다
  • 컴파일타임 의존성을 고정시키고 런타임 의존성을 변경하라
  • 변하지 않는 부분을 추상화하고, 추상화에 의존하라

생성 사용 분리

  • 유연한 설계를 위해서는 객체를 생성하는 객체과 사용하는 객체를 분리해야 한다
  • 가장 보편적인 방법은 객체를 생성할 책임을 클라이언트로 옮기는 것
  • 하지만 클라이언트도 생성과 사용을 같이 하게된다면? 좋지 않은 설계가 된다
  • 그래서 객체의 생성만 담당하는 factory 패턴을 적용할 수 있다

순수한 가공물

  • 도메인 모델링을 추상화를 이용해 애플리케이션을 구축하다보면, 도메인 개념이 유연하지 못한 경우가 생긴다
  • 그럴 때, 도메인 개념을 포함하지 않는, 인위적으로 만든 클래스(순수한 가공물)에게 책임을 할당하라
  • 한 예로, 위의 factory 패턴이 순수한 가공물이다

의존성 주입

  • 8장의 의존성 해결을 위한 수단을 의존성 주입이라고 부른다
  • 의존성 주입에는 대표적으로 3가지 방법이 있다
    • 생성자 주입
    • setter 주입
    • 메서드 주입

의존성 역전 원칙

  • 상위 수준의 모듈은 하위 수준의 모듈에 의존해서는 안된다. 둘 다 추상화에 의존해야 한다
  • 하위 수준의 모듈이 추상화에 의존하면 의존성 방향이 반대가 되는데, 이를 의존성 역전이라고 한다
  • 상위 수준의 협력 흐름을 재사용하기 위해서는 추상화가 제공하는 인터페이스의 소유권 역시 역전시켜야 한다
  • 의존성을 역전시켜야만 유연하고 재사용 가능한 설계를 얻을 수 있다

10장. 상속과 코드 재사용

상속과 중복코드

  • 중복 코드는 코드를 수정하는 데 필요한 노력을 몇 배로 증가시키는 문제를 야기한다
  • 따라서 DRY 원칙을 따라야 한다
  • 두 클래스 사이의 중복을 제거하는 방법으로 타입 코드를 사용하거나, 상속을 이용하는 방법이 있다

취약한 기반 클래스 문제

  • 상속은 자식 클래스가 부모 클래스의 세부사항에 강하게 결합하게 만든다
  • 이처럼 부모 클래스의 변경에 자식 클래스가 영향을 받는 현상을 취약한 기반 클래스 문제라고 한다
  • 상속은 코드 재사용을 위해 캡슐화를 희생한다. 완벽한 캡슐화를 원한다면 코드 재사용을 포기하거나 상속 이외의 다른 방법을 사용해야 한다

11장. 합성과 유연한 설계

  • 합성은 구현에 의존하지 않는다는 점에서 상속과 다르다
  • 합성은 내부에 포함되는 객체의 구현이 아닌 퍼블릭 인터페이스에 의존한다
  • 객체 합성이 클래스 상속보다 더 좋은 방법이다

믹스인

  • 합성이 실행시점에 객체를 조합하는 재사용 방법이라면 믹스인은 컴파일 시점에 필요한 코드 조각을 조합하는 방법이다

12장. 다형성

  • 상속의 목적은 코드 재사용이 아니다. 상속은 타입 계층을 구조화하기 위해 사용해야 한다

다형성

  • 오버로딩 다형성 : 한 클래스 안에 여러 시그니처를 가지는 동일한 이름의 메서드가 존재하는 경우

  • 강제 다형성 : 언어가 지원하거나, 사용자가 직접 구현하여 동일한 연산자를 여러 타입에서 사용할 수 있는 경우 (ex. + 연산자는 타입에 따라 다르게 동작한다)

  • 매개변수 다형성 : 제네릭. 임의의 타입을 지정한 후, 사용할 때 구체적인 타입으로 지정하는 방식

  • 포함 다형성 : 메세지가 동일하더라도, 수신한 객체에 따라 실제로 수행되는 행동이 달라지는 것. 객체지향에서 다형성 이라고 하면 일반적으로 포함 다형성을 의미한다

  • 포함 다형성을 위해서는 서브타입 계층을 구축해야 한다

  • 상속은 서브타입 구축을 위한 방법이다

  • 상속 외에도 서브타입 관계를 만들 수 있는 다양한 방법이 존재한다

업캐스팅

  • 상속을 이용하면 부모 클래스의 퍼블릭 인터페이스가 자식 클래스의 퍼블릭 인터페이스에 합쳐지기 때문에 부모 클래스의 인스턴스에게 전송할 수 있는 메세지를 자식 클래스의 인스턴스에게 전송할 수 있다.
  • 이런 특성을 이용할 수 있는 대표적인 두 가지가 대입문과 메서드의 파라미터 타입이다
  • 반대로 부모 클래스의 인스턴스를 자식 클래스 타입으로 변환하기 위해서는 명시적인 타입 캐스팅이 필요한데 이를 다운캐스팅이라고 부른다

동적 바인딩

  • 객체지향 언어에서는 메세지를 수신했을 때 실행될 메서드가 런타임에 결정된다
  • 이처럼 실행될 메서드를 런타임에 결정하는 방식을 동적 바인딩 또는 지연 바인딩이라고 부른다
  • 객체지향 시스템은 다음 규칙을 따라 실행할 메서드를 선택한다
    • 메세지를 수신한 객체의 클래스에 적합한 메서드가 존재하는지 검사한다. 존재하면 메서드를 실행한다
    • 메서드를 찾지 못했다면 상속 계층을 따라 찾을 때까지 부모 클래스로 처리가 위임된다
    • 상속 계층의 가장 최상위 클래스에 이르렀지만 메서드를 발견하지 못한 경우 예외를 발생시킨다
  • self 또는 this 키워드로 메세지를 수신한 객체를 참조한다
  • super 키워드는 현재 클래스의 부모 클래스부터 메서드 탐색을 시작할 때 사용한다

13장. 서브클래싱과 서브타이핑

타입

  • 객체지향 패러다임에서 타입이란 호출 가능한 오퍼레이션(메세지)의 집합을 정의한다
  • 객체가 수신할 수 있는 메세지의 집합은 퍼블릭 인터페이스와 동일하다
  • 그러므로, 객체지향 프로그래밍에서 타입을 정의하는 것은 퍼블릭 인터페이스를 정의하는 것과 동일하다
  • 동일한 퍼블릭 인터페이스를 제공하는 객체들은 동일한 타입으로 분류된다

타입 계층

  • 타입은 집합이므로 다른 타입을 포함할 수 있다
  • 슈퍼타입은 하위 타입의 모든 멤버를 포함하고, 타입 정의가 다른 타입보다 좀 더 일반적이다
  • 서브타입은 집합의 인스턴스들이 더 큰 집합에 포함되고, 타입 정의가 다른 타입보다 좀 더 구체적이다
  • 객체지향의 퍼블릭 인터페이스의 관점에서는 다음과 같이 정의할 수 있다
    • 슈퍼타입이란 서브타입의 퍼블릭 인터페이스를 일반화시켜 상대적으로 범용적이고 넓은 의미로 정의한 것
    • 서브타입이란 슈퍼타입의 퍼블릭 인터페이스를 특수화시켜 상대적으로 구체적이고 좁은 의미로 정의한 것

서브클래싱과 서브타이핑

  • 객체지향에서 타입 계층을 구현하는 일반적인 방법은 클래스의 상속을 이용하는 것이다

  • 언제 상속을 사용해야 하는가?

    • 클라이언트의 입장에서, 부모 클래스의 타입으로서 자식 클래스를 사용해도 무방할 때
    • 즉, 클라이언트 입작에서 두 타입이 동일하게 행동할 것이라고 기대한다면 두 타입을 타입 계층으로 묶을 수 있다
  • 클라이언트의 기대에 따라 계층 분리하는 방법으로 인터페이스를 분리하는 방법이 있다

  • 클라이언트에 따라 인터페이스를 분리하면 변경의 파급 효과를 효과적으로 제어할 수 있게 되고,

  • 이처럼 인터페이스를 클라이언트의 기대에 따라 분리하는 설계 원칙을 인터페이스 분리 원칙(ISP)라고 부른다

  • 서브클래싱

    • 다른 클래스의 코드를 재사용할 목적으로 상속하는 경우
    • 자식 클래스가 부모 클래스의 인스턴스를 대체할 수 없다
    • 구현 상속 또는 클래스 상속이라고 부른다
  • 서브타이핑

    • 타입 계층을 구성하기 위해 상속을 사용하는 경우
    • 자식 클래스가 부모 클래스의 인스턴스를 대체할 수 있다
    • 인터페이스 상속이라고 부른다
  • 서브타이핑 관계가 유지되기 위해서는 서브타입이 슈퍼타입의 모든 행동을 동일하게 할 수 있는 행동 호환성, 모든 문맥에서 자식 클래스가 부모 클래스를 대체할 수 있는 대체 가능성을 만족시켜야 한다

  • 이러한 원칙을 리스코프 치환 원칙(LSP) 이라고 한다

14장. 일관성 있는 협력

  • 가능하면 유사한 기능을 구현하기 위해 유사한 협력 패턴을 사용하라
  • 객체들의 협력이 전체적으로 일관성있는 유사한 패턴을 따른다면 시스템을 이해하고 확장하기 쉽다

설계에 일관성 부여하기

  • 일관성 있는 설계를 만들기 위해서는 다양한 설계 경험을 익혀야 한다
  • 그리고 널리 알려진 디자인 패턴을 학습하고, 변경이라는 문맥 안에서 디자인 패턴을 적용해 보아야 한다
  • 일관성 있는 협력을 위한 지침
    • 변하는 개념을 변하지 않는 개념으로부터 분리하라
    • 변하는 개념을 캡슐화하라

15장. 디자인 패턴과 프레임워크

패턴

  • 실무 컨텍스트에서 유용하게 사용해 왔고 다른 실무 컨텍스트에서도 유용할 것이라고 예상되는 아이디어

패턴의 분류

  • 아키텍처 패턴

  • 분석 패턴

  • 디자인 패턴

  • 이디엄

  • 패턴마다 캡슐화하는 것이 있다. 디자인 패턴을 볼 때 무엇을 캡슐화하는지 생각해보자

프레임워크

  • 프레임워크란 개발자가 현재의 요구사항에 맞게 커스터마이징할 수 있는 애플리케이션의 골격을 의미한다
  • 디자인 패턴이 설계의 재사용이라면, 프레임워크는 설계 재사용과 코드 재사용을 적절한 수준으로 조합한다
  • 상위 정책과 하위 세부구현을 분리하면 바뀌지 않는 상위 정책의 협력 관계를 재사용할 수 있다. 이 부분이 프레임워크다

제어 역전

  • 의존성을 역전시킨 객체지형 구조에서는 반대로 프레임워크가 애플리케이션에 속하는 서브클래스의 메서드를 호출한다
  • 이렇게 의존성을 역전시키면 제어 흐름의 주체 역시 역전되는 것을 제어 역전 원리(IoC) 또는 할리우드 원리라고 한다
profile
Engineer

0개의 댓글