오브젝트 리뷰 - 6

기술/아키텍처 2021. 8. 9. 20:00 Posted by 아는 개발자

유연한 설계

 

개방-폐쇄 원칙 

 

개방-폐쇄 원칙은 런타임 의존성과 컴파일타임 의존성에 관한 이야기다. 개방-폐쇄 원칙을 수용하는 코드는 컴파일타임 의존성을 수정하지 않고도 런타임 의존성을 쉽게 변경할 수 있다. 개방-폐쇄 원칙의 핵심은 추상화에 의존하는 것이다. 올바른 추상화를 설계하고 추상화에 대해서만 의존하도록 관계를 제한함으로써 설계를 유연하게 확장할 수 있다.

 

생성과 사용의 분리

 

유연하고 재사용 가능한 설계를 원한다면 객체와 관련된 두 가지 책임을 서로 다른 객체로 분리해야 한다. 하나는 객체를 생성하는 것이고, 다른 하나는 객체를 사용하는 것이다. 한 마디로 말해서 객체에 대한 생성과 사용을 분리(seprating use from creation)해야 한다.

 

경우에 따라 객체 생성과 관련된 책임만 전담하는 별도의 객체를 추가하고 Client는 이 객체를 사용하도록 만들 수 있다. 이처럼 생성과 사용을 분리하기 위해 객체 생성에 특화된 객체를 FACTORY라고 부른다.

 

의존성 주입 

 

객체가 아닌 외부의 독립적인 객체가 인스턴스를 생성한 후 이를 전달해서 의존성을 해결하는 방법을 의존성 주입이라고 부른다. 의존성 주입은 의존성을 해결하기 위해 의존성을 객체의 퍼블릭 인터페이스에 명시적으로 드러내서 외부에서 필요한 런타임 의존성을 전달할 수 있도록 만드는 방법을 포괄하는 명칭이다. 

 

의존성 주입은 필요한 의존성을 클래스 퍼블릭 인터페이스에 명시적으로 드래너기 때문에 의존성을 이해하기 위해 코드 내부를 읽을 필요가 없어 객체의 캡슐을 단단하게 보호한다. 가급적 의존성을 객체의 퍼블릭 인터페이스에 노출하라. 의존성을 구현 내부에 숨기면 숨길수록 코드를 이해하기도, 수정하기도 어려워진다.

 

상속과 코드 재사용 

 

상속과 중복 코드 

 

중복코드가 가지는 가장 큰 문제는 코드를 수정하는 데 필요한 노력을 몇 배로 증가시킨다는 것이다. 중복 여부를 판단하는 기준은 변경이다. 요구사항이 변경됐을 때 두 코드를 함께 수정해야 한다면 이 코드는 중복이다. 

 

상속은 객체지향 프로그래밍에서 중복을 제거하기 위한 대표적인 방법이다. 상속을 이용해 코드를 재사용하기 위해서는 부모 클래스의 개발자가 세웠던 가정이나 추론 과정을 정확하게 이해해야한다. 이것은 자식 클래스의 작성자가 부모 클래스의 구현 방법에 대한 정확한 지식을 가져야 한다는 것을 의미한다. 따라서 상속은 결합도를 높인다. 그리고 부모 클래스와 자식 클래스 사이의 강한 결합이 코드를 수정하기 어렵게 만든다.

 

취약한 기반 클래스 문제 

 

부모클래스의 변경에 의해 자식 클래스가 영향을 받는 현상을 취약한 기반 클래스 문제라고 부른다. 취약한 기반 클래스 문제는 캡슐화를 약화시키고 결합도를 높인다. 상속은 자식 클래스가 부모 클래스의 구현 세부사항에 의존하도록 만들기 때문에 캡슐화를 약화시킨다. 객체지향의 기반은 캡슐화를 통한 변경의 통제인데 상속은 코드 재사용을 위해 캡슐화의 장점을 희석시키고 구현에 대한 결합도를 높임으로써 객체지향이 가진 강력함을 반감시킨다.

 

 

 

 

728x90

'기술 > 아키텍처' 카테고리의 다른 글

오브젝트 리뷰 - 6  (0) 2021.08.09
오브젝트 리뷰 - 5  (0) 2021.07.27
오브젝트 리뷰 - 4  (0) 2021.07.25
오브젝트 리뷰 - 3  (0) 2021.07.21
오브젝트 리뷰 - 2  (0) 2021.07.12
오브젝트 리뷰 - 1  (0) 2021.07.11

오브젝트 리뷰 - 5

기술/아키텍처 2021. 7. 27. 20:00 Posted by 아는 개발자

https://book.naver.com/bookdb/book_detail.nhn?bid=15007773

 

오브젝트

역할, 책임, 협력을 향해 객체지향적으로 프로그래밍하라!객체지향으로 향하는 첫걸음은 클래스가 아니라 객체를 바라보는 것에서부터 시작한다. 객체지향으로 향하는 두번째 걸음은 객체를

book.naver.com

 

의존성 관리하기 

 

의존성 이해하기 

 

어떤 객체가 예정된 작업을 정상적으로 수행하기 위해 다른 객체를 필요로 하는 경우 두 객체 사이에 의존성이 존재한다고 말한다. 의존성은 방향성을 가지며 항상 단방향이다 (p254)

 

의존성은 전이될 수 있기 때문에 의존성의 종류를 직접 의존성(direct dependency)간접 의존성(indirect dependency)로 나누기도 한다. 직접 의존성이란 한 요소가 다른 요소에 직접 의존하는 경우를 가리킨다. 간접 의존성이란 직접적인 관계는 존재하지 않지만 의존성 전이에 의해 영향이 전파되는 경우를 뜻한다. (p257)

 

런타임 의존성은 실행 시점에 객체 간의 의존성을 뜻한다. 컴파일 타임은 코드 상에서 클래스 간의 의존성을 뜻한다. 런타임 의존성과 컴파일타임 의존성이 다를 수 있다. 유연하고 재사용 가능한 코드를 설계하기 위해서는 두 종류의 의존성을 서로 다르게 만들어야 한다. 런타임 의존성과 컴파일 타임 의존성 사이의 거리가 멀면 멀수록 설계가 유연해진다. (p258)

 

클래스가 사용될 특정한 문맥에 대해 최소한의 가정만으로 이뤄져 있다면 다른 문맥에서 재사용하기가 더 수월해진다. 이를 컨텍스트 독립성이라고 부른다. 설계가 유연해지기 위해서는 가능한 자신이 실행될 컨텍스트에 대한 구체적인 정보를 최대한 적게 알아야한다. (p260)

 

유연한 설계 

 

바람직한 의존성은 재사용성과 관련이 있다. 어떤 의존성이 다양한 환경에서 클래스를 재사용할 수 없도록 제한한다면 그 의존성은 바람직하지 못한 것이다. 컨텍스트에 독립된 의존성은 다양한 환경에서 재사용 될 수 있는 가능성을 열어 놓는다(p265, 266)

 

바람직한 의존성을 가질 때 두 객체는 느슨한 결합도(loose coupling) 또는 약한 결합도를(weak coupling) 가진다고 말한다. 반대로 바람직하지 못한 의존성을 가질 때 두 객체는 단단한 결합도(tight coupling) 또는 강한 결합도(strong coupling)을 가진다고 말한다. (p266)

 

결합도의 정도는 한 요소가 자신이 의존하고 있는 다른 요소에 대해 알고 있는 정보의 양으로 결정된다. 한 요소가 다른 요소에 대해 더 많은 정보를 알고 있을 수록 두 요소는 강하게 결합된다. 반대로 더 적은 정보를 알고 있을 수록 두 요소는 약하게 결합된다. 더 많이 알수록 더 많이 결합된다. 더 많이 알고 있다는 것은 더 적은 컨텍스트에서 재사용 가능하다는 것을 의미한다. 결합도를 느슨하게 만들기 위해선 협력하는 대상에 대해 필요한 정보 외에는 최대한 감추는 것이 중요하다(p267)

 

추상화란 어떤 양상, 세부사항, 구조를 좀더 명확하게 이해하기 위해 특정 절차나 물체를 의도적으로 생략하거나 감춤으로써 복잡도를 극복하는 방법이다. 구체 클래스에 비해 추상 클래스는 클라이언트가 알아야하는 지식의 양이 더 적기 때문에 구체 클래스보다 추상 클래스에 의존하는 것이 결합도가 더 낮다. 인터페이스에 의존하면 추상 클래스의 상속 계층을 모르더라도 협력이 가능해진다. 인터페이스 의존성은 협력하는 객체가 어떤 메시지를 수신할 수 있는지에 대한 지식만을 남기기 때문에 추상 클래스보다 결합도가 낮다. 의존하는 대상이 추상적일수록 결합도는 더 낮아진다는 것이 핵심이다. (p268)

 

의존성이 퍼블릭 인터페이스에 노출되는 경우 명시적 의존성이라고 한다. 반대로 퍼블릭 인터페이스에 노출되지 않는 경우 숨겨진 의존성이라고 한다. 의존성이 명시적이지 않으면 클래스를 다른 컨텍스트에서 재사용하기 위해 내부 구현을 직접 변경해야 할 수 있다 (p270, 271)

 

728x90

'기술 > 아키텍처' 카테고리의 다른 글

오브젝트 리뷰 - 6  (0) 2021.08.09
오브젝트 리뷰 - 5  (0) 2021.07.27
오브젝트 리뷰 - 4  (0) 2021.07.25
오브젝트 리뷰 - 3  (0) 2021.07.21
오브젝트 리뷰 - 2  (0) 2021.07.12
오브젝트 리뷰 - 1  (0) 2021.07.11

오브젝트 리뷰 - 4

기술/아키텍처 2021. 7. 25. 15:56 Posted by 아는 개발자

https://book.naver.com/bookdb/book_detail.nhn?bid=15007773

 

오브젝트

역할, 책임, 협력을 향해 객체지향적으로 프로그래밍하라!객체지향으로 향하는 첫걸음은 클래스가 아니라 객체를 바라보는 것에서부터 시작한다. 객체지향으로 향하는 두번째 걸음은 객체를

book.naver.com

 

메시지와 인터페이스 

 

협력과 메시지

 

메시지는 객체 사이의 협력을 가능하게 하는 매개체다. 객체가 다른 객체에게 접근할 수 있는 유일한 방법은 메시지를 전송하는 것뿐이다. 전통적인 방법은 클라이언트-서버 모델이다. 메시지를 전송하는 객체를 클라이언트, 메시지를 수신하는 객체를 서버라 부르며 협력은 클라이언트가 서버의 서비스를 요청하는 단방향 상호작용이다. (p176)

 

객체가 독립적으로 수행할 수 있는 것보다 더 큰 책임을 수행하기 위해서는 다른 객체와 협력해야 한다. 두 객체 사이의 협력을 가능하게 해주는 매개체가 바로 메시지다. (p177)

 

메시지는 객체들이 협력하기 위해 사용할 수 있는 유일한 의사소통 수단이다. 한 객체가 다른 객체에게 도움을 요청하는 것을 메시지 전송 또는 메시지 패싱이라고 부른다. 이때 메시지를 전송하는 객체를 메시지 전송자라고 부르고 메시지를 수신하는 객체를 메시지 수신자라고 부른다. 메시지는 오퍼레이션명과 인자로 구성되며 메시지 전송은 여기에 메시지 수신자를 추가한 것이다. (p177)

 

메시지를 수신했을 때 실제로 실행되는 함수 또는 프로시저를 메서드라 부른다. 코드 상에서 동일한 이름의 변수에게 동일한 메시지를 전송하더라도 객체의 타입에 따라 실행되는 메서드가 달라질 수 있다. 객체는 메시지와 메서드라는 두 가지 서로 다른 개념을 실행 시점에 연결해야 하기 때문에 컴파일 시점과 실행 시점의 의미가 달라질 수 있다. 이런 메시지와 메서드의 구분은 전송자와 메시지 수신자가 느슨하게 결합될 수 있게 한다. (p178)

 

객체가 의사소통을 위해 외부에 공개하는 메시지의 집합을 퍼블릭 인터페이스라고 부른다. 프로그래밍 언어 관점에서 퍼블릭 인터페이스에 포함된 메시지를 오퍼레이션이라고 부른다. 그에 비해 메시지를 수신했을 때 실제로 실행되는 코드는 메서드라고 부른다. (p179)

 

오페레이션의 이름과 파라미터 목록을 합쳐 시그니처라고 부른다. 오퍼레이션은 실행 코드 없이 시그니처만을 정의한 것이고 메서드는 이 시그니처에 구현을 더한 것이다. (p180)

 

인터페이스와 설계 품질 

 

좋은 인터페이스는 최소한의 인터페이스와 추상적인 인터페이스라는 조건을 만족해야한다 (p181)

 

디미터 법칙은 객체 내부 구조에 강하게 결합되지 않도록 협력 경로를 제한한다. 오직 하나의 도트만 사용하라는 말로 요약되기도 한다. 디미터 법칙을 따르기 위해서는 클래스가 특정한 조건을 만족하는 대상에게만 메시지를 전송하도록 프로그래밍 해야한다. 디미터 법칙을 따르면 부끄럼타는 코드를 작성할 수 있다. 부끄럼타는 코드란 불필요한 어떤 것도 다른 객체에게 보여주지 않으며 다른 객체의 구현에 의존하지 않는 코드를 의미한다. (p183, 184, 185)

 

디미터 법칙을 만족하지 않은 경우 내부 구조에 대해 물어보고 반환받은 요소에 대해 연쇄적으로 메세지를 전송하는 경우가 생기는데 이런 경우를 기차 충돌(train wreck)이라 한다. 기차 충돌은 클래스의 내부 구현이 외부로 노출됐을 때 나타나는 전형적인 형태다. (p186)

 

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

 

메서드 이름을 짓는 두번째 방법은 '어떻게'가 아니라 '무엇'을 하는지를 드러내는 것이다. 무엇을 하는지를 드러내는 이름은 코드를 이해하기 쉽고 유연한 코드를 낳는 지름길이다. 무엇을 하는지 드러내도록 메서드의 이름을 짓기 위해서는 객체가 협력 안에서 수행해야 하는 책임에 관해 고민해야한다. 이처럼 무엇을 하느냐에 따라 메서드의 이름을 짓는 패턴을 의도를 드러내는 선택자라고 부른다. (p188, 189, 190)

 

명령 - 쿼리 분리 원칙 

 

어떤 절차를 묶어 호출하도록 이름을 부여한 기능 모듈을 루틴이라 부른다. 루틴은 프로시저와 함수로 구분 되는데 프로시저는 정해진 절차에 따라 내부 상태를 변경하는 루틴이며 함수는 어떤 절차에 따라 필요한 값을 계산해서 반환하는 루틴의 종류이다. 명령은 객체의 인터페이스 측면에서 프로시저에 해당하고, 쿼리는 함수에 해당한다. (p202)

 

명령-쿼리 분리 원칙의 요지는 오퍼레이션은 부수효과를 발생시키는 명령이거나 부수효과를 발생시키지 않는 쿼리 중 하나여야 한다는 것이다. 어떤 오퍼레이션도 명령인 동시에 쿼리여서는 안된다. 명령은 상태를 변경할 수 있지만 상태를 반환해서는 안되며 쿼리는 객체의 상태를 반환할 수 있지만 상태를 변경해서는 안된다. (p203)

 

명령-쿼리 분리 원칙은 부수효과를 가지는 명령으로부터 부수효과를 가지지 않는 쿼리를 명백하게 분리함으로써 제한적이나마 참조 투명성의 혜택을 누릴 수 있게 해준다 (p213)

 

객체 분해

 

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

 

현대적인 프로그래밍 언어를 특징 짓는 중요한 두 가지 추상화 메커니즘은 프로시저 추상화와 데이터 추상화다. 프로시저 추상화는 소프트웨어가 무엇을 해야하는지를 추상화하고 테이터 추상화는 소프트웨어가 무엇을 알아야 하는지를 추상화한다. (p218)

 

프로시저 추상화를 중심으로 시스템을 분해하기로 결정했다면 기능분해(functional decomposition)의 길로 들어서는 것이다. 기능 분해는 알고리즘 분해라고 부르기도 한다. 데이터 추상화를 중심으로 시스템을 분해하기로 결정했다면 데이터를 중심으로 타입을 추상화(type abstraction)하는 방향과 데이터를 중심으로 프로시저를 추상화 하는 방법(procedure abstraction)중 하나를 선택한다 (p218)

 

프로시저 추상화와 기능 분해

 

전통적인 기능 분해 방법은 하향식 접근법을 따른다. 하향식 접근법이란 시스템을 구성하는 가장 최상위 기능을 정의하고 좀 더 작은 단계의 하위 기능으로 분해해가는 방법을 말한다. (p219)

 

하향식 접근법과 기능분해는 근본적으로 변경에 취약한 설계를 갖는다. 메인 함수를 유일한 정상으로 간주하는 하향식 기능 분해의 경우에는 새로운 기능을 추가할 때마다 매번 메인 함수를 수정해야 한다. (p226, 227)

 

하향식 접근법은 비즈니스 로직을 설계하는 초기 단계부터 입력방법과 출력 양식을 함께 고민하도록 강요한다. 그러나 사용자 인터페이스는 시스템 내에서 가장 많은 변경이 발생하는 부분이므로 수정이 발생하면 상대적으로 변경이 적은 비즈니스 로직까지 영향을 미치게 된다. 근본적으로 변경에 불안정한 아키텍처가 만들어진다. (p229)

 

하향식 접근법의 경우 처음부터 구현을 염두에두기 때문에 자연스럽게 함수들의 실행 순서를 정의하는 시간 제약을 강조한다. 분해를 진행할 수 없어 기능 분해 방식은 중앙집중 제어스타일의 형태를 띨 수 밖에 없다. 이를 해결하기 위해선 시간 제약에 대한 미련을 버리고 좀 더 안정적인 논리적 제약을 설계 기준으로 삼아야 한다. 객체 지향은 삼수 간의 호출 순서가 아니라 객체 사이의 논리적인 관계를 중심으로 설계를 이끌어간다. (p230)

 

모듈 

 

시스템을 모듈 단위로 분해하기 위한 기본 원리로 시스템에서 자주 변경되는 부분을 상대적으로 덜 변경되는 안정적인 인터페이스 뒤로 감춰야 한다는 것이 핵심이다. 모듈 분해는 감춰야 하는 비밀을 선택하고 비밀 주변에 안정적인 보호막을 설치하는 보존의 과정이다. 비밀을 결정하고 모듈을 분해한 후에는 기능 분해를 이용해 모듈에 필요한 퍼블릭 인터페이스를 구현할 수 있다. 모듈은 복잡성과 변경 가능성을 감춰야한다. (p235, 236)

 

모듈은 기능이 아니라 변경의 정도에 따라 시스템을 분해한다. 각 모듈은 외부에 감춰야 하는 비밀과 관련성 높은 데이터와 함수의 집합이다. 따라서 모듈 내부는 높은 응집도를 유지한다. 또한 퍼블릭 인터페이스를 통해서만 통신해야 하므로 낮은 결합도를 유지한다. 모듈에게 있어 핵심은 데이터다. (p239)

 

클래스 

 

명확한 의미에서 클래스는 추상 데이터 타입과는 다르다. 핵심적인 차이는 클래스는 상속과 다형성을 지원하는데 비해 추상 데이터 타입은 지원하지 못한다는 것이다.  타입 추상화를 기반으로 하는 대표적인 기법이 바로 추상 데이터 타입이다. 추상 데이터 타입은 오퍼레이션을 기준으로 여러가지 타입을 나타낼 수 있다. 반면 객체 지향은 타입을 기준으로 오퍼레이션을 묵는다. (p245, 246)

 

실제 내부에서 수행되는 절차는 다르지만 클래스를 이용한 다형성은 절차에 대한 차이점을 감춘다. 다시 말해 객체 지향은 절차 추상화다.

 

 

 

 

 

728x90

'기술 > 아키텍처' 카테고리의 다른 글

오브젝트 리뷰 - 6  (0) 2021.08.09
오브젝트 리뷰 - 5  (0) 2021.07.27
오브젝트 리뷰 - 4  (0) 2021.07.25
오브젝트 리뷰 - 3  (0) 2021.07.21
오브젝트 리뷰 - 2  (0) 2021.07.12
오브젝트 리뷰 - 1  (0) 2021.07.11

오브젝트 리뷰 - 3

기술/아키텍처 2021. 7. 21. 19:00 Posted by 아는 개발자

https://book.naver.com/bookdb/book_detail.nhn?bid=15007773

 

오브젝트

역할, 책임, 협력을 향해 객체지향적으로 프로그래밍하라!객체지향으로 향하는 첫걸음은 클래스가 아니라 객체를 바라보는 것에서부터 시작한다. 객체지향으로 향하는 두번째 걸음은 객체를

book.naver.com

Chapter 5 리뷰 

 

책임 주도 설계를 향해 

 

데이터보다 행동을 먼저 결정하라. 객체에게 중요한 것은 데이터가 아니라 외부에 제공하는 행동이다. 클라이언트 관점에서 객체가 수행하는 행동이란 곧 객체의 책임을 의미한다. 

 

협력이라는 문맥 안에서 책임을 결정하라. 객체에게 할당된 책임의 품질은 협력에 적합한 정도로 결정된다. 객체에게 할당된 책임이 협력에 어울리지 않는다면 그 책임은 나쁜 것이다. 협력에 적합한 책임을 수확하기 위해선 객체를 결정한 후에 메시지를 선택하는 것이 아니라 메시지를 결정한 후에 객체를 선택해야 한다.

 

올바른 객체지향 설계는 클라이언트가 전송할 메시지를 결정한 후에 비로소 객체의 상태를 저장하는 데 필요한 내부 데이터에 관해 고민하기 시작한다. 결록적으로 협력이라는 문맥 안에서 객체가 수행할 책임에 초점을 맞춘다. 

 

책임 할당을 위한 GRASP 패턴 

 

INFORMATION EXPERT

 

객체에게 책임을 할당하는 첫 번째 원칙은 책임을 수행할 정보를 알고 있는 객체에게 책임을 할당하는 것이다. GRASP에서는 이를 INFORMATION EXPERT(정보 전문가) 패턴이라고 부른다. 정보 전문가 패턴은 객체가 자신이 소유하고 있는 정보와 관련된 작업을 수행한다는 일반적인 직관을 표현하는 것이며 여기서 말하는 정보는 데이터와는 다르다. 정보를 알고 있다고 해서 정보를 저장하고 있을 필요는 없다.

 

LOW COUPLING, HIGH COHESION

 

책임을 할당할 수 있는 다양한 대안들이 존재한다면 응집도와 결합도의 측면에서 더 나은 대안을 선택하는 것이 좋다. 다시 말해 두 협력 패턴 중에서 높은 응집도와 낮은 결합도를 얻을 수 있는 설계가 있다면 그 설계를 선택해야 한다는 것이다. GRASP에서는 이를 LOW COUPLING(낮은 결합도) HIGH COHESION(높은 응집도) 패턴이라고 부른다. 설계를 진행하면서 책임과 협력의 품질을 검토하는 데 사용할 수 있는 중요한 평가 기준이다.

 

응집도가 낮다는 것은 연관성이 없는 기능이나 데이터가 하나의 클래스 안에 뭉쳐있다는 것을 의미한다. 문제를 해결하기 위해선 변경의 이유에 따라서 클래스를 분리해야한다. 먼저 변경의 이유가 하나 이상인 클래스를 찾는 것으로부터 시작하는 것이 좋다. 변경의 이유를 파악할 수 있는 첫번째 방법은 인스턴스 변수가 초기화되는 시점을 살펴보는 것이다. 응집도가 높은 클래스는 인스턴스를 생성할 때 모든 속성을 함께 초기화 한다. 반면 응집도가 낮은 클래스는 객체의 속성 중 일부만 초기화하고 일부는 초기화되지 않은 상태로 남겨져 있다. 함께 초기화되는 속성을 기준으로 코드를 분리해야 한다. 

 

두번째 방법은 메서드들이 인스턴스 변수를 사용하는 방식을 살펴보는 것이다. 모든 메서드가 객체의 모든 속성을 사용하면 응집도가 높다고 볼 수 있으나 속성에따라 그룹별로 나뉜다면 클래스의 응집도가 낮다고 볼 수 있다. 응집도를 높이기 위해서는 속성 그룹과 해당 그룹에 접근하는 메서드 그룹을 기준으로 코드를 분리해야 한다.

 

POLYMORPHISM, PROTECTED VARIATION 

 

변화가 예상되는 불안정한 지점을 식별하고 그 주위에 안정된 인터페이스를 형성하도록 책임을 할당하라. PROTECTED VARIATION(변경 보호) 패턴은 책임 할당의 관점에서 캡슐화를 설명한 것이다. 클래스를 변경에 따라 분리하고 임터페이스를 이용해 변경을 캡슐화 하는 것은 설계의 결합도와 응집도를 향상시키는 매우 강력한 방법이다. 하나의 클래스가 여러 타입의 행동을 구현하고 있는 것처럼 보인다면 클래스를 분해하고 POLYMORPHISM 패턴에 따라 책임을 분산시켜라. 예측 가능한 변경으로 인해 여러 클래스들이 불안정해진다면 PROTECTED VARIATION 패턴에 따라 안정적인 인터페이스 뒤로 변경을 캡슐화하라. 

 

책임 주도 설계의 대안 

 

최대한 빠르게 목적한 기능을 수행하는 코드를 작성하고 리팩토링 한다.

728x90

'기술 > 아키텍처' 카테고리의 다른 글

오브젝트 리뷰 - 5  (0) 2021.07.27
오브젝트 리뷰 - 4  (0) 2021.07.25
오브젝트 리뷰 - 3  (0) 2021.07.21
오브젝트 리뷰 - 2  (0) 2021.07.12
오브젝트 리뷰 - 1  (0) 2021.07.11
응집도(Cohesion)와 결합도(Coupling)  (0) 2021.07.10

오브젝트 리뷰 - 2

기술/아키텍처 2021. 7. 12. 20:05 Posted by 아는 개발자

https://book.naver.com/bookdb/book_detail.nhn?bid=15007773

 

오브젝트

역할, 책임, 협력을 향해 객체지향적으로 프로그래밍하라!객체지향으로 향하는 첫걸음은 클래스가 아니라 객체를 바라보는 것에서부터 시작한다. 객체지향으로 향하는 두번째 걸음은 객체를

book.naver.com

지난번 글에 이어 리뷰를 추가한다. 

 

협력, 책임, 역할 

 

객체지향 패러다임의 관점에서 핵심은 역할(role), 책임(responsibility), 협력(collaboration) 이다. 객체지향의 본질은 협력하는 객체들의 공동체를 창조하는 것이다. 협력을 구성하기위해 적절한 객체를 적절한 책임을 할당하는 과정이 필요하다 (p73)

 

협력 

 

- 협력이란 객체들이 애플리케이션의 기능을 구현하기 위해 수행하는 상호작용을 말한다. (p74)  

- 두 객체 사이의 협력은 하나의 객체가 다른 객체에게 도움을 요청할 때 시작되며 메시지 전송(message sending) 커뮤니케이션으로 이뤄진다. 메시지를 수신한 객체는 메서드를 실행해 요청에 응답한다. (p75)

- 객체는 자신에게 할당된 책임을 수행하던 중에 필요한 정보를 알지 못하거나 외부의 도움이 필요한 경우 적절한 객체에게 메시지를 전송해서 협력을 요청한다. (p76) 

- 객체가 참여하고있는 협력이 객체의 행동을 결정한다. 객체의 상태를 결정하는 것은 객체의 행동이다. 객체의 상태는 그 객체가 행동을 수행하는 데 필요한 정보가 무엇인지로 결정된다. 따라서 객체가 참여하는 협력이 객체를 구성하는 행동과 상태를 모두 결정한다. 협력은 객체를 설계하는데 필요한 일종의 문맥(context)를 제공한다 (p77)

 

책임 

 

- 협력에 참여하기 위해 객체가 수행하는 행동을 책임이라고 부른다. (p78)

- 협력을 설계하는 출발점은 시스템이 사용자에게 제공하는 기능을 시스템이 담당할 하나의 책임으로 보는 것이다. 객체지향 설계는 협력에 필요한 메시지를 찾고 메시지에 적절한 객체를 선택하는 반복적인 과정을 통해 이뤄진다 (p83)

- 객체에게 책임을 할당하는데 필요한 메시지를 먼저 식별하고 메시지를 처리할 객체를 나중에 선택하는것이 중요하다. 객체가 메시지를 선택하는 것이 아니라 메시지가 객체를 선택하게 한다. (p84) 

- 객체를 객체답게 만드는 것은 객체의 상태가 아니라 객체가 다른 객체에게 제공하는 행동이다. (p85) * 여기서 말하는 객체의 상태는 객체가 갖고있는 데이터를 의미하는 것 같다. 

- 책임을 찾고 책임을 담당할 객체를 찾아 책임을 할당하는 방식으로 협력을 설계하는 방식을 RDD(Responsibility DrivenDesign) 이라 부른다 (p83)

 

역할 

 

- 객체가 어떤 특정한 협력 안에서 수행하는 책임의 집합을 역할이라고 부른다. 역할이 중요한 이유는 역할을 통해 유연하고 재사용 가능한 협력을 얻을 수 있기 때문이다. (p86, 87)

- 협력을 구체적인 객체가 아니라 추상적인 역할의 관점에서 설계하면 협력이 유연하고 재사용 가능해진다. 역할의 가장 큰 장점은 설계의 구성요소를 추상화할 수 있다는 것이다. (p92)

- 추상화의 첫번째 장점은 세부사항에 억눌리지 않고도 상위 수준의 정책을 쉽고 간단하게 표현할 수 있다는 것이고 두번째 장점은 설계를 유연하게 만들 수 있다는 것이다. 협력 안에서 역할이라는 추상화를 이용하면 기존코드를 수정하지 않고도 새로운 행동을 추가할 수 있다 (p94)

 

 

728x90

'기술 > 아키텍처' 카테고리의 다른 글

오브젝트 리뷰 - 4  (0) 2021.07.25
오브젝트 리뷰 - 3  (0) 2021.07.21
오브젝트 리뷰 - 2  (0) 2021.07.12
오브젝트 리뷰 - 1  (0) 2021.07.11
응집도(Cohesion)와 결합도(Coupling)  (0) 2021.07.10
클린 아키텍처  (0) 2021.05.20

오브젝트 리뷰 - 1

기술/아키텍처 2021. 7. 11. 15:53 Posted by 아는 개발자

https://book.naver.com/bookdb/book_detail.nhn?bid=15007773

 

오브젝트

역할, 책임, 협력을 향해 객체지향적으로 프로그래밍하라!객체지향으로 향하는 첫걸음은 클래스가 아니라 객체를 바라보는 것에서부터 시작한다. 객체지향으로 향하는 두번째 걸음은 객체를

book.naver.com

 

객체지향 프로그래밍을 다룬 오브젝트를 읽으며 나의 잘못된 과거의 코딩이 생각나던 구절, 나도 모르게 밑줄을 치게 되던 주옥같은 문장, 기억해야할 용어를 정리해본다. 이 책의 모든 내용을 한 포스트에 정리하긴 어려울 것 같고 공부한 날짜별로 메모하고 싶은 내용만 담아본다. 기본적으로 책의 내용을 참조 했지만 공부한 내용을 요약해서 정리하고자 몇몇 문장과 단어를 추가하고 수정했다. 그리고 페이지 별로 주요 문장을 뽑아냈기 때문에 목차별로 전달하고자 하는 핵심 메시지가 누락돼 저자의 의도가 제대로 전달되지 않을 수 있다. 추상적이거나 애매해보이는 보이는 표현은 책을 통해서 확인 바란다.

 

캡슐화의 개념과 목적

 

- 개념적이나 물리적으로 객체 내부의 세부적인 사항을 감추는 것. 목적은 변경하기 쉬운 객체로 만드는 것이며, 캡슐화를 통해 객체 내부 접근을 제한하면 객체와 객체 사이의 결합도를 낮아지기 때문에 변경하기 쉬운 객체로 만드는게 가능하다. (p20)

 

응집도가 높은 객체란

 

- 밀접하게 연관된 작업만을 수행하고, 연관성 없는 작업은 다른 객체에게 위임하는 객체. 스스로 자신의 데이터에 책임을 지는 객체가 응집도가 높다고 말할 수 있다. (p26)

 

절차지향 프로그래밍과 객체지향 프로그래밍

 

- 프로세스와 데이터를 별도의 모듈에 위치시키는 방식을 절차적 프로그래밍(Procedural Programming)이라 부르며 반대로 데이터와 프로세스가 동일한 모듈 내부에 위치하도록 프로그래밍 하는 방식을 객체지향 프로그래밍(Object-Oriented Programming)이라 한다. (p26) 여기서 프로세스는 처리하는 작업을 담당하는 메서드에 해당하고 데이터는 작업을 처리할 때 필요한 정보를 말한다. 

 

- 절차지향 프로그래밍에서는 별도의 모듈에 프로세스와 데이터가 있기 때문에 의존관계가 높아진다. 반명 객체지향 프로그래밍에서는 연관관계가 있는 프로세스와 데이터가 하나의 모듈에 있기 대문에 의존관계가 낮아진다.

 

훌륭한 객체 지향 설계를 위한 원칙

 

- 캡슐화를 이용해 의존성을 적절히 관리함으로써 객체 사이의 결합도를 낮춘다 (p27)

- 객체지향 설계에서는 독재자가 존재하지 않고 각 객체에 책임이 적절하게 분배된다. 따라서 각 객체는 자신을 스스로 책임진다. (p28)

- 소프트웨어를 구성하는 모든 객체들이 자율적으로 행동하는 설계 (p34)

- 변경이 쉬우며 협력하는 객체 사이의 의존성을 적절하게 관리 (p36)

 

도메인 

 

- 문제를 해결하기 위해 사용자가 프로그램을 사용하는 분야를 도메인이라 부른다 (p41)

 

- 어떤 클래스가 필요한지를 고민하기 전에 어떤 객체들이 필요한지를 고민한다. 어떤 객체들이 어떤 상태와 행동을 가지는지 우선 결정한다. 그리고 객체를 독립적인 존재가 아니라 기능을 구현하기 위해 협력하는 공동체의 일원으로 봐야한다. (p41) 

 

자율적인 객체 

 

- 객체는 상태와 행동을 가지는 복합적인 존재이며 스스로 판단하고 행동하는 자율적인 존재.  (p44)

- 객체 내부에 대한 접근을 통제해 객체를 자율적인 존재로 만든다. 자율적으로 만드는 것은 외부의 의존성으로부터 자유로워지는 것을 의미한다. (p44) 

- 외부에서 접근가능한 부분을 퍼블릭 인터페이스(public interface)라고 하며 내부에서만 접근 가능한 부분을 구현(implementation)이라 부른다. (p44)

 

객체 간의 협력 

 

- 다른 객체의 인터페이스에 공개된 행동을 수행하도록 요청(Request) 할 수 있고 요청 받은 객체는 자율적인 방법에 따라 응답(Response)한다. (p49)

- 객체끼리 상호작용할 수 있는 유일한 방법은 메시지를 전송하는 것 뿐이다. 다른 객체에게 요청이 도착할 때 해당 객체가 메시지를 수신했다고 이야기한다. 수신된 메시지를 처리하기 위한 자신만의 방법을 메서드라고 한다 (p49)

 

상속과 다형성 

 

- 상속과 인터페이스를 사용하면 컴파일타임 의존성과 실행 시점의 의존성이 다를 수 있다. 클래스 사이의 의존성과 객체사이의 의존성이 동일하지 않을 수 있다. (p59)

- 상속과 인터페이스를 사용하면 코드가 유연해진다. 그러나 유연해진만큼 코드를 이해하고 디버깅하는 것은 어려워진다. 반대로 유연성을 억제하면 재사용과 확장가능성은 낮아진다 (p59) 

- 동일한 메시지를 전송하지만 실행되는 메서드는 수신되는 객체에 따라서 달라진다. 이를 다형성이라 부른다. 다형성은 컴파일 시간 의존성과 실행시간 의존성을 다를수 있는 사실을 기반으로 하며 동일한 메시지를 수신했을 때 객체의 타입에 따라 다르게 응답하는 능력을 말한다. (p 63)

 

728x90

'기술 > 아키텍처' 카테고리의 다른 글

오브젝트 리뷰 - 3  (0) 2021.07.21
오브젝트 리뷰 - 2  (0) 2021.07.12
오브젝트 리뷰 - 1  (0) 2021.07.11
응집도(Cohesion)와 결합도(Coupling)  (0) 2021.07.10
클린 아키텍처  (0) 2021.05.20
DIP(Dependency Inversion Principle)  (0) 2021.05.09

응집도(Cohesion)와 결합도(Coupling)

기술/아키텍처 2021. 7. 10. 12:16 Posted by 아는 개발자

모듈

 

응집도와 결합도를 설명하기 위해선 모듈이라는 단어를 사용해야하는데 모듈은 문맥에 따라서 의미가 달라지는 경우가 많아서 이 글에서는 클래스나 패키지, 라이브러리 같은 프로그램을 이루는 임의의 요소로 정의한다.

 

응집도 

 

모듈을 이루는 요소들의 연관성 척도다. 클래스내의 함수와 변수, 더 큰 범위에선 패키지 내의 개별 클래스가 하나의 목적으로 연관관계가 이뤄질 경우 응집도가 높고 그렇지 않은 경우에는 응집도가 낮은 것으로 판단한다. 응집도가 높을 수록 소프트웨어를 수정 할 경우 변경하게될 범위가 명확해지기 때문에 좋은 설계로 본다. 반대로 응집도가 낮을 수록 소프트웨어를 변경해야하는 이유가 많아지기 때문에 좋은 설계로 보기 어렵다.

 

결합도 

 

소프트웨어를 구성한 여러 모듈은 서로를 호출하는 관계를 가지게 되는데, 각각의 모듈이 서로를 의존하는 정도를 결합도라고 한다. 모듈간의 의존 관계가 생기면 하나의 모듈을 수정할 때 다른 모듈이 영향을 받게 된다. 결합도가 높을 수록 다른 모듈의 변경에 영향을 많이 받게 되고 반대로 낮을 수록 다른 모듈의 변경에 영향이 적게된다. 일반적으로 결합도가 낮을 수록 좋은 설계로 본다.

 

그림 1

 

위 그림에서 각 모듈간 의존 관계가 많다. 특히 모듈E는 A,B,C,D 모듈이 의존하고 있는 모듈이기 때문에 E가 변경된다면 A, B, C, D 모두 영향을 받게 되므로 결합도가 높은 좋지 못한 설계에 해당한다. 이런 경우 유지 보수가 어렵기 때문에 도메인 요구사항에 맞춰서 재설계 하는 편이 좋다.

 

그림 2

 

그림 2는 그림 1의 결합도 문제를 해결한 설계다. E에 의존했던 A,B 는 구조를 변경해 E를 참조하지 않아도 되게끔 수정했다. 또한 A와 D 간의 의존관계도 수정했다. 이전보다 의존 관계가 많이 줄었으므로 결합도가 낮아진 것으로 볼 수 있다. 

 

* 그림 2는 설명을 위한 예시다. 실제로 소프트웨어 상에서 결합도 문제를 해결하기 위한 과정은 이렇게 단순하지 않다.

728x90

'기술 > 아키텍처' 카테고리의 다른 글

오브젝트 리뷰 - 2  (0) 2021.07.12
오브젝트 리뷰 - 1  (0) 2021.07.11
응집도(Cohesion)와 결합도(Coupling)  (0) 2021.07.10
클린 아키텍처  (0) 2021.05.20
DIP(Dependency Inversion Principle)  (0) 2021.05.09
ISP (Interface Segregation Principle)  (0) 2021.05.09

클린 아키텍처

기술/아키텍처 2021. 5. 20. 19:51 Posted by 아는 개발자

 

로버트 마틴의 클린 코드에선 코드를 깔끔하게 잘 짜는 방법을 배웠다면 클린 아키텍처에서는 소프트웨어를 더 잘 만드는 방법을 배운 것 같다. 책의 표현을 빌리자면 클린 코드에서는 좋은 벽돌을 구분하는 방법을 배웠다면 클린 아키텍처에서는 좋은 벽돌로 건물을 짓는 방법을 배운 느낌이랄까. 책에선 저자가 경험한 내용을 바탕으로 전달하려는 교훈이 많다. 저수준, 고수준, 프레임워크는 세부사항일 뿐이다 등등.. 그런데 나의 소프트웨어 깊이가 부족해 공감하기 어려운 부분도 있었고 이해되지 않던 부분도 있어서 모두 소화하진 못했다. 그래도 연차가 늘어나고 더 큰 규모의 소프트웨어를 경험하다보면 이 책에서 내가 캐치하지 못했던 새로운 면이 보일 것 같아 기대된다. 2-3년 후에 다시 이 책을 읽어봐야 겠다.

 

많은 전달 내용 중 내 머릿속을 관통하는 소프트웨어 원칙은 이 그림으로 표현 할 수 있다.

 

 

다이어그램 상에선 컴포넌트A 가 컴포넌트B를 가리키고 있는 그림인데 소프트웨어상에선 컴포넌트A 가 컴포넌트B 에 의존한다는 의미의 그림이다. 이 의존 관계는 소프트웨어 상에서 생길 수 있는 가장 중요한 관계고 이 관계를 어떻게 정의하느냐에 따라서 소프트웨어의 아키텍처가 결정된다. 컴포넌트A는 저수준으로, 컴포넌트B는 고수준으로 둬야한다고 그림상에선 표현 했는데 여기서 말하는 수준은 어떤 컴포넌트가 우월한지를 결정하는 기준이 아니라 얼마만큼 변동성이 크냐를 기준으로 결정한다.

 

위 그림처럼 의존관계가 성립되려면 고수준인 컴포넌트B는 수정할 일이 적어야 한다. 그래야 컴포넌트A에 미치는 영향을 최소화 할 수 있기 때문이다. 위 그림처럼 결정되는 대표적인 예가 애플리케이션에서 String, Math 같은 자바 고유 라이브러리 클래스를 사용하는 경우다. 자바 버전에 따라서 클래스가 변경될 소지가 있지만 그래도 우리가 개발하는 클래스보다 변경될 소지는 적다. 이런 경우 의존 관계는 적절한 것으로 볼 수 있다.

 

그래도 가끔은 이런 의존 관계를 성립하기 힘든 경우도 있다. 둘다 변경의 소지가 크지만 두 클래스를 연결해야할 때가 있다. 이럴때 사용하는 방식이 의존성 역전원칙이다. 좀더 고수준으로 보이는 클래스에 특정 인터페이스를 만들고 이것과 상속 관계로 만든다. 그리고 저수준 클래스를 인터페이스에 의존하는 관계로 만든다. 인터페이스는 변경될 소지가 적기 때문에 이 의존 관계도 적절한 관계로 볼 수 있다.

 

 

물론 매번 이렇게 코딩할 수는 없다. 개발하다보면 새로운 함수도 추가해야돼 인터페이스도 손될 일이 많아지니까. 모든 원칙을 지키다 보면 오버 엔지니어링이돼 개발 프로세스가 느려지는 부작용도 생길 수 있다. 항상 모든 원칙을 지키기는 어려울 것 같다. 하지만 원칙을 알고 생략하는 것과 모르고 넘어가는 것은 차이가 크다. 앞으로 일하면서 어떤 원칙을 넘기면서 개발하고 있는지 되새겨봐야 할 것 같다.

728x90

'기술 > 아키텍처' 카테고리의 다른 글

오브젝트 리뷰 - 1  (0) 2021.07.11
응집도(Cohesion)와 결합도(Coupling)  (0) 2021.07.10
클린 아키텍처  (0) 2021.05.20
DIP(Dependency Inversion Principle)  (0) 2021.05.09
ISP (Interface Segregation Principle)  (0) 2021.05.09
LSP (Liskov Substitution Principle)  (0) 2021.05.09

DIP(Dependency Inversion Principle)

기술/아키텍처 2021. 5. 9. 08:57 Posted by 아는 개발자

좋은 아키텍처는 변동성이 큰 모듈에 의존하지 않는 것이다. 그런데 개발하다 보면 예상치 못한 버그도 종종 생기기 마련이기 때문에 어떤 클래스는 릴리즈마다 계속 수정을 할 수 밖에 없다. 그런데 이때마다 새로운 함수를 추가하고 새로운 변수가 등장한다면 이 클래스를 의존하는 다른 모듈에도 영향이 미친다. 이런 형태면 하나의 클래스를 수정하는데도 다른 클래스까지 영향을 주게 된다.

 

그림 1

위 그림에선 사용자가 버그가 많은 결제 시스템 클래스를 의존하고 있다. 지금까지 pay 함수에 버그가 많아서 3개의 레거시 함수가 있다. 이런 형태는 새로운 함수가 추가될 때 마다 사용자의 코드에 영향을 주게 되는 사례다.

 

해법은 인터페이스를 이용하는 것이다. 모듈은 안정화된 인터페이스에 의존하고 변동성이 큰 실제 구현체는 인터페이스를 바꾸지 않는 선에서 수정한다. 인터페이스가 바뀌지 않는것이 보장됐기 때문에 원래 실제 구현체에 의존하는 클래스는 수정이 있어도 코드를 수정하지 않아도 된다. 이런 철학으로 만든 원칙이 DIP(Dependency Inversion Principle) 의존성 역전 원칙이다.

 

그림 2

그림 2는 그림 1에서 DIP를 적용한 버전이다. 사용자는 안정화된 결제시스템 Interface를 참조하고 있기 때문에 수정할 일이 없다. 버그가 많은 결제시스템만 수정해도 소프트웨어의 안정성은 보장된다. 요즘에는 프레임워크 차원에서 이렇게 구현할 수 있도록 지원하고 있다. 안드로이드의 Hilt, Dagger가 DIP를 지원하는 대표적인 라이브러리니 아직 사용해보지 않은 분들은 한번 써보는게 좋을 것 같다.

728x90

'기술 > 아키텍처' 카테고리의 다른 글

응집도(Cohesion)와 결합도(Coupling)  (0) 2021.07.10
클린 아키텍처  (0) 2021.05.20
DIP(Dependency Inversion Principle)  (0) 2021.05.09
ISP (Interface Segregation Principle)  (0) 2021.05.09
LSP (Liskov Substitution Principle)  (0) 2021.05.09
OCP (Open Closed Principle)  (0) 2021.05.04

ISP (Interface Segregation Principle)

기술/아키텍처 2021. 5. 9. 08:28 Posted by 아는 개발자

ISP는 필요 이상 많은 것을 포함하는 모듈에 의존하지 말자는 원칙에서 유래됐다. 이것도 사례를 먼저 보자.

 

위 그림을 보면 결제관리, 환불관리, 포인트 관리 객체가 시스템 클래스를 참조하고 있다. 그런데 결제관리 객체는 pay 함수만 호출 할 것이고, 환불 관리는 refund, 포인트관리는 addPoint함수만 사용할 것이다. 각 객체의 입장에서는 필요 이상으로 시스템 클래스에 의존하고 있는 형태다. 만약 객체에서 불필요하게 다른 함수를 호출한다면 에러가 발생할 위험도 있다.

 

이런 경우에는 시스템을 각각 쪼개주는 방법이 있다. 결제시스템, 환불시스템, 포인트 시스템을 각각 인터페이스로 만들어고 시스템 클래스가 인터페이스의 하위타입으로 구현한다. 그리고 각각의 객체는 관계가 있는 인터페이스에만 의존하도록 만들면 의존하는 모듈에 불필요하게 포함된 함수들을 제거할 수 있다.

728x90

'기술 > 아키텍처' 카테고리의 다른 글

클린 아키텍처  (0) 2021.05.20
DIP(Dependency Inversion Principle)  (0) 2021.05.09
ISP (Interface Segregation Principle)  (0) 2021.05.09
LSP (Liskov Substitution Principle)  (0) 2021.05.09
OCP (Open Closed Principle)  (0) 2021.05.04
SRP (Single Responsibility Principle)  (0) 2021.05.04

LSP (Liskov Substitution Principle)

기술/아키텍처 2021. 5. 9. 08:11 Posted by 아는 개발자

LSP는 1988년에 미국MIT 공과대학 교수였던 바버라 리스코브가 제안한 것으로, 상호 대체 가능한 구성요소를 이용해 소프트웨어 시스템을 만들 수 있으려면, 이들 구성요소는 반드시 서로 치환가능해야 한다는 원칙이다. 문장으로 보면 무슨뜻인지 정확히 파악하기 어려우나 원칙을 지킨 사례와 어긴 사례를 보면 어떤 의미인지 감을 잡을 수 있을 것이다.

 

원칙을 지킨 사례

위 그림은 원칙을 지킨 사례다. 구매자가 결제 시스템을 사용하는데 결제 시스템에선 네이버페이, 카카오페이 둘중 하나를 사용하고 있다. 구매자는 네이버페이를 사용하던, 카카오페이를 사용하던 동일한 결제시스템 인터페이스를 사용하게 될 것이고 영향 받지 않는다. 따라서 결제 시스템은 필요에 따라 하위타입을 치환 가능하다. 객체지향형 프로그램을 개발해본 사람이라면 이렇게 설계하는 건 매우 당연한 일이다.

 

원칙을 어긴 사례

위 그림은 원칙을 어긴 유명한 사례다. 사용자는 직사각형에 의존하고 있는데 직사각형의 하위 타입은 정사각형으로 선언돼있다. 수학적으로보면 정사각형은 직사각형에 포함되기 때문에 위 관계가 맞으나 위 설계상에선 위 관계가 적합하지 않다. 사용자가 의존하는 직사각형 클래스에는 setWidth, setHeight 함수가 있다. 너비와, 높이를 설정해주는 함수다. 그런데 정사각형은 높이가 너비가 같은 사각형이다. setWidth, setHeight 함수가 의도한 대로 동작하지 않게 되므로 정사각형은 하위타입으로서 적합하지 않다.

728x90

'기술 > 아키텍처' 카테고리의 다른 글

클린 아키텍처  (0) 2021.05.20
DIP(Dependency Inversion Principle)  (0) 2021.05.09
ISP (Interface Segregation Principle)  (0) 2021.05.09
LSP (Liskov Substitution Principle)  (0) 2021.05.09
OCP (Open Closed Principle)  (0) 2021.05.04
SRP (Single Responsibility Principle)  (0) 2021.05.04

OCP (Open Closed Principle)

기술/아키텍처 2021. 5. 4. 18:39 Posted by 아는 개발자

SOLID 원칙의 두번째 원칙인 OCP (Open-Closed Principle)은 소프트웨어가 기존 코드를 수정하기보단, 새로운 코드를 추가함으로써 시스템의 행위를 변경할 수 있는 설계 원칙이다. OCP 원칙을 들어보지 못했더라도 Spring이나 안드로이드 최신 개발 프레임워크에서 지향하는 구조 (Spring Repository-Service-Controller 구조, 안드로이드 MVVM)를 사용하고 있다면 암묵적으로 OCP 원칙에 따르고 있게 되는데 이 원칙의 핵심은 소프트웨어를 이루는 컴포넌트를 저수준에서 고수준으로 계층화하는 것이다.

 

위 그림은 웹페이지를 출력하는 클라이언트를 단순하게 표현한 것이다. Interactor는 서버에 있는 데이터를 가져 올 수 있는 인터페이스의 역할을 하고, Controller는 Interactor를 통해서 노출할 정보를 가져오고 Presenter는 Controller에서 가져온 정보를 일차적으로 처리한 후 WebView는 Presenter에서 일차적으로 처리한 정보를 이용해 화면에 노출한다. 데이터의 연산이 가장 적은 WebView는 가장 저수준의 컴포넌트고 서버와 맞닿아 있는 Interactor는 가장 고수준의 소프트웨어다. 

 

컴포넌트간의 의존 관계가 일방향이기 때문에 컴포넌트의 변화가 미치는 영향을 최소화 할 수 있어 소프트웨어의 수정이 쉬워진다. 화면상의 버튼의 위치를 수정하게 되는 경우에는 WebView만 수정하면 되게 되고 불러오는 일부 데이터 처리의 변경하고 싶다면 Presenter를, 불러오는 데이터를 변경한다면 Controller를 수정하면 된다. 변경하려는 구조가 저수준에서 고수준으로 갈수록 수정이 미치는 영향이 커지지만, 저수준의 컴포넌트의 변화에서 고수준 컴포넌트를 보호 할 수 있어 유지관리에 유리하다. 

 

 

728x90

'기술 > 아키텍처' 카테고리의 다른 글

클린 아키텍처  (0) 2021.05.20
DIP(Dependency Inversion Principle)  (0) 2021.05.09
ISP (Interface Segregation Principle)  (0) 2021.05.09
LSP (Liskov Substitution Principle)  (0) 2021.05.09
OCP (Open Closed Principle)  (0) 2021.05.04
SRP (Single Responsibility Principle)  (0) 2021.05.04

SRP (Single Responsibility Principle)

기술/아키텍처 2021. 5. 4. 17:30 Posted by 아는 개발자

소프트웨어 디자인 원칙으로 유명한 SOLID에서 처음으로 소개되는 항목 SRP는 Single Responsibility Principle 의 준말이다. 풀네임의 뜻을 통해 원칙의 의미를 추측한다면 "모든 모듈은 하나의 일만 해야 한다"로 오해하기 쉬운데 이건 함수인 경우에 적용되는 원칙이지 모듈 범위에서 SRP가 뜻하는 바는 이와 다르다. 디자인 원칙에서 SRP는 "하나의 모듈은 하나의, 오직 하나의 액터에 대해서만 책임져야 한다"는 것을 의미한다. 아마 이 문장의 느낌이 바로 와닿지가 않을 것 같기 때문에 다음 예제를 소개한다.

 

위 그림 상에의 Employee 클래스는 초과근무 시간(overtimeHours)을 속성으로 갖고 있고 재무팀에선 이 정보를 이용해 초과근무 수당을 계산하는 함수를(overtimeHours) 관리하고, 인사팀에서는 허위로 초과근무 시간을 채우는 사람이 없는지 감시하고자 초과근무 시간을 보고하는 함수(reportHours)를 관리하고 있다. 두개의 액터가 하나의 클래스를 변경 할 수 있는 요인이기 때문에 이미 SRP원칙을 어긴 구조다.

 

갑자기 인사팀에서 초과 근무 시간을 보고하는 함수를 수정해야하는 일이 생겼다. 할 일은 많은데 CEO는 계속 초과근무 시간을 줄이라고 하니 어쩔 수 없이 데이터를 조작하기로 했다(실제로 그러시면 안됩니다). 회사원 별로 근무한 초과 시간에서 30% 를 낮추고자 했는데 문제는 reportHours() 함수의 리턴 값을 수정한게 아니라 클래스의 속성값인 overtimeHours를 수정한 것이다. 이 속성값은 인사팀만 사용하는 것이 아니라 재무팀도 사용하고 있었기 때문에 calculatePay 함수가 영향을 받게 됐고 근로자는 실제로 초과 근무한 시간의 30%를 제외한 부분만 인정돼 수당의 70% 만 받게되는 사건이 벌어졌다. 그 결과 재무팀은 초과근무한 직원들로부터 끝없는 컴플레인을 받게 됐고 이 소식을 들은 CEO는 인사팀이 데이터를 조작했다는 사실을 알게돼 인사팀장이 아주 난처해졌다는 웃픈 가상의 이야기.

 

몇몇 분들은 바보 같이 overtimeHours를 수정한게 문제라고 할 것 같다. 개발자가 공통으로 사용하고 있는 속성 값을 건드린 건 아마추어적인 실수긴 하다. 그러나 실제 코딩을 해보면 이런 아마추어적인 실수를 종종 하게된다. 모든 코드를 분석할 시간이 없기 때문에 하나의 클래스를 참조하는 모든 연관관계를 보는 것은 불가능하다. 그래서 등장한 소프트웨어 원칙은 이런 실수를 미리 만들지 않게끔 구조를 세울 수 있도록 한다. 앞서 소개한 예제를 SRP원칙을 적용해 변형한 버전은 이렇게 바꿔볼 수 있다.

 

HoursRepoter, PayCalculator 두 개의 클래스가 새로 생겼고 각각의 클래스는 인사팀, 재무팀이 관리한다. Employee는 HoursRepoter클래스와 PayCalculator 클래스를 참조해 calculatePay 와 reportHour 함수의 리턴값을 처리한다. overtimeHours는 인사팀, 재무팀 모두 변경할 수 없게끔 분리했기 때문에 앞으로 초과근무 수당이 미지급되는 사건이 벌어진다면 모두 재무팀의 문제로(?) 돌릴 수 있게 됐다. 

 

SRP원칙을 처음 들어봤을 땐 쉽다고 생각했는데 예제를 공부하면서 깊이 생각해보게 되고 내가 짠 코드를 보면 볼수록 더 깊이가 있는 원칙인 것 같다. 클래스 구조를 단순하게만 바라보게 되는건 아닌지 다시 한번 생각해보게끔 하는 원칙이다.

728x90

'기술 > 아키텍처' 카테고리의 다른 글

클린 아키텍처  (0) 2021.05.20
DIP(Dependency Inversion Principle)  (0) 2021.05.09
ISP (Interface Segregation Principle)  (0) 2021.05.09
LSP (Liskov Substitution Principle)  (0) 2021.05.09
OCP (Open Closed Principle)  (0) 2021.05.04
SRP (Single Responsibility Principle)  (0) 2021.05.04