Software Engineering

소프트웨어 공학: 컴포넌트 컴포지션 (Composition) - 새로운 컴포넌트의 효율적 재구성과 조합

habana4 2024. 9. 29. 21:42
반응형
 

CBSE의 중요한 개념은 컴포지션입니다.

컴포지션을 통해 또 다른 새로운 컴포넌트를 생성할 수 있어요.

이번 포스팅에서는 컴포지션의 개념에 대해 살펴 보도록 할게요.

 

 

이 글은 Ian Sommervile의 "Software Engineering, 9th Edition"에 기반하여 정리되었습니다.

 

 

컴포넌트 컴포지션은 컴포넌트와 특별히 작성된 ’글루 코드(glue code)’를 서로 통합하여 시스템이나 다른 컴포넌트를 만드는 과정입니다. 컴포넌트 컴포지션 방법에는 여러 가지가 있으며, 다음 17.10에서 그 예를 보여주고 있습니다. 왼쪽에서 오른쪽으로 차례로 순차적 컴포지션, 계층적 컴포지션, 그리고 부가적 컴포지션성을 나타냅니다. 아래의 논의에서는 두 개의 컴포넌트(A와 B)를 결합하여 새로운 컴포넌트를 만드는 상황을 가정합니다:

그림 17.10 컴포넌트 컴포지션 유형

 

1. 순차적 컴포지션은 그림 17.10의 상황 (a)입니다.

기존의 두 개의 컴포넌트를 순서대로 호출하여 새로운 컴포넌트를 만듭니다. 이 컴포지션은 "Provides Interface"의 컴포지션으로 생각할 수 있습니다. 즉, 컴포넌트 A가 제공하는 서비스를 호출하고, A에서 반환된 결과를 컴포넌트 B가 제공하는 서비스 호출에 사용합니다. 순차적 컴포지션에서는 컴포넌트들이 서로를 호출하지 않습니다. 컴포넌트 서비스를 올바른 순서로 호출하고 컴포넌트 A가 반환한 결과가 컴포넌트 B가 기대하는 입력과 호환되도록 하기 위해 추가적인 글루 코드가 필요합니다. 이 컴포지션의 "Provides Interface"는 A와 B의 결합된 기능에 의존하지만, 일반적으로 A와 B의 "Provides Interface"의 컴포지션은 아닙니다. 이 유형의 컴포지션은 프로그램 요소인 컴포넌트와 서비스인 컴포넌트 모두에 사용할 수 있습니다.

2. 계층적 컴포지션은 그림 17.10의 상황 (b)입니다.

이 유형의 컴포지션은 한 컴포넌트가 다른 컴포넌트가 제공하는 서비스를 직접 호출할 때 발생합니다. 호출된 컴포넌트는 호출 컴포넌트가 요구하는 서비스를 제공합니다. 따라서 호출된 컴포넌트의 "Provides Interface"는 호출 컴포넌트의 ‘"Requires Interface"와 호환되어야 합니다. 컴포넌트 A가 컴포넌트 B를 직접 호출하며, 인터페이스가 일치하면 추가적인 코드가 필요하지 않을 수 있습니다. 그러나 A의 "Requires Interface"와 B의 "Provides Interface" 간에 불일치가 있는 경우, 일부 변환 코드가 필요할 수 있습니다. 서비스에는 "Requires Interface"가 없으므로, 이 컴포지션 방식은 컴포넌트가 웹 서비스로 구현된 경우에는 사용되지 않습니다.

3. 부가적 컴포지션은 그림 17.10의 상황 (c)에 해당합니다.

이는 두 개 이상의 컴포넌트를 결합하여 그들의 기능을 결합한 새로운 컴포넌트를 만들 때 발생합니다. 새로운 컴포넌트의 "Provides Interface"와 "Requires Interface"는 A와 B의 해당 인터페이스의 결합입니다. 이 경우 컴포넌트 A와 B는 독립적이며, 서로를 호출하지 않습니다. 이 유형의 컴포넌트는 프로그램 단위인 컴포넌트와 서비스인 컴포넌트 모두에 사용할 수 있습니다.

 

시스템을 만들 때 모든 유형의 컴포넌트 컴포지션을 사용할 수 있습니다. 모든 경우에 컴포넌트를 연결하는 글루 코드를 작성해야 할 수도 있습니다. 예를 들어, 순차적 컴포지션의 경우, 컴포넌트 A의 출력이 일반적으로 컴포넌트 B의 입력이 됩니다. 중간 단계에서는 컴포넌트 A를 호출하고 결과를 수집한 다음, 그 결과를 매개변수로 컴포넌트 B를 호출해야 합니다. 한 컴포넌트가 다른 컴포넌트를 호출할 때, "Provides Interface"와 "Requires Interface"가 호환되도록 하는 중간 컴포넌트를 도입해야 할 수 있습니다.

 

컴포지션을 위해 새 컴포넌트를 작성할 때, 이러한 컴포넌트의 인터페이스를 시스템 내의 다른 컴포넌트와 호환되도록 설계해야 합니다. 이렇게 하면 이 컴포넌트를 쉽게 하나의 단위로 컴포지션할 수 있습니다. 그러나 재사용을 위해 독립적으로 개발된 컴포넌트를 사용할 때는 인터페이스의 불일치에 직면하게 되는 경우가 많습니다. 이는 컴포지션하려는 컴포넌트의 인터페이스가 동일하지 않다는 것을 의미합니다. 세 가지 유형의 불일치가 발생할 수 있습니다:

 

1. 매개변수 불일치: 인터페이스 양쪽의 연산이 같은 이름을 가졌지만, 매개변수의 유형이나 매개변수의 수가 다른 경우입니다.

2. 연산 불일치: ‘"Provides Interface"와 "Requires Interface"의 연산 이름이 다른 경우입니다.

3. 연산 불완전성: 한 컴포넌트의 ‘제공’ 인터페이스가 다른 컴포넌트의 "Requires Interface" 인터페이스의 부분 집합이거나 그 반대인 경우입니다.

 

모든 경우에서 두 컴포넌트의 인터페이스를 조정하는 어댑터를 작성하여 불일치 문제를 해결합니다. 어댑터 컴포넌트는 하나의 인터페이스를 다른 인터페이스로 변환합니다. 어댑터의 정확한 형태는 컴포지션 유형에 따라 다릅니다. 때로는 어댑터가 하나의 컴포넌트에서 나온 결과를 가져와서 다른 컴포넌트에 입력으로 사용할 수 있는 형식으로 변환합니다. 다른 경우에는 A가 B를 호출하고자 하지만, A의 "Requiess Interface"‘ 세부사항이 B의 "Provides Interface" 세부사항과 일치하지 않는 경우, 어댑터가 이를 조정하여 A의 입력 매개변수를 B의 요구된 입력 매개변수로 변환한 다음 B를 호출하여 A가 요구하는 서비스를 제공합니다.

 

어댑터를 설명하기 위해 그림 17.11에 표시된 인터페이스가 호환되지 않는 두 컴포넌트를 고려해 보겠습니다. 이들은 응급 서비스 시스템의 일부일 수 있습니다. 응급 상황에서 전화가 걸려오면, 전화번호가 입력되어 주소를 찾는 addressFinder 컴포넌트로 전달되고, 이후에 배치된 차량으로 보낼 지도를 인쇄하기 위해 mapper 컴포넌트가 사용됩니다. 사실, 이 컴포넌트는 여기서 보여주는 것보다 더 복잡한 인터페이스를 가질 수 있지만, 간단한 버전으로 어댑터의 개념을 설명할 수 있습니다.

그림 17.11 맞지 않는 인터페이스를 갖는 컴포넌트

 

첫 번째 컴포넌트인 addressFinder는 전화번호와 일치하는 주소를 찾습니다. 또한, 전화번호와 연관된 소유자 정보와 해당 부동산의 유형도 반환할 수 있습니다. mapper 컴포넌트는 우편번호를 사용하여 그 코드 주변의 지역을 나타내는 지도를 표시하거나 인쇄합니다.

 

이 컴포넌트들은 원칙적으로 조합이 가능하지만, addressFinder에서 위치 데이터를 가져와 우편번호를 추출하는 postCodeStripper라는 어댑터 컴포넌트를 작성해야 합니다. 이 우편번호는 mapper에 입력되어 1:10,000의 축척으로 지도가 표시됩니다. 다음 코드는 순차적 컴포지션의 예시이며, 이를 구현하기 위한 호출 순서를 보여줍니다.

address = addressFinder.location (phonenumber) ;
postCode = postCodeStripper.getPostCode (address) ;
mapper.displayMap(postCode, 10000) ;

 

어댑터 컴포넌트가 사용될 수 있는 또 다른 경우는 계층적 컴포지션에서 발생하며, 한 컴포넌트가 다른 컴포넌트를 사용하고자 하지만, 컴포지션의 컴포넌트 간에 "Provides Interface"와 "Requires Interface" 간에 불일치가 있는 경우입니다.

 

그림 17.12에서 어댑터가 데이터 수집기와 센서 컴포넌트를 연결하는 데 사용된 예를 보여주고 있습니다. 

그림 17.12 Data Collector와 Sensor 컴포넌트를 이어주는 어댑터 컴포넌트

 

센서와 데이터 수집기 컴포넌트는 어댑터를 사용하여 데이터 수집 컴포넌트의 "Requires Interface"와 센서 컴포넌트의 "Provides Interface"를 조정합니다. 데이터 수집기 컴포넌트는 센서 데이터 수집 및 센서 관리를 지원하는 일반적인 "Requires Interface"로 설계되었습니다. 이 작업들 각각에 대해 매개변수는 특정 센서 명령을 나타내는 문자열입니다. 예를 들어, 수집 명령을 발행하려면 “collect”라는 문자열을 사용하여 sensorData(“collect”)를 호출합니다. 그림 17.12에 표시된 것처럼, 센서 자체에는 시작, 중지, getdata와 같은 별도의 작업이 있습니다.

 

어댑터는 입력 문자열을 구문 분석하고 명령을 식별한 다음, 센서 값을 수집하기 위해 Sensor.getdata를 호출합니다. 그런 다음 결과를 문자열 형식으로 데이터 수집기 컴포넌트에 반환합니다. 이 인터페이스 스타일 덕분에 데이터 수집기는 다양한 유형의 센서와 상호작용할 수 있습니다. 데이터 수집기에서 센서 인터페이스로의 센서 명령을 변환하는 별도의 어댑터가 각 센서 유형에 대해 구현됩니다.

 

컴포넌트 컴포지션을 논의할 때, 컴포넌트 설명서만으로도 인터페이스가 호환되는지 여부를 알 수 있다고 가정하고 있습니다. 물론, 인터페이스 정의에는 작업 이름과 매개변수 유형이 포함되므로 이를 통해 호환성을 어느 정도 평가할 수 있습니다. 그러나 인터페이스가 의미론적으로 호환되는지 결정하려면 컴포넌트 설명서에 의존해야 합니다.

그림 17.13 Photo Library 컴포지션

 

이 문제를 설명하기 위해 그림 17.13에 표시된 컴포지션된 상황을 고려해 보겠습니다. 이 컴포넌트들은 디지털 카메라에서 이미지를 다운로드하고 사진 라이브러리에 저장하는 시스템을 구현하는 데 사용됩니다. 시스템 사용자는 추가 정보를 제공하여 사진을 설명하고 분류할 수 있습니다. 혼란을 피하기 위해, 여기에 모든 인터페이스 메서드를 표시하지는 않았습니다. 대신, 컴포넌트 설명서 문제를 설명하는 데 필요한 메서드만 표시합니다. Photo Library의 인터페이스에서 메서드는 다음과 같습니다:

public void addItem (Identifier pid ; Photograph p; CatalogEntry photodesc) ;
public Photograph retrieve (Identifier pid) ;
public CatalogEntry catEntry (Identifier pid);

 

Photo Library의 addItem 메서드에 대한 설명이 다음과 같다고 가정합니다:

이 메서드는 사진을 라이브러리에 추가하고 사진 식별자 및 카탈로그 설명자를 사진과 연결합니다.

 

이 설명은 컴포넌트가 수행하는 작업을 설명하는 것처럼 보이지만, 다음 질문을 고려해 보십시오:

 

이미 라이브러리에 사진과 연관된 사진 식별자가 있는 경우 어떻게 됩니까?

사진 설명자는 카탈로그 항목과 함께 사진에 연관됩니까? 즉, 사진을 삭제하면 카탈로그 정보도 삭제됩니까?

 

addItem의 비공식적인 설명만으로는 이러한 질문에 답할 수 없습니다. 물론, 메서드의 자연어 설명에 더 많은 정보를 추가할 수 있지만, 일반적으로 모호성을 해결하는 가장 좋은 방법은 인터페이스를 설명하기 위해 공식 언어를 사용하는 것입니다. 그림 17.14에 표시된 사양은 Photo Library 인터페이스 설명의 일부로, 비공식적인 설명에 정보를 추가합니다.

그림 17.14 Photo Library 인터페이스의 OCL Description

 

도표 17.14에서 사용된 사양은 UML의 일부인 객체 제약 언어(OCL)를 기반으로 한 표기법으로 정의된 전제 조건과 후제 조건을 사용합니다. OCL은 UML 객체 모델에서 제약 조건을 설명하기 위해 설계되었으며, 항상 참이어야 하는 술어, 메서드가 실행되기 전에 참이어야 하는 술어, 그리고 메서드가 실행된 후에 참이어야 하는 술어를 표현할 수 있습니다. 이것들은 불변 조건, 전제 조건, 후제 조건입니다. 변수의 값을 작업 전에 액세스하려면 이름 뒤에 @pre를 추가합니다. 예를 들어 age라는 변수를 사용하면:

age = age@pre + 1

 

이 문장은 작업 후에 age의 값이 그 작업 전보다 하나 더 많음을 의미합니다.

 

OCL 기반 접근 방식은 UML 모델에 의미 정보를 추가하기 위해 점점 더 많이 사용되고 있으며, OCL 설명은 모델 주도 엔지니어링에서 코드 생성기를 구동하는 데 사용될 수 있습니다. 일반적인 접근 방식은 Meyer의 계약에 의한 설계 접근 방식(Meyer, 1992)에서 파생되었으며, 이 접근 방식에서는 통신 객체의 인터페이스와 의무를 공식적으로 지정하고 런타임 시스템에서 이를 강제합니다. Meyer는 신뢰할 수 있는 컴포넌트를 개발하려면 계약에 의한 설계를 사용하는 것이 필수적이라고 제안합니다(Meyer, 2003).

 

그림 17.14는 Photo Library의 addItem 및 delete 메서드에 대한 사양을 포함하고 있습니다. 지정된 메서드는 키워드 context로 나타내며, 전제 조건과 후제 조건은 각각 키워드 pre 및 post로 표시됩니다. addItem의 전제 조건은 다음과 같습니다:

 

1. 라이브러리에 입력하려는 사진과 동일한 식별자를 가진 사진이 없어야 합니다.

2. 라이브러리가 존재해야 합니다. (라이브러리를 생성하면 항목 하나가 추가되어 라이브러리 크기가 항상 0보다 큽니다.)

3. addItem의 후제 조건은 다음과 같습니다:

라이브러리의 크기가 1 증가합니다(따라서 항목 하나만 추가됩니다).

동일한 식별자를 사용하여 검색하면 추가한 사진이 반환됩니다.

동일한 식별자를 사용하여 카탈로그를 조회하면 입력한 카탈로그 항목이 반환됩니다.

 

delete의 사양은 추가 정보를 제공합니다. 전제 조건은 항목을 삭제하려면 라이브러리에 있어야 하며, 삭제 후에는 사진을 더 이상 검색할 수 없으며 라이브러리 크기가 1 줄어들어야 한다고 명시합니다. 그러나 delete는 카탈로그 항목을 삭제하지 않습니다. 사진이 삭제된 후에도 카탈로그 정보를 계속 검색할 수 있습니다. 그 이유는 삭제된 사진이 왜 삭제되었는지, 새로운 위치는 어디인지 등을 카탈로그에 유지하려고 할 수 있기 때문입니다.

 

컴포넌트를 컴포지션하여 시스템을 만들 때, 기능 및 비기능 요구 사항 간의 잠재적 갈등, 시스템을 가능한 한 빨리 전달해야 하는 필요성과 요구 사항이 변경됨에 따라 시스템을 발전시킬 수 있는 필요성 간의 갈등이 발생할 수 있습니다. 고려해야 할 결정 사항은 다음과 같습니다:

 

1. 시스템의 기능 요구 사항을 가장 효과적으로 전달할 수 있는 컴포넌트 컴포지션은 무엇입니까?

2. 요구 사항이 변경될 때 복합 컴포넌트를 더 쉽게 수정할 수 있는 컴포지션은 무엇입니까?

3. 컴포지션된 시스템의 출현 특성은 무엇일까요? 이러한 특성은 성능 및 신뢰성과 같은 특성입니다. 전체 시스템이 구현된 후에만 이러한 특성을 평가할 수 있습니다.

 

불행히도, 많은 상황에서 컴포지션 문제에 대한 해결책이 서로 충돌할 수 있습니다. 예를 들어, 그림 17.15에 표시된 대로 두 가지 대체 컴포지션을 통해 시스템을 만들 수 있는 상황을 고려해 보십시오. 이 시스템은 다양한 출처에서 데이터를 수집하고 데이터베이스에 저장한 다음, 해당 데이터를 요약하는 다양한 보고서를 생성하는 데이터 수집 및 보고 시스템입니다.

그림 17.15 Data collection 컴포넌트와 Report generation 컴포넌트

 

여기에서 적응성(adaptability)과 성능 간의 잠재적 갈등이 있습니다. 컴포지션 (a)은 더 적응 가능하지만, 컴포지션 (b)은 아마도 더 빠르고 신뢰성이 있을 것입니다. 컴포지션 (a)의 장점은 보고서 작성과 데이터 관리를 분리하여 미래의 변경에 더 유연하다는 점입니다. 데이터 관리 시스템을 교체할 수 있고, 현재 보고서 컴포넌트가 생성할 수 없는 보고서가 필요할 경우, 데이터 관리 컴포넌트를 변경하지 않고도 보고서 컴포넌트를 교체할 수 있습니다.

 

컴포지션 (b)에서는 내장된 보고서 기능이 있는 데이터베이스 컴포넌트(예: Microsoft Access)가 사용됩니다. 컴포지션 (b)의 주요 장점은 더 적은 컴포넌트가 필요하므로, 컴포넌트 통신 오버헤드가 없어 더 빠른 구현이 가능하다는 것입니다. 또한, 데이터베이스에 적용되는 데이터 무결성 규칙은 보고서에도 적용됩니다. 이 보고서는 데이터를 잘못된 방식으로 결합할 수 없습니다. 컴포지션 (a)에서는 이러한 제약이 없으므로 보고서에서 오류가 발생할 수 있습니다.

 

일반적으로 좋은 컴포지션 원칙은 관심사의 분리 원칙을 따르는 것입니다. 즉, 각 컴포넌트가 명확하게 정의된 역할을 가지도록 시스템을 설계해야 하며, 이상적으로는 이러한 역할이 겹치지 않도록 해야 합니다. 그러나 두 개 또는 세 개의 별도 컴포넌트보다 하나의 다기능 컴포넌트를 구입하는 것이 더 저렴할 수 있습니다. 또한, 다수의 컴포넌트를 사용할 때 신뢰성이나 성능에 대한 불이익이 있을 수 있습니다.

반응형