기타/도서

[도서]객체지향의 사실과 오해

steadyMan 2023. 2. 21. 00:51

객체지향적으로 코드를 작성하는 건 어려운 일이다. 하지만 확장성 높고 유지보수성 좋은 코드를 작성하려면 알아야 하는

요건이라 생각한다. 하지만 SOILD원리 같은규칙을 외워서 할 수 있는 일이 아니었다. 그래서 찾다가 발견한 책이  "객체지향의 사실과 오해"였다. 이 책은 많이 알려진 책이었고 평이 상당히 좋았다. 책의 내용을 전부 습득하진 못했지만 책에서 사용한 예시는 객체지향이 어떤 개념인지 쉽게 이해할 수 있도록 했고 포인트를 잡을 수 있도록 해주었다.

 

책을 읽으면서 중요하다고 생각되는 내용과 개인적 의견을 글로 정리하며 복습합니다.


 

객체의 자율성이란 다른 객체로 부터 나의 상태가 변경되는 게 아닌 상태는 감추고
행동이 발생하길 요청받아 그것을 수행하는 자율성이다. 

데이터의 내부 표현 방식과 무관하게 행동만이 고려 대상이라는 사실은 외부에 데이터를 감춰야 한다는 것을 의미한다.

훌륭한 객체지향 설계는 외부에 행동만 제공하고 데이터를 뒤로 감춰야 한다. 이 원칙을 캡슐화라고 한다

 

객체를 구성할때 상태부터 고려를 한다면 타 객체와 상호작용 하는 객체를 만들기 힘들다
항상 행동을 먼저 고려해야 한다.

평소 클래스를 생성하고 필드 데이터를 먼저 선언하고 메소드를 작성하는데 책에서는 이와 반대로 외부에 제공해야하는 책임을 먼저 결정하고 그 책임을 수행하는데 적합한 데이터는 나중에 결정하고 그다음 인터페이스 뒤로 캡슐화해야 한다고 말한다. 데이터보다 행동에 중점을 잡아야 한다는 건 이해를 했지만 어떤 이점이 발생할지는 앞으로 직접 행위에 중점을 두고 설계하면서 경험해야겠다. 현재 생각되는 건 책임과 행동을 먼저 결정을 하면 필드를 좀 더 구체적이고 명확하게 가져갈 수 있지 않을까 생각된다.

 

객체지향 설계는 협력에 참여하기 위해 어떤 객체가 어떤 책임을 수행해야 하고
어떤 객체로부터 메시지를 수신할 것인지를 결정하는 것으로부터 시작된다.

책에서는 단순히 어떤 기능을 제공해야된다 보단 어떤 메시지를 받고 어떤 역할을 수행해야 하는지 "협력"이라는 키워드 아래서 생각해야 한다고 말한다. 책은 "책임", "협력", "역할"을 중심으로 객체지향을 이야기하는데 비즈니스 안에서 메시지를 정하고 해당 책임을 수행할 역할을 도메인에 부여하는데 도메인을 결정하는 건 관련 데이터를 알고 있는 도메인을 선택해야 한다는 것이다.

 

메시지가 책임을 의미한다고 했던 것을 기억하라. 결국 동일한 역할을 수행할 수 있다는 것은 해당 객체들이 협력 내에서 동일한 책임의 집합을 수행할 수 있다는 것을 의미한다. 동일한 역할을 수행하는 객체들이 동일한 메시지를 수신할 수 있기 때문에 동일한 책임을 수행할 수 있다는 것은 매우 중요한 개념이다. 이 개념을 제대로 이해 해야만 객체지향이 제공하는 많은 장점을 누릴 수 있다.

 

협력에 필요한 책임을 결정하고 객체에게 책임을 할당하는 과정을 얼마나 합리적이고 적절하게 수행했는지가 객체지향 설계의 품질을 결정한다. 

 

객체지향세계에서 객체는 어떻게 설계해야 하는지에 대해서도 책은 명확하게 답하고 있는데 이 부분이 중요하게 느껴진 이유는 항상 코드를 작성할 때 발생되는 문제점에 대한 해결책을 제시해 주었기 때문이다. 결론적으로 이해한 건 처음 설계를 할 때 비즈니스의 안에서 협력을 중심으로 메시지(책임)를 선택하고 역할을 할당하되 관련 데이터를 알고 있는(비즈니즈 안에서) 도메인에 할당하는 게 좋은 설계방법이라는 것이다.

 

책은 객체의 상태와 행위도 중요하지만 가장 중요한건 객체의 자율성, 협력성이라 말하고 있다. 

 

객체가 가져야 하는 상태와 행위에 대해 고민하기 전에 그 객체가 참여할 문맥인
협력을 정의하라.
객체지향 시스템에서 가장 중요한 것은 충분히 자율적인 동시에
충분히 협력적인 객체를 창조하는 것이다

이 목표를 달성할 수 있는 가장 쉬운 방법은 객체를 충분히 협력적으로 만든 후에 협력이라는 문맥 안에서 객체를 충분히 자율적으로 만드는 것이다.

 

객체에게 할당되는 책임이 자율적이어야 한다, 책임이 자율적이지 않다면 객체가 아무리 발버둥친다고 하더라도 자율적으로 책임을 수행하기 어렵다.

 

책에서는 예시로 재판의 증언을 지시하는 왕과 지시에 응하는 모자장수의 이야기로 자율과 협력을 이야기하는데 왕이

증언을 할 책임이 있는 모자장수에게 증언을 요청하면 모자장수가 협력에 참여하기 위해 왕의 요청을 적절하게 처리한 후 응답해야 한다. 요청은 수신자의 책임을 암시하므로 모자 장수는 재판이라는 협력에 참여하기 위해 증언할 책임을 진다. 

 

왕은 모자장수가 증언하라 라는 자신의 요청을 수행한다면 모자장수의 증언방식에는 신경 쓰지 않는다. 모자 장수는

왕의 요청을 받아야 책임을 수행하기 시작하겠지만 증언 방식이나 증언에 필요한 자료는 스스로의 의지와 판단에 따라

자유롭게 선택할 수 있다. 즉 객체가 자율적이기 위해서는 자율성을 충분히 보장할 수 있을 정도의 포괄적이고 추상적인 책임을 부여해야 한다는 것이다. 이 파트가 상당히 난해하게 느껴졌다. 객체의 자율성을 보장할 수 있을만큼 추상적인 책임을 부여해야 된다고 하지만 또 표현하지 못할 정도로 추상적인 것 역시 문제라고 하기 때문이다.

 

하지만 큰 인상을 받은 이유는 메소드를 작성하면 거의 같은 책임이지만 약간만 다르면 사용할 수 없는 상황이 많았는데 당시는 포괄적으로 작성을 해야겠다는 생각은 했지만 구체적인 기준이 없었는데 책을 읽은 시점에서 보면 책임 안에서 자율성이 보장되는 형태로 작성해야 됐다고 생각된다.

 

추상적이고 포괄적인 책임은 협력에서 다양하게 재사용할 수 있도록 유연성을 제공한다는 점 때문이다. 물론 책임은 협력에 참여하는 의도를 명확하게 설명할 수 있는 수준 안에서 추상적이어야 한다. 어떤 책임이 가장 적절한가는 설계 중인 협력이 무엇인가에 따라 달라진다. 

 

'어떻게'가 아니라 '무엇'을

자율적인 책임의 특징은 객체가 어떻게(how) 해야하는가가 아니라 무엇(what)을 해야 하는가를 설명하는 것이다. ‘증언한다’라는 책임은 모자장수가 협력을 위해 ‘무엇’을 해야 하는지를 결정하지만 ‘어떻게’ 해야 하는지에 대해서는 전혀 언급하지 않는다. 증언할 방법은 모자 장수가 자율적으로 선택 할 수 있다. 

 

모자 장수에게 '증언의 순서, 방식'등 '어떻게' 하라는 요청이 함께였다면 모자장수가 선택할 수 있는 부분은 크게 제한될 수밖에 없다. 수신자는 메시지를 처리하기 위해 메시지에 실려 있는 인자를 사용할 수 있다 즉 왕은 '언제', '어디서'등 추가적인 정보를 실어 보낼 수 있다. 다음과 같이 말이다. 증언하라(어제, 왕국)

 

메시지 전송은 수신자와 메시지의 조합이다. 메시지는 메시지 이름과 인자의 조합이 므로 결국 메시지 전송은 수신자, 메시지 이름,   인자의 조합이 된다. 왕 예제에서 왕은 메시지를 누구에게 전송해야 하는지를 알아야 한다. 이 경우 모자 장수가 메시지의 수신 자가 된다. 수신자, 메시지 이름, 인자의 순서대로 나열하면 메시지 전송이 된다.

예를 들어, 왕이 모자 장수에게 어제, 왕국에서 목격한 것을 증언할 것을 요청하고 싶다면 다음과 같은 메시지를

전송할 것이다. 모자장수.증언하라(어제, 왕국)

 

메세지 전송이 수신자, 메세지 이름, 인자의 조합으로 구성된다는 것을 기억하는 것이 중요하다. 이 부분은 메시지의 인자를 활용하여 책임의 자율성은 보장하면서 구체적인 메시지를 요청해야 한다는 것으로 이해했다.

 

객체지향의 강력함은 클래스가 아니라 객체들이 주고받는 메시지로부터 나온다.

 

객체지향 애플리케이션은 클래스를 이용해 만들어지지만 메시지를 통해 정의된다

실제로 애플리케이션을 살아있게 만드는 것은 클래스가 아니라 객체다. 그리고 이런 객체들의 윤곽을 결정하는 것이 바로 객체들이 주고받는 메시지다. 클래스를 정의하는 것이 먼저가 아니라 객체들의 속성과 행위를 식별하는 것이 먼저다.

 

클래스를 중심에 두는 설계는 유연하지 못하고 확장하기 어렵다. 객체지향 패러다임으로의 전환은 시스템을 정적인 클래스들의 집합이 아니라 메시지를 주고받는 동적인 객체들의 집합으로 바라보는 것에서 시작된다. 클래스에 담길 객체들의 공통적인 행위와 속성을 포착하기 위해서는 먼저 협력하는 객체들의 관점에서 시스템을 바라봐야 한다.

 

클래스를 객체지향 세계의 왕좌에서 끌어내렸다고 해서 모든 문제가 해결되는 것은 아니다. 진정한 객체지향 패러다임으로의 도약은 개별적인 객체가 아니라 메시지를 주고받는 객체들 사이의 커뮤니케이션에 초점을 맞출 때 일어난다.

커뮤니케이션이 아닌 객체에 초첨을 맞출 경우 가장 흔히 범하게 되는 실수는 협력이라는 문맥을 배제한 채 객체 내부의 데이터 구조를 먼저 생각한 후 데이터 조작에 필요한 오퍼레이션을 나중에 고려하는 것이다.

객체를 독립된 단위가 아니라 협력이라는 문맥 안에서 생각해야 한다. 결국 객체를 이용하는 이유는 객체가 다른 객체가 필요로 하는 행위를 제공하기 때문이다. 협력 관계 속에서 다른 객체에게 무엇을 제공해야 하고 다른 객체로부터 무엇을 얻어야 하는가의 관점에서 접근할 때만 훌륭한 책임을 수확할 수 있다. 시스템의 기능을 구현하기 위해 객체가 다른 객체에게 제공해야 하는 메시지에 대해 고민하라. 

 

어떻게 생각하면 당연한 이야기로 보인다. 당연히 특정 기능을 제공하기 위해 메서드를 만들고 필요하다면 필드도 생성하기 때문이다. 하지만 분명한 차이점이 있는데 기능을 바라보고 작성하는 게 아닌 객체 간 협력관계를 고려하여 메시지를 중심으로 설계해야 한다는 점이다. 이는 위에서 작성한 협력에 참여하는 의도가 명확한 자율적인 객체를 만드는 방법 중 하나일 거라 생각된다. 

 

객체지향 설계는 적절한 책임을 적절한 객체에게 할당하면서 메시지를 기반으로 협력하는
객체들의 관계를 발견하는 과정이다

책임과 협력하는 객체들을 이용해 시스템을 설계하는 방법을 책임-주도설계라 한다. 책임-주도 설계의 기본 아이디어는

객체들 간에 주고받는 메시지를 기반으로 적절한 역할과 책임, 협력을 발견하는 것이다. 책임-주도 설계 방법에서 역할, 책임, 협력을 식별하는 것은 애플리케이션이 수행하는 기능을 시스템의 책임으로 보는 것으로부터 시작된다 객체가 책임을 완수하기 위해 다른 객체의 도움이 필요하다고 판단되면 도움을 요청하기 위해 어떤 메시지가 필요한지 결정한다. 메시지를 결정한 후에는 메시지를 수신하기에 직접 한 객체를 선택한다. 수신자는 송신자가 메시지를 보내면서 기대한 바를 충족시켜야 한다.

즉, 수신자는 송신자가 기대대로 메세지를 처리할 책임이 있다. 결과적으로 메시지가 수신자의 책임을 결정한다.

객체가 어떤 메시지를 수신하고 처리할 수 있느냐가 객체의 책임을 결정한다. 책임-주도 설계 방법에서는 What/Who 사이클에 따라 협력에 참여할 객체를 결정하기 전에 협력에 필요한 메시지를 먼저 결정한다. 메시지가 결정된 후에야 메시지를 수신할 후보를 선택하는 것으로 초점이 이동한다.

 

묻지 말고 시켜라

처음 이 문장을 봤을 때 객체지향 프로그래밍 강의를 봤을 때가 떠올랐다. 강의에서는 다른 객체의 데이터를 가져와서 직접 비교하고 조건에 맞는 로직을 구현하는 게 객체지향적이지 못한 방법이라고 설명했다. 데이터를 직접 가져와서 비교하는 게 아닌 객체에 요청하여 결과를 받아오는 형식으로 작성해야 된다는 것이었다. 객체의 데이터를 사용한 결과도출은 해당객체의 책임이기 때문에 전적으로 해당 객체의 책임으로 남겨놔야 된다는 것이었다. 

 

묻지 말고 시켜라의 하위내용도 비슷한데 메시지를 먼저 결정하고 객체가 메시지를 따르게 하는 설계 방식을 말한다. 

메시지를 먼저 결정하면 데이터를 직접받아와 비교한다는 생각을 할 필요가 없어진다. 송신자는 수신자가 어떤 객체인지 모르기 때문에 단지 자신이 전송한 메세지를 잘 처리할 거라 믿고 전송할 수밖에 없다.

 

"묻지 말고 시켜라" 스타일은 애플리케이션이 자율적인 객체들의 공동체라는 사실을 강조한다. 객체는 다른 객체의 결정에 간섭하지 말아야 하며, 모든 객체는 자신의 상태를 기반으로 스스로 결정을 해야 한다.

객체는 다른 객체의 상태를 묻지 말아야 한다. 객체가 다른 객체의 상태는 묻는다는 것은 메시지를 전송하기 이전에 객체가 가져야 하는 상태에 관해 너무 많이 고민하고 있었다는 증거다. 고민을 연기하라. 단지 필요한 메세지를 전송하기만 하고 메세지를 수신하는 객체가 스스로 메시지의 처리 방법을 결정하게 하라. 

 

결과적으로 ‘묻지 말고 시켜라’ 스타일은 객체를 자율적으로 만들고 캡슐화를 보장하며 결합도를 낮게 유지시켜 주기 때문에 설계를 유연하게 만든다. 

 

각 객체는 자신의 책임을 완수하는 데 필요한 정보나 서비스가 필요한 경우 이를 제공할 수 있는 다른 객체에게 책임을 요청한다. 따라서 시스템의 기능은 역할과 책임을 수행하는 객체들의 협력 관계를 통해 구현된다.

 

후기

이 책은 한 번더 읽을 예정이며 독서 중 느낀 건 객체지향의 개념, 정의등을 먼저 어느 정도 파악을 하고 그 내용을 좀 더 익숙하고 이해하기 편한 예시로 머리에 입력하는데 이 책은 상당한 도움이 될 거라고 생각한다. 물론 모든 내용을 이해하거나 실제 코드를 작성할 때 상당히 객체지향적으로 작성한다는 등의 일들은 아직일지 모르지만 적어도 어떤 기준으로 설계를 하고 무엇을 우선순위에 두고 작성을 해야 하는지에 대한 개념은 덕분에 잡힌 거 같다.