PromleeBlog
sitemap
aboutMe

posting thumbnail
UML 클래스 다이어그램으로 소프트웨어 설계하기
Designing Software Structure with UML Class Diagrams

📅

🚀

들어가기 전에 🔗

지난 시간에는 소프트웨어가 어떤 과정을 거쳐 만들어지는지, 그 개발 방식에 대해 알아보았습니다.
이번에는 그 과정 중에서도 특히
설계
단계에서 매우 유용하게 사용되는 도구인
UML 클래스 다이어그램
에 대해 함께 공부해 보겠습니다.
마치 건축가가 건물의 설계도를 그리듯, 개발자도 소프트웨어의 구조를 그림으로 표현하는데요, 클래스 다이어그램은 그 핵심적인 역할을 합니다.

🚀

UML 클래스 다이어그램이란 무엇일까요? 🔗

UML(Unified Modeling Language, 통합 모델링 언어)은 소프트웨어 시스템을 만들 때 아이디어를 그림으로 표현하고, 다른 사람들과 소통하며, 문서로 남기기 위한 표준화된 약속입니다.
UML에는 다양한 다이어그램이 있는데, 그중에서도
클래스 다이어그램(Class Diagram)
은 시스템을 구성하는 가장 기본적인 단위인
클래스(Class)
들과, 이 클래스들 사이의 다양한
관계(Relationship)
를 정적으로 보여주는 데 중점을 둡니다.
UML 클래스 다이어그램
UML 클래스 다이어그램
쉽게 말해, 우리가 만들려는 소프트웨어에 어떤 종류의 부품들(클래스들)이 필요하고, 그 부품들이 서로 어떻게 연결되어 있으며, 각 부품은 어떤 특징(속성)과 기능(메서드)을 가지고 있는지를 한눈에 보여주는 상세한 지도와 같습니다.
특히 객체 지향 프로그래밍(Object-Oriented Programming, OOP)의 핵심 개념인 캡슐화, 상속, 다형성 등을 시각적으로 표현하고 이해하는 데 매우 효과적입니다.

🚀

클래스 다이어그램의 기본 구성 요소 🔗

클래스 다이어그램은 몇 가지 표준화된 기호와 표기법을 사용하여 시스템의 구조를 나타냅니다.
이 기본 요소들을 이해하는 것이 클래스 다이어그램을 읽고 그리는 첫걸음입니다.

클래스 (Class) 🔗

클래스는 객체 지향 프로그래밍의 핵심 구성 요소로, 공통된 속성(데이터)과 행동(메서드)을 가지는 객체들을 만들기 위한
또는
설계도
입니다.
클래스 다이어그램에서 클래스는 일반적으로 세 부분으로 나누어진 사각형으로 표현됩니다.
Class
Class
  1. 클래스 이름 (Class Name)
    사각형의 가장 윗부분에 굵은 글씨로 표시됩니다.
    클래스를 유일하게 식별하는 이름입니다. (예: BankAccount, UserService)
  2. 속성 (Attributes)
    클래스의 특징이나 상태 정보를 나타내는 변수들입니다. 중간 부분에 목록 형태로 나열됩니다.
    일반적으로 가시성 이름: 타입 또는 가시성 이름: 타입 = 기본값 형식으로 표현합니다.
    • 가시성(Visibility)
      : 해당 속성에 외부에서 얼마나 접근할 수 있는지를 나타냅니다.
      • + : public (모든 클래스에서 접근 가능)
      • - : private (해당 클래스 내부에서만 접근 가능)
      • # : protected (해당 클래스 및 동일 패키지, 또는 상속받은 하위 클래스에서 접근 가능)
      • ~ : package 또는 default (Java에서) (같은 패키지 내에서만 접근 가능)
    • 예시: - accountNumber: String, private double balance;
  3. 메서드 (Operations/Methods)
    클래스가 수행할 수 있는 동작이나 기능을 나타냅니다. 가장 아랫부분에 목록 형태로 나열됩니다.
    일반적으로 가시성 이름(파라미터 목록): 반환타입 형식으로 표현합니다.
    • 파라미터 목록: 이름: 타입 형식으로 쉼표로 구분하여 나열합니다.
    • 예시: + deposit(amount: double): void, public boolean withdraw(double amount)
➡️

예시 🔗

간단한 Book 클래스를 Java 코드로 작성하고, 이를 클래스 다이어그램 요소로 표현해 보겠습니다.
public class Book {
    private String title;
    private String author;
    public int pageCount;
 
    public Book(String title, String author, int pageCount) {
        this.title = title;
        this.author = author;
        this.pageCount = pageCount;
    }
 
    public String getTitle() {
        return this.title;
    }
 
    private void displayBookDetails() {
        System.out.println("Title: " + title + ", Author: " + author);
    }
}
Book 클래스를 다이어그램으로 표현하면 다음과 같습니다.
`Book` 클래스 다이어그램
`Book` 클래스 다이어그램

관계 (Relationships) 🔗

소프트웨어 시스템은 여러 클래스들이 서로 관계를 맺고 협력하여 기능을 수행합니다.
클래스 다이어그램은 이러한 클래스 간의 다양한 관계를 선과 특정 기호를 사용하여 시각적으로 표현합니다.
주요 관계에는 일반화(상속), 연관, 집합, 합성, 의존 관계 등이 있습니다.
➡️

1. 일반화 관계 (Generalization) 🔗

일반화 관계는 객체 지향의 핵심 개념인
상속(Inheritance)
을 나타냅니다.
부모 클래스(슈퍼클래스, Superclass)가 가진 속성과 메서드를 자식 클래스(서브클래스, Subclass)가 물려받아 사용하거나 확장하는 관계입니다.
"자식 클래스는 부모 클래스의 한 종류이다 (is-a kind of)"로 설명될 수 있습니다. (예: '스마트폰'은 '전자기기'의 한 종류이다)
일반화 관계
일반화 관계
Java 예시
// 부모 클래스 (슈퍼클래스)
abstract class Shape { // 추상 클래스로 정의하여 직접 객체 생성 방지 가능
    protected String color;
 
    public Shape(String color) {
        this.color = color;
    }
 
    public abstract double getArea(); // 자식 클래스에서 반드시 구현해야 하는 추상 메서드
 
    public void displayColor() {
        System.out.println("Color: " + this.color);
    }
}
 
// 자식 클래스 (서브클래스)
class Circle extends Shape {
    private double radius;
 
    public Circle(String color, double radius) {
        super(color); // 부모 클래스의 생성자 호출
        this.radius = radius;
    }
 
    @Override // 부모의 추상 메서드 구현
    public double getArea() {
        return Math.PI * radius * radius;
    }
}
 
class Rectangle extends Shape {
    private double width;
    private double height;
 
    public Rectangle(String color, double width, double height) {
        super(color);
        this.width = width;
        this.height = height;
    }
 
    @Override
    public double getArea() {
        return width * height;
    }
}
위 예에서 CircleRectangleShape을 상속받습니다.
다이어그램에서는 Circle에서 Shape으로, Rectangle에서 Shape으로 각각 일반화 관계 화살표를 그립니다.
Shape 클래스 이름은
이탤릭체
로 표시하거나 {abstract} 제약조건을 붙여 추상 클래스임을 나타낼 수 있습니다.
Shape 일반화 관계
Shape 일반화 관계
➡️

2. 연관 관계 (Association) 🔗

연관 관계는 두 개 이상의 클래스 객체들이 서로 의미 있는 연결을 가지고 있음을 나타냅니다.
한 클래스의 인스턴스가 다른 클래스의 인스턴스를 참조하거나, 메시지를 주고받으며 상호작용하는 경우입니다.
(예: 학생(Student)교수(Professor)는 서로 연관될 수 있습니다. 한 교수는 여러 학생을 지도할 수 있습니다.)
단방향 연관
단방향 연관
양방향 연관
양방향 연관
Java 예시
고객이 자신의 주문 목록을 아는 단방향 연관
import java.util.List;
import java.util.ArrayList;
 
class Order {
    private String orderId;
    // ... 다른 주문 관련 속성들
    public Order(String orderId) { this.orderId = orderId; }
    public String getOrderId() { return orderId; }
}
 
class Customer {
    private String customerId;
    private String name;
    private List<Order> orders; // 고객은 여러 주문(Order)을 가질 수 있음 (참조)
 
    public Customer(String customerId, String name) {
        this.customerId = customerId;
        this.name = name;
        this.orders = new ArrayList<>();
    }
 
    public void addOrder(Order order) {
        this.orders.add(order);
    }
 
    public List<Order> getOrders() {
        return this.orders;
    }
}
이 경우 Customer 클래스는 Order 객체들의 리스트를 멤버 변수(orders)로 가지고 있습니다.
다이어그램에서는 Customer에서 Order로 향하는 화살표를 그리고, Customer 쪽 끝에는 1, Order 쪽 끝에는 0..* (또는 *)를 표시하여 다중성을 나타냅니다.
고객이 자신의 주문 목록을 아는 단방향 연관
고객이 자신의 주문 목록을 아는 단방향 연관
➡️

3. 집합 관계 (Aggregation) 🔗

집합 관계는 '전체(Whole)' 클래스와 '부분(Part)' 클래스 간의 관계를 나타내며, "has-a" 관계의 한 종류입니다.
여기서
부분 객체는 전체 객체와 독립적으로 존재할 수 있습니다
.
즉, 전체 객체가 사라져도 부분 객체는 계속 존재할 수 있는 느슨한 포함 관계입니다.
(예: '팀(Team)'과 '선수(Player)' 관계에서 팀이 해체되어도 선수는 다른 팀으로 가거나 독립적으로 존재할 수 있습니다.)
집합 관계
집합 관계
Java 예시:
// 부분(Part) 클래스
class Keyboard {
    private String modelName;
    public Keyboard(String modelName) { this.modelName = modelName; }
    public String getModelName() { return modelName; }
}
 
// 전체(Whole) 클래스
class Computer {
    private String brand;
    private Keyboard keyboard; // 컴퓨터는 키보드를 "가진다" (has-a)
 
    public Computer(String brand, Keyboard keyboard) { // 외부에서 생성된 Keyboard 객체를 받음
        this.brand = brand;
        this.keyboard = keyboard;
    }
 
    public void setKeyboard(Keyboard keyboard) {
        this.keyboard = keyboard;
    }
 
    public Keyboard getKeyboard() {
        return this.keyboard;
    }
}
 
// 사용 예시
Keyboard myKeyboard = new Keyboard("Logitech MX Keys");
Computer myComputer = new Computer("Apple MacBook Pro", myKeyboard);
// myComputer 객체가 사라지더라도, myKeyboard 객체는 독립적으로 존재 가능
집합 관계
집합 관계
➡️

4. 합성 관계 (Composition) 🔗

합성 관계도 '전체(Whole)'와 '부분(Part)' 간의 "has-a" 관계를 나타내지만, 집합 관계보다 훨씬 강한 소유 관계입니다.
부분 객체는 전적으로 전체 객체에 속하며, 전체 객체가 생성될 때 부분 객체가 함께 생성되거나 관리되고, 전체 객체가 소멸되면 부분 객체도 함께 소멸되는
생명주기 의존성을 가집니다.
(예: '자동차(Car)'와 '엔진(Engine)' 관계에서 자동차가 폐차되면 엔진도 함께 폐기됩니다. 엔진은 자동차 없이는 독립적인 의미를 갖기 어렵습니다.)
합성 관계
합성 관계
Java 예시:
// 부분(Part) 클래스 - 주로 전체 클래스 내부에서만 사용됨
class Heart {
    private int bpm;
    public Heart() { this.bpm = 70; } // 기본 심박수
    public void pump() { System.out.println("Heart is pumping at " + bpm + " bpm."); }
}
 
// 전체(Whole) 클래스
class Person {
    private String name;
    private Heart heart; // 사람은 심장을 "소유한다" (강한 결합)
 
    public Person(String name) {
        this.name = name;
        this.heart = new Heart(); // Person 객체 생성 시 Heart 객체도 함께 생성
    }
 
    public void live() {
        System.out.print(name + " is living. ");
        heart.pump();
    }
    // Person 객체가 소멸되면 (GC 대상이 되면), heart 객체도 함께 소멸 대상이 됨
}
 
// 사용 예시
Person alice = new Person("Alice");
alice.live();
// alice 객체가 사라지면, 그 안에 있던 heart 객체도 더 이상 접근 불가능하고 GC 대상이 됨
합성 관계
합성 관계
➡️

5. 의존 관계 (Dependency) 🔗

의존 관계는 한 클래스가 다른 클래스를
일시적으로 사용
하는, 가장 약한 형태의 관계입니다.
주로 어떤 클래스의 메서드가 실행될 때 다른 클래스를 매개변수로 받거나, 메서드 내에서 다른 클래스의 객체를 지역 변수로 생성하여 잠깐 사용하는 경우에 나타납니다.
영구적인 연결이 아니라 특정 작업을 수행하는 동안에만 필요한 관계입니다. (예: OrderProcessor가 주문을 처리할 때 EmailService를 사용하여 고객에게 알림 메일을 보내는 경우)
의존 관계
의존 관계
Java 예시
// 사용되는 클래스
class Logger {
    public void logMessage(String message) {
        System.out.println("LOG: " + message);
    }
}
 
// 사용하는 클래스
class DataProcessor {
    public void processData(String data, Logger logger) { // Logger 객체를 매개변수로 받아 사용
        // 데이터 처리 로직...
        System.out.println("Processing data: " + data);
        logger.logMessage("Data processing complete for: " + data); // Logger의 기능 사용 (의존)
    }
}
 
// 사용 예시
DataProcessor processor = new DataProcessor();
Logger consoleLogger = new Logger();
processor.processData("SampleData123", consoleLogger);
// DataProcessor는 processData 메서드 실행 중에만 Logger에 의존합니다.
DataProcessorprocessData 메서드는 Logger 타입의 객체를 매개변수로 받아 사용하므로, DataProcessorLogger에 의존합니다.
의존 관계 예시
의존 관계 예시

🚀

설계 의도를 시각화하는 방법 🔗

UML 클래스 다이어그램은 단순히 그림을 그리는 행위를 넘어, 소프트웨어 설계자의
의도
를 명확하게 전달하고, 시스템의
구조
를 효과적으로 파악하며, 팀원들과의
소통
을 원활하게 하는 데 그 목적이 있습니다.
  1. 핵심 개념 식별 (Identify Key Abstractions)
    만들려는 시스템의 주요 개념이나 사물을 나타내는 클래스들을 먼저 식별합니다.
    예: 온라인 쇼핑몰이라면 Product, Customer, ShoppingCart, Payment
  2. 책임과 협력 정의 (Define Responsibilities and Collaborations)
    각 클래스가 어떤 데이터를 가지고(속성), 어떤 행동을 해야 하는지(메서드) 정의합니다.
    또한, 클래스들이 서로 어떻게 협력하여 시스템 기능을 완성하는지 관계를 설정합니다.
  3. 가시성 및 다중성 명시 (Specify Visibility and Multiplicity)
    속성과 메서드의 접근 범위를 나타내는 가시성을 명확히 하고, 클래스 간 관계에서 객체 수를 나타내는 다중성을 정확히 표시하여 관계의 명확성을 높입니다.
  4. 적절한 추상화 수준 유지 (Maintain Appropriate Level of Abstraction)
    하나의 다이어그램에 너무 많은 세부 정보를 담기보다는, 시스템의 특정 부분이나 관점에 초점을 맞춰 여러 다이어그램으로 나누어 그리는 것이 더 효과적일 수 있습니다.
    핵심 설계 의도를 전달하는 데 집중하세요.
  5. 일관성 있는 표기법 사용 (Use Consistent Notation)
    UML 표준 표기법을 일관되게 사용하여 오해의 소지를 줄입니다.
  6. 지속적인 검토와 개선 (Review and Refine Continuously)
    다이어그램은 살아있는 문서입니다. 팀원들과 함께 검토하고 피드백을 통해 지속적으로 개선해나가세요.
    코드가 변경되면 다이어그램도 업데이트하는 것이 이상적입니다.

🚀

결론 🔗

오늘은 UML 클래스 다이어그램을 사용하여 객체 지향 소프트웨어의 정적인 구조를 설계하는 방법에 대해 Java 예제와 함께 자세히 알아보았습니다.
클래스의 기본 구성 요소인 이름, 속성, 메서드를 정의하고, 클래스들 간의 다양한 관계(일반화, 연관, 집합, 합성, 의존)를 표현하는 방법을 익혔습니다.
다음 시간에는 시스템 내 객체들이 특정 작업을 수행하기 위해 시간의 흐름에 따라 어떻게 메시지를 주고받으며 상호작용하는지 보여주는
시퀀스 다이어그램으로 객체 간 동작 흐름 그리기
에 대해 알아보겠습니다.

참고 🔗