[오브젝트] 4일차

da__ell·2023년 8월 3일
0

독서 - 오브젝트

목록 보기
4/25
post-thumbnail

p.34 ~ p.50

객체지향 설계

설계가 왜 필요한가?

설계란 코드를 배치하는 것이다.

이전에 책에서 제시했던 예시 코드의 변경도 실행한 결과는 같았다. 다만 코드의 배치가 달랐을 뿐이다.

이를 통해 두 프로그램은 서로 다른 설계를 갖게 된다.

좋은 설계는 두 가지 조건을 충족해야 한다.

  1. 요구 기능을 온전히 수행하는 것
  2. 변경이 용이할 것

개발이 시작 될 때 모든 요구사항을 수집할 수 없을 뿐더러, 모든 요구사항을 수집했다 가정하더라도 요구사항은 변경될 수 밖에 없기 때문이다.

따라서 요구사항의 변경에도 용이한 코드를 작성하는 것이 버그의 발생을 낮추고 코드 수정에 대한 어려움을 줄여준다.

객체지향 설계

변경가능한 설계란 변경이 유연함과 동시에 이해하기 쉬워야 하는 조건을 만족해야 한다.

객체 지향 패러다임은 자신의 데이터를 스스로 책임지는 자율적인 존재인 객체를 통해 이해하기 쉽게 코드를 작성할 수 있도록 도와준다.

객체 지향적으로 설계된 어플리케이션은 이 객체들의 각자의 책임을 수행하고 객체들의 상호작용을 통해 애플리케이션의 기능이 구현이 된다. 이 협력의 과정을 메시지로 표현할 수 있다. 이 과정에서 객체들은 다른 객체에 대한 의존이 발생하게 된다.

좋은 객체지향 설계는 이 협력하는 객체들 사이의 의존성을 적절히 관리하여 변경에 용이한 설계로 만드는 것이다.


객체지향 프로그래밍

협력, 객체, 클래스

객체지향 프로그래밍에서 대부분의 사람들은 클래스를 결정하고 클래스에 어떤 속성과 메서드가 필요한지 고민한다. 하지만 이것은 객체지향의 본질과 거리가 멀다.

클래스가 아닌 객체에 초점

  1. 클래스를 고민하기 전 어떤 객체가 필요한지 고민하라
    클래스는 공통적인 상태와 행동을 공유하는 객체를 추상화하는 것
    어떤 객체가 어떤 상태와 행동을 가지는지 먼저 결정하라
  2. 객체를 기능을 구현하기 위해 협력하는 공동체의 일원으로 인식하라
    객체는 독립적인 존재가 아닌 다른 객체와 상호작용하고 의존하는 협력적 존재
    객체에 대한 윤곽을 잡고 → 공통된 특성과 상태를 가진 객체를 타입으로 분류 → 이 타입을 기반으로 클래스를 구현하라.

도메인

소프트웨어는 사용자가 원하는 특정 문제를 해결하기 위해 만들어진 것이다. 이 문제를 해결하기 위해 사용자가 프로그램을 사용하는 분야를 도메인이라 한다.

일반적으로 클래스의 이름은 대응하는 도메인 개념의 이름과 동일하거나 적어도 유사하게 지어야 한다
클래스 사이의 관계도 최대한 도메인 개념 사이에 맺어진 관계와 유사하게 만들어서 프로그램의 구조를 이해하고 예상하기 쉽게 만들어야 한다.

예를 들어 영화 예매 도메인을 구현한다고 할 때 상영에 대한 클래스 구현을 한다고 하면 이 개념과 최대한 동일하게 Screening 클래스로 구현하는 것이 적절할 것이다.

클래스 구현

클래스를 구현하거나 다른 개발자가 개발한 클래스를 사용할 때 중요한 것은 클래스의 경계를 구분짓는 것

훌륭한 클래스를 설계하기 위한 핵심은 클래스를 내부와 외부로 구분하고 어떤 부분을 외부에 공개하고 어떤 부분을 감출지 결정하는 것이다.

외부에서는 객체의 속성에 직접 접근할 수 없도록 막고, 적절한 public 메서드를 통해 내부 상태를 변경할 수 있도록 해야한다.

클래스의 내부와 외부를 구분해야하는 이유 → 경계의 명확성이 객체의 자율성을 보장하기 때문이다. 그리고 구현의 자유를 제공한다.

자율적인 객체

  1. 객체는 상태와 행동을 함께 가지는 복합적 존재이다.
  2. 객체는 스스로 판단하고 행동하는 자율적 존재이다.

대부분의 객체지향 프로그래밍 언어는 캡슐화를 통해 데이터와 기능을 객체 내부로 묶는 캡슐화에서 더 나아가 외부에서 접근을 통제할 수 있는 접근제어 메커니즘도 제공한다.

이 접근 제어를 위해 public, protected, private와 같은 접근 수정자(access modifier)를 제공한다.

객체 내부에 대한 접근을 통제하는 이유는 앞서 말한 객체지향의 핵심을 지키기 위해서다. 외부에서 객체에 대한 직접적인 개입을 하지 않고 객체에게 필요한 것을 요청하고 객체 스스로 결정하도록 맡겨야 한다.

캡슐화와 접근제어는 객체를 외부와 내부로 나누는데. 외부에서 접근 가능한 부분을 퍼블릭 인터페이스(public interface)로, 내부에서만 접근 가능한 부분을 구현(implementation)으로 명명한다.

인터페이스와 구현을 분리하는 것은 휼륭한 객체지향 프로그램을 만들기 위해 따라야하는 핵심 원칙이다.

클래스의 속성과 내부 구현에 포함되는 메서드는 private이나 public으로 선언하여 감추고, 외부에 제공하는 일부 메서드 즉 퍼블릭 인터페시스는 public으로 선언해야 한다.

구현 은닉

프로그래머의 역할을 클래스 작성자(class creator)와 클라이언트 프로그래머(client programmer)로 구분하는 것이 유용하다.

클래스 작성자는 새로운 데이터 타입을 프로그램에 추가하고 클라이언트 프로그래머는 클래스 작성자가 추가한 데이터 타입을 활용한다.

이때 접근 제어 매커니즘을 통해 클래스의 내부와 외부를 명확하게 경계 짓는 것이 구현 은닉(implementation hiding)이라 부른다.

구현 은닉을 통해 클래스 작성자는 외부에 미칠 영향을 걱정하지 않고 내부 구현을 변경할 수 있고, 클라이언트 프로그래머는 인터페이스만 알고 있어도 클래스를 사용할 수 있기 때문에, 지식의 양을 줄일 수 있다.

따라서 클래스를 개발할 때마다 인터페이스와 구현을 깔끔하게 분리하도록 노력해야 한다.

협력하는 객체들의 공동체

의미의 명시적 표현

객체지향의 장점은 객체를 이용해 도메인의 의미를 풍부하게 표현할 수 있다는 것이다.
객체를 사용해서 해당 개념을 구현함으로써 개념의 의미를 명시적으로 표현할 수 있다면, 전체적인 설계의 명확성과 유연성을 높이는 방법이 된다.

예를 들어 금액을 구현하기 위해 Long 타입을 사용하는 것보다, Money 클래스를 구현하여 저장하는 값이 금액과 관련되어 있다는 의미를 전달할 수 있다. 그리고 금액과 관련된 로직이 서로 다른 곳에 중복되는 것도 방지할 수 있다.

  • 구현

    public class Money {
    
        private final BigDecimal amount;
    
        public Money(BigDecimal amount) {
            this.amount = amount;
        }
    
        public static Money wons(long amount) {
            return new Money(BigDecimal.valueOf(amount));
        }
    
        public Money plus(Money amount) {
            return new Money(this.amount.add(amount.amount));
        }
    
        public BigDecimal getAmount(Money money) {
            return money.amount;
        }
    }
    
    public class test {
        public static void main(String[] args) {
            Money item1 = Money.wons(1000);
            Money item2 = Money.wons(2000);
            Money total = item1.plus(item2);
            System.out.println(total.getAmount(total) + "원");
    		// 3000원
        }
    }

협력에 대한 단상

시스템의 어떤 기능을 구현하기 위해 객체들의 상호작용을 협력(Collaboration)이라 부른다.

객체지향 프로그래밍에서는 협력의 관점에서 어떤 객체가 필요한지 결정하고, 객체의 공통 상태와 행위를 구현하기 위해 클래스를 만든다.

객체의 내부상태는 접근제어자를 통해 외부에서 접근하지 못하도록 감춰져있다. 외부에 공개된 퍼블릭 인터페이스를 통해 내부상태에 접근할 수 있도록 요청하고, 그 요청을 받은 객체가 스스로 요청을 처리하고 응답하는 형태이다.

이 요청을 메시지를 전송한다(send a message)라고 표현하고, 요청이 도착하는 것을 메시지를 수신한다(receive a message)라고 표현한다. 수신된 메시지를 처리하기 위한 객체의 처리 방법을 메서드로 표현한다.

메시지와 메서드를 구분하는 것이 중요하다.

profile
daelkdev@gmail.com

0개의 댓글