[태그:] 집합관계

  • 클래스 다이어그램 완벽 가이드: 시스템의 청사진을 그리는 기술

    클래스 다이어그램 완벽 가이드: 시스템의 청사진을 그리는 기술

    소프트웨어 개발이라는 복잡한 여정에서 모든 이해관계자가 같은 그림을 보며 나아가게 하는 등대와 같은 존재가 있다면, 그것은 바로 ‘클래스 다이어그램(Class Diagram)’일 것입니다. 객체 지향 시스템의 구조를 표현하는 가장 대표적이고 핵심적인 이 다이어그램은, 시스템을 구성하는 클래스들과 그들이 가지는 속성, 기능, 그리고 서로 간의 관계를 한눈에 볼 수 있는 청사진입니다. 이는 단순히 개발자들만의 기술 문서를 넘어, 제품 책임자(PO), 기획자, 디자이너, 테스터 모두가 시스템의 논리적 뼈대를 이해하고 소통하는 공용어 역할을 합니다.

    우리가 만들고자 하는 제품의 데이터 모델, 즉 ‘사용자’는 어떤 정보를 가져야 하는지, ‘상품’과 ‘주문’은 어떻게 연결되는지와 같은 비즈니스의 핵심 규칙이 바로 이 클래스 다이어그램 위에 그려집니다. 따라서 이 다이어그램을 읽고 해석하는 능력은 정보처리기사 자격증 취득을 위한 필수 지식일 뿐만 아니라, 성공적인 제품을 만들기 위해 시스템의 본질을 꿰뚫어 보는 통찰력을 제공합니다. 이번 포스팅에서는 클래스 다이어그램의 가장 기초적인 구성 요소부터 복잡한 관계 표현법, 그리고 실전 예제까지, 시스템의 청사진을 그리는 기술의 모든 것을 완벽하게 파헤쳐 보겠습니다.


    클래스 다이어그램의 기본 구성 요소: 사각형 하나에 담긴 의미

    클래스 이름 (Class Name)

    클래스 다이어그램의 가장 기본 단위는 클래스를 나타내는 하나의 사각형입니다. 이 사각형의 가장 윗부분에는 클래스의 이름이 명시됩니다. 클래스 이름은 해당 클래스가 시스템 내에서 어떤 개념이나 사물을 대표하는지를 나타내는 고유한 식별자입니다. 일반적으로 명확하고 간결한 명사를 사용하며, 여러 단어로 이루어질 경우 각 단어의 첫 글자를 대문자로 표기하는 파스칼 케이스(PascalCase)나 카멜 케이스(camelCase)를 따르는 것이 관례입니다. 예를 들어, 온라인 쇼핑몰 시스템이라면 UserProductShoppingCart 등이 클래스 이름이 될 수 있습니다. 이 이름만으로도 우리는 시스템이 어떤 핵심 요소들로 구성되어 있는지 대략적으로 짐작할 수 있습니다.

    속성 (Attributes)

    사각형의 두 번째 부분에는 클래스의 속성, 즉 클래스가 가지는 정적인 데이터나 상태 정보가 나열됩니다. 속성은 클래스의 특징을 나타내며, ‘변수’ 또는 ‘멤버 변수’라고도 불립니다. 예를 들어, User 클래스는 userIdpasswordnameemail 과 같은 속성을 가질 수 있습니다. 각 속성은 일반적으로 ‘접근 제한자 이름: 타입’의 형식으로 표현됩니다. name: String 은 ‘name’이라는 이름의 속성이 문자열(String) 타입의 데이터를 저장한다는 의미입니다. 이러한 속성 정의를 통해 우리는 해당 클래스의 인스턴스가 어떤 종류의 데이터를 저장하고 관리하는지를 명확히 알 수 있습니다.

    오퍼레이션 (Operations)

    사각형의 세 번째, 마지막 부분에는 클래스의 오퍼레이션이 위치합니다. 오퍼레이션은 클래스가 수행할 수 있는 행동이나 기능을 의미하며, ‘메서드(Method)’ 또는 ‘함수’라고도 불립니다. 이는 클래스의 동적인 책임을 나타냅니다. User 클래스는 login()logout()updateProfile() 과 같은 오퍼레이션을 가질 수 있습니다. 오퍼레이션은 보통 ‘접근 제한자 이름(파라미터): 반환타입’ 형식으로 기술됩니다. login(id: String, pw: String): boolean 이라는 표기는 login 이라는 오퍼레이션이 아이디와 비밀번호를 문자열로 입력받아, 로그인 성공 여부를 불리언(boolean) 타입으로 반환한다는 것을 의미합니다.

    접근 제한자 (Access Modifiers)

    속성과 오퍼레이션 앞에 붙는 기호는 접근 제한자를 나타내며, 객체 지향의 중요 원칙 중 하나인 ‘정보 은닉(Information Hiding)’을 표현합니다. 이는 클래스 외부에서 내부의 데이터나 기능에 얼마나 접근할 수 있는지를 제어하는 규칙입니다. 가장 흔히 사용되는 기호는 다음과 같습니다. + (public): 어떤 클래스에서든 자유롭게 접근 가능합니다. - (private): 해당 클래스 내부에서만 접근 가능하며, 외부에서는 접근할 수 없습니다. # (protected): 해당 클래스와 그 클래스를 상속받은 자식 클래스에서 접근 가능합니다. ~ (package): 같은 패키지에 속한 클래스들 사이에서만 접근 가능합니다. 일반적으로 속성은 private으로 설정하여 데이터를 보호하고, 오퍼레이션을 public으로 설정하여 외부와의 소통 창구로 사용하는 것이 좋은 설계 원칙으로 여겨집니다.


    클래스 간의 관계 1: 연관, 집합, 그리고 복합

    연관 관계 (Association)

    연관 관계는 클래스 다이어그램에서 가장 일반적으로 사용되는 관계로, 두 클래스가 개념적으로 서로 연결되어 있음을 나타냅니다. 이는 한 클래스의 인스턴스가 다른 클래스의 인스턴스와 관계를 맺고 서로의 존재를 인지하며 메시지를 주고받을 수 있음을 의미합니다. 다이어그램에서는 두 클래스를 잇는 실선으로 표현됩니다. 예를 들어, ‘학생(Student)’ 클래스와 ‘강의(Course)’ 클래스는 ‘수강한다’는 의미의 연관 관계를 가질 수 있습니다.

    연관 관계에서 중요한 요소는 ‘다중성(Multiplicity)’입니다. 이는 관계에 참여하는 인스턴스의 수를 나타내며, 선의 양 끝에 숫자로 표기됩니다. 1은 정확히 하나, 0..1은 없거나 하나, * 또는 0..*는 0개 이상, 1..*는 1개 이상을 의미합니다. 예를 들어, 한 명의 학생은 여러 개의 강의를 수강할 수 있고(1..*), 하나의 강의는 여러 명의 학생이 수강할 수 있으므로(*) 양쪽 다중성을 표기하여 관계를 더 구체화할 수 있습니다. 또한, 화살표를 사용하여 관계의 방향성(A가 B를 알지만, B는 A를 모름)을 나타낼 수도 있습니다.

    집합 관계 (Aggregation)

    집합 관계는 전체(Whole)와 부분(Part)의 관계를 나타내는 특별한 형태의 연관 관계입니다. 이는 ‘~을 소유한다(has-a)’의 의미를 가지지만, 전체와 부분의 생명주기가 독립적인 느슨한 결합을 의미합니다. 다이어그램에서는 전체 클래스 쪽에 속이 빈 다이아몬드를 붙여 표현합니다. 예를 들어, ‘컴퓨터’와 ‘키보드’, ‘마우스’의 관계가 바로 집합 관계입니다. 컴퓨터는 키보드와 마우스를 부분으로 가지지만, 컴퓨터가 없어져도 키보드와 마우스는 독립적인 부품으로 존재할 수 있습니다. 즉, 부분 객체가 전체 객체와 독립적으로 생성되고 소멸될 수 있습니다.

    복합 관계 (Composition)

    복합 관계 역시 전체와 부분의 관계를 나타내지만, 집합 관계보다 훨씬 강한 결합을 의미합니다. 복합 관계에서는 부분의 생명주기가 전체에 완전히 종속됩니다. 즉, 전체 객체가 생성될 때 부분이 함께 생성되고, 전체 객체가 소멸될 때 부분도 반드시 함께 소멸됩니다. 다이어그램에서는 전체 클래스 쪽에 속이 채워진 다이아몬드를 붙여 표현합니다. 가장 대표적인 예는 ‘집’과 ‘방’의 관계입니다. 방은 집의 일부이며, 집이 철거되면 그 안의 방도 함께 사라집니다. 방이 집 없이 독립적으로 존재할 수는 없습니다. 이처럼 복합 관계는 부분 객체가 다른 전체 객체와 공유될 수 없는, 강력한 소유 관계를 나타냅니다.


    클래스 간의 관계 2: 일반화, 의존, 그리고 실체화

    일반화 관계 (Generalization)

    일반화 관계는 객체 지향의 핵심 특징인 ‘상속(Inheritance)’을 표현하는 관계입니다. 이는 ‘~이다(is-a)’의 의미를 가지며, 더 일반적인 개념의 부모 클래스(Superclass)와 더 구체적인 개념의 자식 클래스(Subclass) 사이의 관계를 나타냅니다. 다이어그램에서는 자식 클래스에서 부모 클래스로 향하는, 속이 빈 화살표로 표현됩니다. 예를 들어, ‘동물’이라는 부모 클래스가 있고, ‘강아지’와 ‘고양이’라는 자식 클래스가 있다면, 강아지와 고양이는 각각 동물을 상속받습니다.

    이 관계를 통해 자식 클래스는 부모 클래스의 모든 속성과 오퍼레이션을 물려받아 그대로 사용할 수 있으며, 자신만의 고유한 속성이나 오퍼레이션을 추가하거나 부모의 기능을 재정의(Overriding)할 수도 있습니다. ‘동물’ 클래스에 eat()이라는 오퍼레이션이 있다면 ‘강아지’와 ‘고양이’는 이를 물려받아 바로 사용할 수 있습니다. 이는 코드의 재사용성을 극대화하고, 클래스 간의 계층 구조를 만들어 시스템을 더 체계적으로 관리할 수 있게 해줍니다.

    의존 관계 (Dependency)

    의존 관계는 클래스 간의 관계 중 가장 약한 연결을 나타냅니다. 이는 한 클래스가 다른 클래스를 임시적으로, 짧은 시간 동안만 사용하는 경우에 형성됩니다. 주로 어떤 클래스의 오퍼레이션을 실행할 때, 다른 클래스를 파라미터(매개변수)로 받거나, 오퍼레이션 내부에서 지역 변수로 생성하여 사용하는 경우에 발생합니다. 다이어그램에서는 사용하는 쪽에서 사용되는 쪽으로 점선 화살표를 그려 표현하며, ‘uses-a’ 관계로 설명할 수 있습니다.

    예를 들어, Driver 클래스의 drive(Car car) 오퍼레이션은 Car 타입의 객체를 파라미터로 받아서 사용합니다. 이 경우 Driver는 Car에 의존한다고 말할 수 있습니다. Car 클래스의 인터페이스가 변경되면 Driver 클래스의 drive 오퍼레이션도 영향을 받아 수정되어야 할 수 있기 때문입니다. 연관 관계와 달리, 의존 관계는 클래스가 상대방을 속성으로 유지하지 않는 일시적인 관계라는 점에서 차이가 있습니다.

    실체화 관계 (Realization)

    실체화 관계는 ‘인터페이스(Interface)’와 그 인터페이스를 구현(implement)하는 클래스 사이의 관계를 나타냅니다. 인터페이스는 기능의 명세, 즉 오퍼레이션의 목록만을 정의한 껍데기(약속)이며 실제 구현 코드는 없습니다. 실체화 관계는 특정 클래스가 그 인터페이스에 정의된 모든 오퍼레이션을 실제로 구현했음을 의미합니다. 다이어그램에서는 구현 클래스에서 인터페이스로 향하는, 속이 빈 점선 화살표로 표현합니다.

    예를 들어, Flyable이라는 인터페이스에 fly()라는 오퍼레이션이 정의되어 있다면, Airplane 클래스와 Bird 클래스는 이 Flyable 인터페이스를 실체화하여 각자에게 맞는 fly() 메서드를 구현할 수 있습니다. 이는 “Airplane은 날 수 있다(can-do)”를 의미하며, 유연하고 확장 가능한 설계를 만드는 데 핵심적인 역할을 합니다. 나중에 Drone이라는 새로운 클래스가 생겨도 Flyable 인터페이스만 구현하면 기존 시스템과 쉽게 통합될 수 있습니다.


    실전 예제로 배우는 클래스 다이어그램: 은행 시스템 모델링

    핵심 클래스 도출하기

    이제 간단한 은행 시스템을 클래스 다이어그램으로 모델링하는 과정을 살펴보겠습니다. 먼저 시스템의 핵심 개념들을 클래스로 도출해야 합니다. 은행 시스템에는 당연히 ‘고객(Customer)’과 ‘계좌(Account)’가 필요할 것입니다. 고객은 고객번호, 이름, 주소 등의 속성을 가질 것이고, 계좌는 계좌번호, 잔액, 비밀번호와 같은 속성을 가질 것입니다. 또한, 입금, 출금과 같은 거래가 발생하므로 ‘거래내역(Transaction)’ 클래스도 필요합니다. 이 클래스는 거래일시, 거래종류, 거래금액 등의 속성을 가질 수 있습니다. 이렇게 CustomerAccountTransaction 이라는 세 개의 핵심 클래스를 정의하는 것이 모델링의 첫걸음입니다.

    관계 설정 및 다중성 표현하기

    다음으로 이 클래스들 간의 관계를 설정합니다. 한 명의 고객은 여러 개의 계좌를 가질 수 있으므로, Customer와 Account 사이에는 1 대 다(1..*)의 관계가 형성됩니다. 이 관계는 고객이 계좌를 소유하는 개념이므로, Customer를 전체로, Account를 부분으로 하는 집합(Aggregation) 관계로 표현하는 것이 적절합니다. 고객 정보가 사라져도 계좌는 은행에 남아있을 수 있기 때문입니다.

    하나의 계좌에는 여러 개의 거래내역이 쌓입니다. 따라서 Account와 Transaction 사이에도 1 대 다(1..*)의 관계가 있습니다. 이 관계는 계좌가 없으면 거래내역도 의미가 없으므로, 생명주기를 함께하는 강력한 결합인 복합(Composition) 관계로 표현하는 것이 더 정확합니다. Account 클래스는 deposit()withdraw()와 같은 오퍼레이션을 가질 것이고, 이 오퍼레이션이 실행될 때마다 Transaction 인스턴스가 생성되어 해당 계좌에 기록될 것입니다.

    상속 관계 적용하기

    은행의 계좌에는 여러 종류가 있을 수 있습니다. 예를 들어, 일반적인 ‘입출금계좌(CheckingAccount)’와 대출 기능이 있는 ‘마이너스계좌(MinusAccount)’가 있다고 가정해 봅시다. 이 두 계좌는 계좌번호, 잔액 등 공통된 특징을 가지므로, 이들을 포괄하는 Account 클래스를 부모로 하는 일반화(상속) 관계를 적용할 수 있습니다.

    CheckingAccount와 MinusAccount는 Account 클래스를 상속받아 모든 속성과 기능을 물려받습니다. 그리고 MinusAccount 클래스에는 loanLimit(대출한도)라는 자신만의 속성과 executeLoan()(대출실행)이라는 오퍼레이션을 추가할 수 있습니다. 이처럼 상속을 활용하면 공통된 부분은 Account 클래스에서 한 번만 관리하고, 각 계좌의 특수한 부분만 자식 클래스에서 확장하여 효율적이고 체계적인 구조를 만들 수 있습니다.


    결론: 잘 그린 클래스 다이어그램의 가치와 주의점

    기술적 설계를 넘어선 소통의 도구

    클래스 다이어그램은 단순히 개발자가 코드를 작성하기 전에 그리는 기술적 산출물이 아닙니다. 이는 프로젝트에 참여하는 모든 사람이 시스템의 구조와 규칙에 대해 동일한 이해를 갖도록 돕는 강력한 소통의 도구입니다. 제품 책임자(PO)는 클래스 다이어그램을 통해 비즈니스 요구사항이 데이터 모델에 어떻게 반영되었는지 확인할 수 있고, UI/UX 디자이너는 어떤 데이터를 화면에 표시해야 하는지를 파악할 수 있으며, 테스터는 클래스 간의 관계를 기반으로 테스트 시나리오를 설계할 수 있습니다. 잘 만들어진 클래스 다이어그램 하나가 수십 페이지의 설명서를 대체할 수 있는 것입니다.

    좋은 클래스 다이어그램을 위한 조언

    클래스 다이어그램의 가치를 극대화하기 위해서는 몇 가지를 유의해야 합니다. 첫째, 모든 것을 담으려 하지 말아야 합니다. 시스템의 모든 클래스를 하나의 다이어그램에 표현하려는 시도는 오히려 복잡성만 가중시킬 뿐입니다. 다이어그램의 목적에 맞게 핵심적인 부분이나 특정 기능과 관련된 부분만 추려서 그리는 것이 효과적입니다. 둘째, 추상화 수준을 유지해야 합니다. 너무 상세한 구현 레벨의 정보보다는 클래스의 책임과 관계를 중심으로 표현하는 것이 좋습니다. 마지막으로, 다이어그램은 살아있는 문서여야 합니다. 설계가 변경되면 다이어그램도 함께 업데이트하여 항상 현재의 시스템 상태를 반영하도록 노력해야 합니다. 클래스 다이어그램을 토론의 시작점으로 삼고 팀과 함께 지속적으로 발전시켜 나갈 때, 비로소 성공적인 프로젝트의 견고한 초석이 될 것입니다.