C 라는 절차지향 언어에서 C++ 이라는 객체지향 언어가 등장한 이유는 '모듈화' 때문이다.
모듈화를 한다는 것은 소스코드의 각 조각을 독립적으로 만든다는 것이고,
각 조각이 독립적이라는 것은 다른 소스코드에 의존하지 않는다는 것이다.
또한 각 조각은 필요에 따라 얼마든지 쉽게 '재사용' 될 수 있다.
객체지향에서 소스코드를 재사용하기 위한 제일 핵심적인 개념은 '캡슐화(encapsulation)' 이다.
필요한 행위만 외부에 보여주고 나머지 디테일한 구현은 다 숨기는 것이다.
캡슐화를 할 때는 2가지를 지켜야 한다.
1. 외부에 보여질 인터페이스의 수 (public function) 를 최소화해야 한다.
2. 인터페이스를 한번 정의할 때, 시간이 지나도 수정되지 않도록 잘 정의해야 한다.
지금까지는 캡슐화의 범위를 '클래스' 단위로 봤다면, 이제 그 범위를 넓혀보자.
클래스들이 모인 단위를 '컴포넌트' 라고 부르고, 컴포넌트가 모이면 '서브시스템' 이 된다.
이렇게 계층이 확장될 때도 모든 레벨에서 '캡슐화' 개념을 적용해야 한다.
물론 이런 '캡슐화' 개념은 객체지향에서만 사용가능한 것은 아니다.
절차지향에서도 '캡슐화' 개념은 적용할 수 있다.
다만 객체지향이 캡슐화 개념을 적용하기에 유리한 언어일 뿐이다.
예를 들어 하나의 서비스를 개발할 때, 두 팀이 각각 서브시스템을 나눠서 개발하는 상황을 생각해보자.
이 두 팀에서는 제일 먼저 각 서브시스템이 제공할 '인터페이스' 를 먼저 정의해야 한다.
이때 상술했듯 인터페이스의 수는 최소화하고, 인터페이스가 시간이 지나도 바뀌지 않게끔 설계하는 것이 중요하다.
인터페이스를 정의한 뒤, 두 팀은 각자 돌아가서 그 인터페이스를 기반으로 각자의 서브시스템을 구현할 것이다.
그리고 이 둘은 어떻게 구현하더라도 인터페이스에만 맞춰 개발했다면 하나로 통합할 때 문제가 없을 것이다.
하지만 인터페이스가 바뀌면 모든게 깨진다.
한쪽의 인터페이스가 바뀌면 그 인터페이스를 기반으로 개발한 다른 서브시스템도 모두 수정해야 한다.
두 서브시스템 간의 '의존성' 이 생가버리는 것이다.
따라서 인터페이스는 매우 신중하게 정의해야 한다.
이제 컴포넌트 단위로 캡슐화(모듈화)를 적용하는 과정을 살펴보자.
먼저 '컴포넌트' 는 클래스들이 모여있는 모듈로, 독립적인 시스템 또는 서브시스템을 나타낸다.
'독립적' 이라는 것은 개발할 때 독립적으로 개발할 수 있다는 뜻이다.
클래스들을 모아서 '컴포넌트' 라는 단위를 만들었다면, 이 단위에 대한 인터페이스를 정의해야 한다.
컴포넌트는 인터페이스를 통해 다른 외부의 컴포넌트와 상호작용할 수 있다.
이때 서로는 각 컴포넌트 내부에 어떤 클래스들이 존재하는지 알아서는 안된다.
그래서 내가 어떤 기능을 사용할 때 다른 시스템의 어떤 기능이 필요하다면
그 기능을 마치 전원 플러그를 꼽듯이 딱 꼽아서 사용가능하도록 만들어야 한다.
즉, 어떤 컴포넌트는 다른 컴포넌트에게 서비스를 제공하기 위해 존재하는 것이다.
그리고 그 서비스는 인터페이스를 통해 제공한다.
또한 서비스를 이용하는 컴포넌트 쪽에서는 구체적으로 '어떻게' 서비스가 제공되는지 몰라도 된다.
그냥 원하는 서비스가 제공되기만 하면 된다.
소스코드의 '재사용' 관점에서 보았을 때 'Pattern' 이라는 개념도 생각해볼 수 있다.
패턴은 특정 문제에 대한 솔루션을 말한다.
반복적으로 자주 등장하는 문제에 대해, 반복적으로 등장한 풀이법을 '패턴' 으로 정의해서 이름붙인 것이다.
이에 대해서도 나중에에 몇 가지 예시와 함께 정리할 것이다.
이제 컴포넌트 사이의 관계를 모델링해보자.
컴포넌트 간 관계를 모델링할 때는 'Component Diagram' 을 그려서 모델링한다.
그리고 각 컴포넌트 간 인터렉션은 커뮤니케이션 다이어그램으로 모델링한다.
컴포넌트 다이어그램은 위 그림과 같이 그린다.
먼저 공책모양 아이콘은 이 요소가 컴포넌트임을 나타내는 아이콘이다.
특정 컴포넌트에서 인터페이스를 '제공' 하는 경우, 동그라미와 직접 연결된다.
동그라미는 '인터페이스' 를 나타내며, 위 그림에서는 주문 데이터를 조회하는 'takeOrder' 인터페이스를 보여주고 있다.
다른 컴포넌트의 인터페이스를 '사용' 하는 경우에는 동그라미를 기계손으로 잡는 듯한 모양으로 연결한다.
따라서 위 그림은 Payment 컴포넌트에서 Order 컴포넌트의 takeOrder 인터페이스를 사용하는 관계를 보여준다.
이때 takeOrder 와 같은 '인터페이스' 는 정말 형태만 정의한 '인터페이스' 를 나타낸다.
이 인터페이스의 구현은 Order 컴포넌트를 구성하는 여러 클래스들의 기능을 조합하여 구현한다.
외부에서는 컴포넌트를 구성하는 클래스는 보이지 않고 컴포넌트와 인터페이스만 보인다.
그리고 인터페이스에 들어있는 함수들은 커뮤니케이션 다이어그램을 통해서 표현한다.
따라서 커뮤니케이션 다이어그램을 그릴 때는 '컴포넌트' 는 그릴 필요가 없다.
컴포넌트 다이어그램에서 등장한 '인터페이스' 기준으로만 그 안에 필요한 함수들을 정의하면 된다.
정리하면 하나의 서비스를 설계할 때는 먼저 서브시스템과 서브시스템 사이의 인터페이스를 정의한다.
다음으로 서브시스템의 인터페이스를 구현할 때 활용될 컴포넌트와 각 컴포넌트의 인터페이스를 정의한다.
마지막으로 각 컴포넌트의 인터페이스를 구현할 때 활용될 클래스와 각 클래스의 인터페이스를 정의한다.
이제 구체적인 예시로 호텔 예약 시스템의 컴포넌트 다이어그램과 커뮤니케이션 다이어그램을 그려보자.
먼저 컴포넌트 다이어그램은 위와 같이 그릴 수 있다.
결제, 예약, 고객, 객실, 체크인 컴포넌트 5개로 구분하고
각 컴포넌트와 컴포넌트사이에서 활용할 인터페이스 이름을 정의한다.
이제 다음으로 커뮤니케이션 다이어그램을 그려보자.
커뮤니케이션 다이어그램도 '기능' 단위로 그리면 된다.
한번 객실을 온라인으로 예약한 고객이 실제 호텔에 와서 체크인하여 객실을 할당받는 과정을 그려보자.
커뮤니케이션 다이어그램은 컴포넌트를 제외한 '인터페이스' 단위로만 그리면 된다.
1. 고객정보 조회
먼저 온라인으로 예약한 고객 정보를 조회해야 한다.
CheckIn 인터페이스는 호텔 관리자와 상호작용하는 과정에서 제일 먼저 활용될 인터페이스로
먼저 예약 정보를 갖고 있는 reservationRef 로부터 getCustomer() 함수로 고객정보를 조회해온다.
CheckIn 컴포넌트와 Customer 컴포넌트는 직접 연결되어있지 않고 Reservation 컴포넌트와 간접적으로 연결되어있기 때문에 인터페이스를 통해서 읽어올 때도 그 흐름에 맞춰 고객 정보를 조회해온다.
먼저 reservationRef 가 담고 있는 데이터를 읽어들이기 위해
TakeReservation 인터페이스에서 getReservation 메서드를 정의한다.
이 메서드는 reservationRef 로부터 예약과 관련된 모든 정보를 읽어올 것이다.
이 정보 안에는 customerID 가 들어있다.
이 ID를 기반으로 고객의 실제 정보를 조회해오는 것은 ManageCustomer 인터페이스의 getCustomer 메서드가 수행한다.
가져온 고객정보는 return 값으로 CheckIn 인터페이스가 받아 화면에 뿌려줄 것이다.
2. 체크인
이제 해당 고객 정보로 체크인을 진행해야 한다.
먼저 CheckIn() 메서드를 호출하면 TakeReservation 인터페이스의 checkInCustomer() 메서드가 호출되어 해당 고객의 예약에 대해 체크인 처리한다.
3. 빈 방 조회
CheckIn 인터페이스는 getRoomsAvail() 메서드를 통해 빈 방을 조회한다.
이 메서드는 TakeReservation 인터페이스를 지나 AllocateRooms 인터페이스의 getEmptyRoom() 메서드를 통해 빈방을 조회한다.
빈 방 리스트는 재귀적으로 return 되어 화면에 보여진다.
4. 방 선택
고객이 빈 방 중에 하나를 선택하면 selectRoom() 메서드를 지나 AllocateRooms 인터페이스의 allocateRoom() 메서드를 통해 방이 실제로 할당된다.
5. 예약 마무리
예약의 상태를 마무리 짓기 위해 closeReservation() 메서드를 호출한다.
TakeReservation 인터페이스까지만 closeReservation() 메서드를 정의하면 된다.
이런 식으로 다른 use case 에 대해서도 커뮤니케이션 다이어그램을 그리면 각 인터페이스별로 메서드 형태가 정의된다.
이제 이 메서드의 형태에 맞춰 클래스를 정의하고 구현하면 된다.
'CS > 소프트웨어공학' 카테고리의 다른 글
[소프트웨어공학] 20. Design Pattern (0) | 2025.06.09 |
---|---|
[소프트웨어공학] 19. System Design and Architecture (0) | 2025.06.08 |
[소프트웨어공학] 17. State Machine 예제 (수정예정) (0) | 2025.06.07 |
[소프트웨어공학] 16. Designing Boundary Class (0) | 2025.06.07 |
[소프트웨어공학] 15. Detailed Design (2) | 2025.06.05 |