7장 정리

사람은 문제 해결에 필요한 요소의 수가 단기 기억의 용량을 초과하는 순간 문제 해결 능력이 급격하게 떨어진다. 이를 인지 과부하라고 부른다.
이런 인지 과부하를 피하기 위해 불필요한 정보를 제거하고 현재의 문제 해결에 필요한 핵심만 남기는 작업을 추상화라고 한다.
맞닥뜨린 문제가 한 번에 해결하기 어려울 정도로 크다면 해결 가능한 작은 문제로 나누는 작업을 분해라고 한다.
인류는 이런 추상화와 분해를 통해 복잡한 분야의 문제를 해결해왔다.

프로시저 추상화와 데이터 추상화

어셈블리어, 고수준 언어 같은 프로그래밍 언어의 발전은 효과적인 추상화를 이용해 복잡성을 극복하려는 개발자의 노력에서 출발했다.
프로그래밍 언어를 통해 표현되는 추상화의 발전은 다양한 프로그래밍 패러다임의 탄생으로 이어졌다.
프로그래밍 패러다임은 프로그래밍을 구성하기 위해 사용되는 추상화의 종류와 이 추상화를 이용해 소프트웨어를 분해하는 방법의 두 가지 요소로 결정되며, 따라서 추상화와 분해의 관점에서 설명할 수 있다.

현대적인 프로그래밍 언어를 특징 짓는 중요한 두 가지 추상화 메커니즘은 프로시저 추상화와 데이터 추상화이다.

 

프로시저 추상화

  • 소프트웨어가 무엇을 해야 하는지를 추상화한다.

데이터 추상화

  • 소프트웨어가 무엇을 알아야 하는지를 추상화한다.

프로그래밍 패러다임이란 적절한 추상화의 윤곽을 따라 시스템을 어떤 식으로 나눌 것인지를 결정하는 원칙과 방법의 집합이다.
시스템을 분해하는 방법을 결정하려면 프로시저 추상화를 중심으로 할 것인지, 데이터 추상화를 중심으로 할 것인지를 결정해야 한다.

 

프로시저 추상화 중심

  • 기능분해

데이터 추상화 중심

  • 데이터를 중심으로 타입을 추상화 하면 추상 데이터 타입
  • 데이터를 중심으로 프로시저를 추상화하면 객체지향

객체지향 패러다임은 역할과 책임을 수행하는 자율적인 객체들의 협력 공동체를 구축하는 것이다.
객체지향 패러다임에서 '역할과 책임을 수행하는 객체'가 추상화를 의미하고, 기능을 '협력하는 공동체'를 구성하도록 객체들로 나누는 과정이 분해를 의미한다.

하향식 기능 분해의 문제점

전통적인 기능 분해 방법은 시스템을 구성하는 가장 최상위 기능을 정의하고, 여기서부터 좀 더 작은 단계의 하위 기능으로 분해하는 하향식 접근법을 따른다.
분해는 세분화된 마지막 하위 기능이 프로그래밍 언어로 구현 가능한 수준이 될 때 까지 계속되며, 각 세분화 단계는 바로 위 단계보다 더 구체적이어야 한다.
하향식 기능 분해는 시스템을 최상위의 가장 추상적인 메인 함수로 정의하고, 메인 함수를 구현 가능한 수준까지 세부적인 단계로 분해한다.
이상적인 방법으로 보일 수 있지만 우리가 사는 세계는 그렇게 체계적이지도, 이상적이지도 않기 때문에 아래와 같은 다양한 문제가 발생한다.

  • 시스템은 하나의 메인 함수로 구성돼 있지 않다.
  • 기능 추가나 요구사항 변경으로 인해 메인 함수를 빈번하게 수정해야 한다.
  • 비즈니스 로직이 사용자 인터페이스와 강하게 결합된다.
  • 하향식 분해는 너무 이른 시기에 함수들의 실행 순서를 고정시키기 때문에 유연성과 재사용성이 저하된다.
  • 데이터 형식이 변경될 경우 파급 효과를 예측할 수 없다.

데이터 추상화와 추상 데이터 타입 / 클래스

타입을 사용했지만
초기 절차형 프로그래밍 언어에서는 적은 수의 내장타입만을 제공했고, 새로운 타입을 추가하는 것이 불가능하거나 제한적이었다.
프로시저 추상화로는 프로그램의 표현력을 향상시키는데 한계가 있었고, 이를 보완하기 위해 데이터 추상화의 개념이 나왔다.

추상 데이터 타입과 클래스의 차이는 아래와 같다.

  • 추상 데이터 타입은 오퍼레이션을 기준으로 타입을 묶는다.
  • 클래스는 타입을 명시적으로 정의하고 타입의 유형을 기준으로 오퍼레이션을 지정한다.
    클래스는 타입을 기준으로 절차들을 추상화 함으로써 다형성을 이용할 수 있다.

객체지향은 단순히 클래스를 구현 단위로 사용하는 것을 의미하지 않는다.

클래스를 사용하더라도 타입을 기준으로 절차를 추상화하지 않았다면 그것은 객체지향 분해가 아니다.
객체지향에서는 클라이언트가 객체의 타입을 확인한 후 적절한 메서드를 호출하는 것이 아니라, 객체가 메시지를 처리할 적절한 메서드를 선택한다.
객체지향에서는 타입 변수를 이용한 조건문을 다형성으로 대체하고, 이로써 새로운 로직을 추가해도 클라이언트 코드를 수정하지 않아도 된다.
하지만 항상 절차를 추상화하는 객체지향 설계 방식을 따라야하는 것은 아니다.
설계는 변경과 관련된 것이므로 변경의 방향성과 발생 빈도에 따라 결정해야한다.
변경의 방향성이 타입 추가인지, 오퍼레이션 추가인지에 따라 추상 데이터 타입과 클래스를 적절하게 선택해야 한다.
타입 계층과 다형성은 협력이라는 문맥 안에서 책임을 수행하는 방법에 관해 고민한 결과물이어야 하며, 그 자체가 목적이 되어서는 안 된다.

인상깊었던 점

"다형성은 협력이라는 문맥 안에서 책임을 수행하는 방법에 관해 고민한 결과물이어야 하며, 그 자체가 목적이 되어서는 안 된다."
이번 주에는 마지막 쪽에 있던 이 말이 가장 와닿았다.
항상 코드를 짤 때 "재사용 할 수 있고 변경에 더 좋은 설계를 고민해야 한다"는 생각을 하는데, 최근 들어 필요해서 객체지향 설계를 적용하는게 아닌, 객체지향 자체가 목적이 된 것 같다는 생각이 자주 들었다.
객체지향적으로 코드를 짤 땐 과연 쓸데 없는 노력을 쓰는건 아닌지 다시 생각을 해보면서 주의해야겠다는 생각이 들었다.

'공부방 > 북스터디' 카테고리의 다른 글

[오브젝트] 9장  (0) 2024.02.20
[오브젝트] 8장  (0) 2024.02.06
[오브젝트] 6장  (0) 2024.01.23
[오브젝트] 5장  (0) 2024.01.15
[오브젝트] 4장  (0) 2024.01.05

6장 정리

애플리케이션은 클래스로 구성되지만 메시지를 통해 정의된다는 사실을 기억해야 한다.
객체지향 프로그래밍의 애플리케이션에서는 클래스라는 구현 도구가 아닌 객체들이 주고 받는 메시지에 초점을 둔다.
객체가 수신하는 메시지들이 객체의 퍼블릭 인터페이스를 구성한다.
훌륭한 퍼블릭 인터페이스를 얻기 위해서는 책임 주도 설계 방법 뿐 아니라, 유연하고 재사용 가능한 퍼블릭 인터페이스를 만드는 데 도움이 되는 설계 원칙과 기법을 익히고 적용해야 한다.

인터페이스와 설계 품질

객체가 수신할 수 있는 메시지가 객체의 퍼블릭 인터페이스와 그 안에 포함될 오퍼레이션을 결정한다.
객체의 퍼블릭 인터페이스가 객체의 품질을 결정하기 때문에 결국 메시지가 객체의 품질을 결정한다.

책임 주도 설계 방법은 훌륭한 인터페이스를 얻을 수 있는 지침을 제공하지만, 더 넓은 안목과 올바른 설계를 위해서는 퍼블릭 인터페이스의 품질에 영향을 미치는 설계 원칙과 기법을 익히고 적용해야 한다.

디미터 법칙

흔히들 "낯선 자에게 말하지 말라", 자바같은 언어에서는 "오직 하나의 도트만 사용하라" 라는 말로 요약되는 법칙이다.
디미터 법칙을 따르기 위해서는 클래스가 특정한 조건을 만족하는 대상에게만 메시지를 전송하도록 프로그래밍 해야한다.

  • this 객체
  • 메서드의 매개변수
  • this의 속성
  • this의 속성인 컬렉션의 요소
  • 메서드 내에서 생선된 지역 객체

디미터 법칙을 따르면 불필요한 어떤 것도 다른 객체에게 보여주지 않으며, 다른 객체의 구현에 의존하지 않는 부끄럼타는 코드(shy code) 를 작성할 수 있다.

묻지 말고 시켜라

객체의 상태에 관해 묻지 말고 원하는 것을 시키는 메시지 작성을 장려하는 원칙이다.
묻지 말고 시켜라 원칙을 따르면 객체의 정보를 이용하는 행동을 객체의 외부가 아닌 내부에 위치시키기 때문에 자연스럽게 정보와 행동을 동일한 클래스 안에 두게 된다.
이 원칙을 따른다면 자연스럽게 정보 전문가에게 책임을 할당하게 되고, 높은 응집도를 가진 클래스를 작성할 확률이 높아진다.

의도를 드러내는 인터페이스

훌륭한 인터페이스는 객체가 어떻게 하는지가 아니라 무엇을 하는지를 서술해야 한다.
무엇을 하는지를 드러내는 인터페이스는 코드를 읽고 이해하기 쉽우며, 유연한 코드를 낳는다.
의도를 드러내는 인터페이스를 한 마디로 요약하면 구현과 관련된 모든 정보를 캡슐화하고 객체의 퍼블릭 인터페이스에는 협력과 관련된 의도만을 표현해야 한다는 것이다.

함께 모으기

디미터 법칙, 묻지 말고 시켜라, 의도를 드러내는 인터페이스라는 원칙들을 통해 결합도가 낮으면서도 의도를 명확히 드러내는 간결합 협력을 구현할 수 있다.

원칙의 함정

원칙을 맹목적으로 추종하지 말라.
현재 상황에 어떤 원칙이 부적합하다고 판단된다면 과감하게 원칙을 무시할 필요도 있다.
원칙을 아는 것보다 더 중요한 것은 언제 원칙이 유용하고 언제 유용하지 않은지를 판단할 수 있는 능력을 기르는 것이다.

디미터 법칙은 하나의 도트(.)를 강제하는 규칙이 아니다

스트림이 좋은 예시다.
IntStream.map(o -> o +1).filter(o -> o % 2 == 0) 에서
map, filter 는 IntStream 을 또 다른 IntStream 인스턴스로 변환할 뿐 내부 구조를 노출 시키지 않는다.
디미터 법칙은 결합도와 관련된 것이며, 이 결합도가 문제 되는 것은 객체의 내부 구조가 외부로 노출되는 경우이다.

결합도와 응집도의 충돌

묻지 말고 시켜라와 디미터 법칙을 준수하는 것이 항상 긍정적인 결과로만 귀결되지는 않는다.
맹목적으로 위임 메서드를 추가하면 하나의 객체 안에 상관 없는 책임들이 공존하게 되고, 결과적으로 응집도가 낮아진다.
가끔씩은 묻는 것 외에는 다른 방법이 존재하지 않을 수도 있다. 컬렉션에 포함된 객체들을 처리하는 유일한 방법은 객체에게 물어보는 것이다.
디미터 법칙의 위반 여부는 묻는 대상이 객체인지, 자료 구조인지에 따라 달려있다. 객체의 내부 구조를 드러내는건 문제가 되지만, 자료 구조의 내부를 노출하는건 문제가 되지 않는다.

결국 설계는 트레이드오프의 산물이며, 원칙을 맹목적으로 따르지 말고 언제나 "경우에 따라 다르다"라는 사실을 명심해야 한다.

명령-쿼리 분리 원칙

프로시저 - 부수효과 발생, void
함수 - 부수효과 x, return 값 있음
개념적으로 프로시저는 명령, 함수는 쿼리라고 할 수 있다.
명령은 부수효과를 일으키므로 상태를 변경하고 결과 값을 반환하지 않는다.
쿼리는 몇번을 실행해도 똑같은 결과를 보장할 수 있으므로 결과 값을 반환한다.
명령과 쿼리가 합쳐진 코드는 내부 상태도 변경하고 결과 값도 반환하므로 예측하기 힘들고 디버깅이 쉽지 않다.
따라서 퍼블릭 인터페이스를 설계할 때 명령과 쿼리를 분리하면 예측 가능하고 이해하기 쉬우며 디버깅이 용이하여 유지보수에 수월한 코드를 작성할 수 있다.


인상깊었던 점

"원칙을 아는 것보다 더 중요한 것은 언제 원칙이 유용하고 언제 유용하지 않은지를 판단할 수 있는 능력을 기르는 것이다."
이번 장에서 인상 깊었던 문장은 이 문장이었다.
1장부터 그랬지만 이 책은 "단순히 객체지향은 이렇게 해야한다." 라고 말하기 보다는 객체지향이 목표하는 것을 설명하고, 훌륭한 객체지향 프로그램을 개발하기 위해서 알아두면 좋은 설계 원칙과 기법을 알려주고 있다.
동시에 원칙을 맹목적으로 추종하지 말고 언제 원칙이 유용하고 언제 유용하지 않은지를 판단할 수 있는 능력을 기르는데 초점을 맞추라고 말한다.
무지성 클린코드 추종자처럼 이상론자가 되지 말고 꾸준히 코드를 작성하면서 생각하고, 체감하고, 공부해서 제대로 이해하고 적용하는게 중요하다고 말하는 점이 좋았다.

'공부방 > 북스터디' 카테고리의 다른 글

[오브젝트] 8장  (0) 2024.02.06
[오브젝트] 7장  (0) 2024.01.29
[오브젝트] 5장  (0) 2024.01.15
[오브젝트] 4장  (0) 2024.01.05
[오브젝트] 3장  (0) 2023.12.25

5장 정리

데이터 중심 설계 - 행동보다 데이터를 먼저 결정해서 협력이라는 문맥을 벗어나 고립된 객체의 상태에 초점을 맞추기 때문에 캡슐화를 위반하기 쉽고, 요소들 사이의 결합도가 높아지며, 코드를 변경하기 어려워진다.
그러니까 책임 중심 설계로 가자.

책임 주도 설계

데이터보다 행동을 먼저 결정하라.

초보자들은 객체지향이라고 객체부터 생각하는데 협력을 통해 생기는 책임에 초점을 맞추지 않고
바로 객체의 데이터에 초점을 맞춘다.
하지만 너무 이른 시기에 데이터에 초점을 맞추면 데이터 중심 설계의 단점을 따라가서 변경에 취약해진다.
그러므로 "객체가 포함해야 하는 데이터가 무엇인가" 에서 "객체가 수행해야 하는 책임은 무엇인가" 를 결정하고 이후에 "책임을 수행하는데 필요한 데이터는 무엇인가" 를 결정하라.
즉, 책임을 먼저 결정한 후에 객체의 상태를 결정하라.

협력이라는 문맥 안에서 책임을 결정하라

책임은 객체의 입장이 아니라 객체가 참여하는 협력에 적합해야 한다.
협력을 시작하는 주체는 메시지 전송자이므로 협력에 적합한 책임이란 메시지 수신자가 아닌, 메시지 전송자에게 적합한 책임을 의미한다. 즉, 메시지를 전송하는 클라이언트의 의도에 적합한 책임을 할당해야 한다.
이는 곧 객체를 결정한 후에 메시지를 선택하는 것이 아니라 메시지를 결정한 후에 객체를 선택함을 의미한다.
"메서드를 만들어두고 누가 호출하지?" 대신에 "메시지를 전송해야하는데 누구에게 전송해야하지?" 라고 질문하는것이 필요하다.
이러면 메시지를 먼저 결정하므로 송신자는 메시지 수신자에 대해 모른다. 그러므로 메시지 전송자의 관점에서 메시지 수신자는 캡슐화가 된다.
책임 중심 설계는 이처럼 캡슐화를 지키기 쉬우므로 응집도가 높고 결합도가 낮으며 변경하기 쉽다.

도메인 개념을 정리하는데 너무 많은 시간을 들이지 말고 빠르게 설계와 구현을 진행하라.

올바른 도메인 모델이란 존재하지 않는다. 유연성이나 재사용성 등과 같이 실제 코드를 구현하면서 도메인 개념이 바뀔 수 있기 때문이다.
기본적으로 INFORMATION EXPERT 패턴을 따르면 자율성이 높은 객체들로 구성된 협력 공동체를 구축할 가능성이 높아진다. 하지만 동일한 기능을 구현할 수 있는 무수히 많은 설계가 존재하므로 필요에 따라 적합한 책임 할당 패턴을 고려한다.

구현과 리팩토링

요구사항에 맞춰 코드를 구현하면서 리팩토링을 진행한다.
DiscountCondition 의 예시처럼 하나 이상의 변경 이유를 가지는것은 낮은 응집도를 의미하므로 변경의 이유에 따라 클래스를 분리한다.
응집도가 낮다는 신호가 몇가지 존재한다. 그 중 하나는 초기화 할때 나타난다.
응집도가 높은 클래스는 인스턴스를 생성할 때 모든 속성을 함께 초기화 한다.
반면 응집도가 낮은 클래스는 객체의 속성 중 일부만 초기화하고 일부는 초기화 되지 않은채로 남는다.
따라서 함께 초기화되는 속성을 기준으로 코드를 분리해야 한다.

설계를 주도하는 것은 변경이다. 변경에 대비할 수 있는 방법은 두 가지 방법이 있다.

  1. 코드를 이해하고 수정하기 쉽도록 최대한 단순하게 설계한다.
  2. 코드를 수정하지 않고도 변경을 수용할 수 있도록 코드를 더 유연하게 만든다.
    대부분의 경우 전자가 더 좋은 방법이지만 유사한 변경이 반복적으로 발생하고 있다면 복잡성이 상승하더라도 유연성을 추가하는 두번째 방법이 더 좋다.

책임 주도 설계에 익숙해지기 위해서는 부단한 노력과 시간이 필요하다.

우선 절차적이든, 데이터 중심적이든 동작하는 코드를 작성하고 동작은 유지한 채로 내부 구조를 변경하여 책임 주도 설계 형식이 되도록 리팩토링 하라.
책임 주도 설계 방법을 단계적으로 따르지 않더라도 캡슐화, 결합도, 응집도를 이해하고 객체지향 원칙을 적용하기 위해 노력한다면 충분히 유연하고 깔끔한 코드를 얻을 수 있다.


인상깊었던 점

"도메인 개념을 정리하는데 너무 많은 시간을 들이지 말고 빠르게 설계와 구현을 진행하라."
이번 장에서 저에게 가장 와닿았던 문장은 이 문장이었다.
나는 뭔가를 시작하는게 제일 어렵다고 생각하는데, 이유를 생각해보면 내가 제대로 하는게 맞나? 하는 생각때문인듯 하다.
그런데 책에서도 처음 그렸던 도메인과 구현을 통해 도메인 바뀐 구조를 보여주니까 안심하고 빠르게 시작할 용기가 생겼다.
책임 주도 설계 방법이 좋다는건 모두가 알지만 결국 적용하는데는 부단한 노력과 시간이 필요하다고.
개발 경험이 쌓이면서 더 책임 주도로 설계하고, 객체지향적인 코드를 작성할 수 있다.

그러니까 책을 읽고 여기에 매몰되지 말고 일단 뭐든 해라 라는 말을 하고 싶었던거 같다.
결국 중요한건 캡슐화, 결합도, 응집도를 이해하고 객체지향 원칙을 적용하기 위해 노력하는것 같다.
이런 기본에 충실하면 처음부터 책임 주도적으로 설계하지 않아도 유연한 설계로 리팩토링이 가능하단걸 느꼈다.

'공부방 > 북스터디' 카테고리의 다른 글

[오브젝트] 7장  (0) 2024.01.29
[오브젝트] 6장  (0) 2024.01.23
[오브젝트] 4장  (0) 2024.01.05
[오브젝트] 3장  (0) 2023.12.25
[오브젝트] 2장  (0) 2023.12.24

4장 정리

협력은 애플리케이션의 기능을 구현하기 위해 메시지를 주고 받는 객체들 사이의 상호작용이다.
책임은 객체가 다른 객체와 협력하기 위해 수행하는 행동이다.
역할은 대체 가능한 책임의 집합이다.

설계는 변경을 위해 존재하고 변경에는 어떤 식으로든 비용이 발생한다.
훌륭한 설계란 합리적인 비용안에서 변경을 수용할 수 있는 구조를 만드는 것이다.
이는 곧 응집도가 높고 서로 느슨하게 결합돼 있음을 의미한다.
객체지향 설계란 올바른 객체에 올바른 책임을 할당하면서, 낮은 결합도와 높은 응집도를 가진 구조를 창조하는 활동이다.

객체의 상태가 아닌, 객체의 행동에 초점을 맞추면 결합도와 응집도를 합리적인 수준으로 맞출 수 있다.
객체를 단순한 데이터의 집합으로 바라보는 시각은 객체의 내부 구현을 퍼블릭 인터페이스에 노출시키므로 변경에 취약해진다.
따라서 객체의 행동, 즉 책임에 초점을 맞추면 객체 사이의 상호작용으로 설계 중심을 이동시키고,

결합도가 낮고 응집도가 높으며 내부 구현을 효과적으로 캡슐화하는 객체들을 창조할 수 있는 기반을 제공한다.

객체의 상태는 구현에 속하며, 구현은 불안정하기 때문에 변하기 쉽다.
상태를 객체 분할의 중심축으로 삼으면 구현에 관한 세부사항이 객체의 인터페이스에 스며들어 캡슐화의 원칙이 무너진다.

캡슐화

객체지향에서는 상태와 행동을 하나의 객체 안에 모아 내부 구현을 외부로부터 감춘다.
이처럼 외부에서 알 필요 없는 부분을 감춤으로써 대상을 단순화하는 추상화 기법을 캡슐화라고 한다.
캡슐화를 통해 변경 가능성이 높은 부분인 구현은 내부에 숨기고 상대적으로 안정적인 부분인 인터페이스를 외부에 공개한다.
외부에서는 인터페이스에만 의존하도록 관계를 설정하여 변경에 대한 파급효과를 줄일 수 있다.
설계는 변경을 위해 존재하고, 불안정한 부분과 안정적인 부분을 분리해서 변경의 영향을 통제하는 캡슐화는

객체지향 설계에서 매우 중요한 요소이다.

응집도와 결합도

응집도는 모듈에 포함된 내부 요소들이 연관돼 있는 정도를 나타낸다.
결합도는 의존성의 정도를 나타내며, 다른 모듈에 대해 얼마나 많은 지식을 갖고 있는지를 나타낸다.
이 둘은 변경과 관련 요소이며, 캡슐화를 지킬수록 모듈 내의 응집도는 높아지고 모듈간의 결합도는 낮아진다.

데이터 중심 설계의 문제점

데이터 중심 설계는 설계를 시작하는 처음부터 데이터를 정해두기 때문에 너무 이른 시기에 내부 구현에 초점을 맞춘다.
데이터와 기능을 분리하는 절차적 방식은 상태와 행동을 하나의 단위로 캡슐화하는 객체지향 패러다임에 반한다.
데이터에 초점을 둔 설계는 getter, setter 를 퍼블릭 인터페이스로 열어놓게 되어 내부 구현이 인터페이스로 스며든다.
이는 캡슐화를 저해하게 되며, 결국 변경에 취약한 설계로 이어진다.
따라서 객체에게 스스로 자신의 데이터를 책임지게 만들어야한다.
캡슐화의 진정한 의미란 변하는 어떤 것이든 감추는 것이다. 그것이 무엇이든 구현과 관련된 것이라면 감춰야한다.


인상깊었던 점

"객체지향 설계란 올바른 객체에 올바른 책임을 할당하면서, 낮은 결합도와 높은 응집도를 가진 구조를 창조하는 활동이다."
이번 장은 이 문장에 대한 설명이었던것 같다. 그 중 캡슐화 파트 부분이 인상깊었다.
캡슐화라고 하면 "내부 구현을 숨긴다" 정도로 생각했지만, 변경 가능성이 높은 구현을 숨기고 상대적으로 안정적인 인터페이스를 외부에 공개하는 추상화 기법 이라는 좀 더 나은 답변을 할 수 있을것 같다.
이 책을 읽으면서 객체지향 프로그래밍이란 무엇인가 라는 질문에 대해 어떻게 정리할지 생각해보고 있다.

완독 후에 기존에 정리했던 OOP 와 이 책을 읽고 난 후에 다시 정리한 OOP 내용을 비교해보면 재미있을것 같다.

'공부방 > 북스터디' 카테고리의 다른 글

[오브젝트] 6장  (0) 2024.01.23
[오브젝트] 5장  (0) 2024.01.15
[오브젝트] 3장  (0) 2023.12.25
[오브젝트] 2장  (0) 2023.12.24
[오브젝트] 1장  (0) 2023.12.15

3장 정리

객체지향 패러다임의 관점에서 핵심은 역할, 책임, 협력이다.
객체지향의 본질은 협력하는 객체들의 공동체를 창조하는 것이며,
객체 지향의 핵심은 협력을 구성하기 위한 적절한 객체를 찾고 적절한 책임을 할당하는 과정에서 드러난다.
애플리케이션의 기능을 구현하기 위해 어떤 협력이 필요하고, 협력을 위해 어떤 역할과 책임이 필요한지를 고민하지 않은 채
너무 이른 시기에 구현에 초점을 맞추는것은 변경하기 어렵고 유연하지 못한 코드를 낳는 원인이 된다.

협력 - 객체들이 애플리케이션의 기능을 구현하기 위해 수행하는 상호작용

메시지 전송은 객체 사이의 협력을 위해 사용할 수 있는 유일한 커뮤니케이션 수단이다.
메시지를 수신한 객체는 메서드를 실행해 요청에 응답한다.
외부의 객체는 오직 메시지만 전송할 뿐, 메시지를 어떻게 처리할지는 메시지를 수신한 객체가 직접 결정한다.
즉, 객체가 자신의 일을 스스로 처리할 수 있는 자율적인 존재라는 의미이다.

자율적인 객체는 자신에게 할당된 책임을 수행 -> 외부의 도움이 필요한 경우 적절한 객체에게 메시지 전송
메시지를 수신한 객체도 자신에게 할당된 책임을 수행 -> 도움이 필요한 경우 적절한 객체에게 메시지 전송
이러한 객체들 사이의 협력을 구성하는 일련의 요청과 응답의 흐름을 통해 애플리 케이션의 기능이 구현된다.

협력이 설계를 위한 문맥을 결정한다.
협력은 객체의 행동을 결정하고, 행동은 객체의 상태를 결정한다.

  • 협력이라는 문맥 속에서 객체가 처리할 메시지에 따라 행동이 결정된다.
  • 행동할때 필요한 정보가 무엇인지에 따라 객체의 상태가 결정된다.

책임 - 객체가 협력에 참여하기 위해 수행하는 로직

객체의 책임은 '무엇을 알고 있는가' 와 '무엇을 할 수 있는가' 로 구성된다.
적절한 협력이 적절한 책임을 제공하고, 적절한 책임을 적절한 객체에 할당해야 단순하고 유연한 설계가 가능하다.

책임 할당 시 고려해야하는 두 가지 요소

1. 메시지가 객체를 결정한다.

  • 필요한 메시지를 먼저 식별하고, 메시지를 처리할 객체를 나중에 선택하면 아래와 같은 장점이 있다.
  • 객체가 최소한의 인터페이스를 가질 수 있다.
    필요한 메시지가 식별될 때까지 객체의 퍼블릭 인터페이스에 어떤 것도 추가되지 않기 때문
  • 객체가 충분히 추상적인 인터페이스를 가질 수 있다.
    객체의 인터페이스는 무엇(what)을 하는지는 표현해야 하지만 어떻게(how)는 노출해선 안된다.
    메시지는 외부의 객체의 추상적인 요청이기 때문에 메시지를 먼저 식별하면 무엇을 수행할지에 초첨을 맞추는 인터페이스를 얻을 수 있다.

2. 행동이 상태를 결정한다.

  • 객체가 존재하는 이유는 협력에 참여하기 위해서다.
    객체지향에 갓 입문한 사람들이 가장 쉽게 빠지는 실수는 객체의 행동이 아니라 상태에 초점을 맞추는 것이다.
    초보자들은 먼저 객체에 필요한 상태가 무엇인지를 결정하고, 그 후에 상태에 필요한 행동을 결정한다.
    이런 방식은 내부 구현이 객체의 퍼블릭 인터페이스에 노출되도록 만들기 때문에 캡슐화를 저해한다.
  • 캡슐화를 위반하지 않도록 구현에 대한 결정을 뒤로 미루면서 객체의 행위를 고려하기 위해서는
    항상 협력이라는 문맥 안에서 객체를 생각해야 한다.

    협력 관계 속에서 다른 객체에게 무엇을 제공해야하고, 다른 객체로부터 무엇을 얻어야 하는지를 고민해야만
    훌륭한 책임을 수확할 수 있다.

    개별 객체의 상태와 행동이 아닌 시스템의 기능을 구현하기 위한 협력에 초점을 맞춰야만
    응집도가 높고 결합도가 낮은 객체들을 창조할 수 있다.

역할 - 객체가 어떤 특정한 협력 안에서 수행하는 책임의 집합

협력을 모델링할 때는 특정한 객체가 아니라 역할에게 책임을 할당한다고 생각하는게 좋다.
역할은 구체적인 객체를 포괄하는 추상화이며, 객체가 들어갈 수 있는 일종의 슬롯이다.
애매하면 객체로 시작해서 동일한 책임을 역할로 묶어내도록 리팩토링하면 된다.

 


인상깊었던 점

"너무 이른 시기에 구현에 초점을 맞추는것은 변경하기 어렵고 유연하지 못한 코드를 낳는 원인이 된다."
이번 장에서 말하고 싶은 부분은 이 부분이라고 생각한다.
역할, 책임, 협력을 충분히 고려하고 코드를 작성하는 습관을 들이도록 노력해봐야겠다.

궁금한 점

JPA 같은 ORM을 이용하면서 ERD 작성때도 그렇고 엔티티가 가질 데이터부터 생각을 하곤 했던거 같습니다.
여러분들은 이럴 때 어떤 순서로 역할, 책임, 협력을 생각하면서 설계하시는지 궁금합니다.

답변 및 토론 내용

  • 한번에 좋은 엔티티들의 관계를 정의하기가 정말 어려운것 같다.
    아직 처음부터 객체지향적인 사고를 가지고 설계하는게 어색해서 먼저 가장 핵심이 되는 엔티티를 기존 방식대로 좀 러프하게 설계하고 리팩토링 하는 방식을 택한다.
    어느정도 핵심이되는 로직들이 만들어지면 그 이후부터는 객체간의 역할 책임 협력 관계가 어느정도 눈에 보이고,
    리팩토링을 하는 과정에서 기능 단위별로 테스트코드를 잘 짜놓으면 일단 어떻게든 완성은 되기 때문에 당장은 이런 방식도 좋을것 같다.
    (테스트코드를 짜는건 정말 귀찮고 힘든 일이지만...)
  • ERD 작성 자체가 데이터 주도 설계이다. 따라서 상태에 대해 생각하는것은 어쩔 수 없는 부분이다.
  • 사실 기능 구현을 할 때 이미 "기능" 이라는 행동에 초점을 두고, 기능을 구현하기 위해 협력할 객체를 선택하고,
    그 이후에 엔티티가 어떤 데이터를 가질 지 생각하는 단계가 아니었을까?
    어쩌면 생각보다 꽤 객체지향적으로 사고하는 습관이 들었던걸지도 모른다.
    그러므로 이 책을 보면서 현 상황에서 더 나은 방향성이 있다면 그걸 참고하면서 발전시키면 좋을것 같다.

'공부방 > 북스터디' 카테고리의 다른 글

[오브젝트] 6장  (0) 2024.01.23
[오브젝트] 5장  (0) 2024.01.15
[오브젝트] 4장  (0) 2024.01.05
[오브젝트] 2장  (0) 2023.12.24
[오브젝트] 1장  (0) 2023.12.15

2장 정리

프로그래밍을 할때 대부분의 사람들은 어떤 클래스가 필요한지부터 고민한다.
그렇게 클래스를 결정한 후에 클래스에 어떤 속성, 메서드가 필요한지 고민한다.
하지만 이는 객체지향적인 설계와 거리가 멀다.
진정한 객체지향 패러다임으로의 전환은 클래스가 아닌 객체에 초점을 맞출 때에만 얻을 수 있다.

이를 위해서는 다음 두가지에 집중해야 한다.

1. 어떤 클래스가 필요한지를 고민하기 전에 어떤 객체들이 필요한지 고민하라.

  • 클래스는 공통적인 상태와 행동을 공유하는 객체들을 추상화한 것이다.
  • 따라서 클래스의 윤곽을 잡기 위해서는 어떤 객체들이 어떤 상태와 행동을 가지는지를 먼저 결정해야 한다.

2. 객체를 독립적인 존재가 아니라 기능을 구현하기 위해 협력하는 공동체의 일원으로 봐야 한다.

  • 객체지향적으로 생각하고 싶다면 객체를 고립된 존재로 바라보지 말고 협력에 참여하는 협력자로 바라봐야 한다.
  • 객체들의 모양과 윤곽이 잡히면 공통된 특성과 상태를 가진 객체들을 타입으로 분류하고, 이 타입을 기반으로 클래스를 구현하라.

시스템의 어떤 기능을 구현하기 위해 서로의 메서드를 호출하며 상호작용하는 것을 협력이라고 한다.
객체는 다른 객체의 인터페이스에 공개된 행동을 수행하도록 요청할 수 있다.
요청을 받은 객체는 자율적인 방법에 따라 요청을 처리한 후 응답한다.
객체가 다른 객체와 상호작용할 수 있는 유일한 방법은 메시지를 전송 하는것 뿐이다.
다른 객체에 요청이 도착할 때 해당 객체가 메시지를 수신했다고 한다.
메시지를 수신한 객체는 스스로의 결정에 따라 자율적으로 메시지를 처리할 방법을 결정하며, 이 방법을 메서드라고 한다.
메시지와 메서드의 구분에서 다형성의 개념이 출발한다.

 

다형성을 이용하면 컴파일 시점의 의존성과 실행 시점의 의존성이 서로 다를 수 있다.
이를 통해 같은 메시지로도 메시지를 수신한 객체의 타입에 따라 다른 메서드를 실행할 수 있다.
이렇게 코드를 작성한다면 더 유연하고, 쉽게 재사용 할 수 있다.
하지만 이러면 코드를 읽었을 때 이해하기 어려워진다.
이와 같은 의존성의 양면성은 설계가 트레이드오프의 산물이라는 사실을 보여준다.
따라서 객체지향적으로 설계하기 위해서는 이런 유연성과 가독성 사이에서 고민해야한다.

 

상속은 구현 상속 / 인터페이스 상속으로 분류할 수있다.
구현 상속 - 코드를 재사용하기 위한 목적
인터페이스 상속 - 다형적인 협력을 위해 부모 클래스와 자식 클래스가 인터페이스를 공유하기 위한 목적
상속은 구현 상속이 아니라 인터페이스 상속을 위해 사용해야 한다.
= 코드 재사용이 아닌, 같은 메시지를 수신할 수 있다는걸 뜻하는 의미에서 사용해야한다.

 

대부분의 사람들은 객체지향 프로그래밍 과정을 클래스 안에 속성과 메서드를 채워넣는 작업이나,
상속을 이용해 코드를 재사용하는 방법 정도로 생각한다.
하지만 이렇게 프로그래밍 관점에 너무 치우쳐서 객체지향을 바라볼 경우 객체 지향의 본질을 놓치기 쉽다.
객체지향이란 객체를 지향하는 것이다.
가장 중요한것은 애플리케이션의 기능을 구현하기 위해 협력에 참여하는 객체들 사이의 상호작용이다.
이런 객체들 사이의 역할, 책임, 협력이라는 본질이 가장 중요하다.

 


인상깊었던 점

"객체지향이란 단순히 클래스 안에 속성과 메서드를 채워넣는 작업이나 상속을 이용해 코드를 재사용하는게 아니다."
이번 장에선 이 문장이 가장 인상깊었다.
지금껏 객체 지향 프로그래밍에서 상속에 대해 설명할 땐 코드 재사용 측면을 위주로 설명했다.
하지만 이번 장을 읽고 상속은 단순히 코드를 재사용한다는게 중점이 아닌,

다형적인 협력을 위해 부모 클래스와 자식 클래스가 인터페이스를 공유하는 것이라는걸 배울 수 있었다.
앞으로 코드를 작성하기에 앞서, 객체 간의 협력 과정을 메시지 / 메서드로 나눠서 생각하는 습관을 들여봐야 겠다.

'공부방 > 북스터디' 카테고리의 다른 글

[오브젝트] 6장  (0) 2024.01.23
[오브젝트] 5장  (0) 2024.01.15
[오브젝트] 4장  (0) 2024.01.05
[오브젝트] 3장  (0) 2023.12.25
[오브젝트] 1장  (0) 2023.12.15

1장 정리

데이터 관점으로 코드를 작성하면 변경 이전 코드처럼 되기 쉽다.
모든 처리가 하나의 클래스 안에 위치하고, 나머지 클래스는 단지 데이터의 역할만 수행한다.
그래서 어떤 클래스가 어떤 정보를 갖고 있는지 너무 많은 정보를 알아야 한다.
이는 코드를 읽는 사람에게도 굉장한 부담을 준다.
또한 너무 많은 정보를 알고 있기 때문에 변경에도 쉽게 영향을 받고 취약하다.

 

캡슐화를 통해 객체의 내부 구현을 숨겨서 객체를 자율적인 존재로 만들면,
객체끼리 메시지를 전달함에 있어서 오로지 퍼블릭 인터페이스에만 의존한다.
즉, 메시지를 수행할 객체의 구현 세부사항을 전혀 몰라도 동작할 수 있다.
알아야할 정보가 줄면 결합도 또한 자연스럽게 줄어든다.
또한 인터페이스에만 의존 하므로 객체의 내부 구현이 변경되어도 영향을 받지 않는다.

 

객체지향 설계에서는 각각의 객체를 자율적인 존재로 만들어 각자의 책임을 수행하도록 한다.
이렇게 책임의 관점에서 설계를 하면 하나의 객체가 가진 과도한 책임을 분산할 수 있다.
하지만 동일한 기능을 한가지 이상의 방법으로 설계할 수 있으므로 결합도와 자율성 사이에서 적절한 트레이드 오프가 필요하다.

 

객체지향적인 프로그래밍이란 흔히들 실세계의 모방이라고 설명한다.
하지만 객체지향적인 설계를 설명하기에 이 실세계의 모방이라는 개념은 적절하지 않다.
객체 세계에선 현실에서 무생물이거나 수동적인 존재를 생물처럼 능동적이고 자율적인 존재로 의인화한다.
훌륭한 객체지향 설계란 소프트웨어를 구성하는 모든 객체들이 자율적으로 행동하는 설계를 가리킨다.
애플리케이션의 기능을 구현하기 위해 객체들이 협력하는 과정속에서 객체들은 다른 객체에 의존하게 된다.
이런 의존성을 적절하게 관리하는것이 훌륭한 객체지향 설계이다.

 

인상깊었던 점

"객체지향적인 설계를 설명하기에 이 실세계의 모방이라는 개념은 적절하지 않다"
1장에서 가장 크게 와닿은건 이 내용이었다.
이전까지 저는 실세계의 모방이라는게 머리에 박혀서 코드를 짤 때 실세계에서는 어떻게 동작할까?
라는 생각을 위주로 코드를 짰던것 같다.
그래서 실세계에서 무생물인건 수동적으로, 생물인건 능동적인 개념으로 코드를 짜는 경향이 있었던것 같다.
지금이라도 이 고정관념에서 벗어날 수 있어서 다행이다.

'공부방 > 북스터디' 카테고리의 다른 글

[오브젝트] 6장  (0) 2024.01.23
[오브젝트] 5장  (0) 2024.01.15
[오브젝트] 4장  (0) 2024.01.05
[오브젝트] 3장  (0) 2023.12.25
[오브젝트] 2장  (0) 2023.12.24

+ Recent posts