UML의 개념
UML(Unified Modeling Language)은 표준화된 모델링 언어로서, 주로 소프트웨어 시스템의 구조와 행동을 시각화, 문서화하는 데 이용되는 도구이다. 클래스 다이어그램은 일반적으로 Use Case 모델링과 Domain 모델링 이후에 모델링한다.
특히 Use Case 모델링 단계가 정말 중요하다고 생각하는데, 구현 기능이 고객의 요구사항과 동떨어져 산으로 가는 것을 방지하는 이정표 역할을 해줄 수 있고, Test Case의 기반 시나리오도 되어줄 수 있기 때문이다.
UML 모델링 없이 프로젝트를 진행했었는데, 특히 Use Case 모델링을 하지 않고 그때그때 생각나는 대로 만들다 보니 기본적으로 제공되어야 할 기능이 너무 늦게 만들어지는 일도 있었다. 따라서 앞으로 진행할 프로젝트는 항상 개발 전에 UML 모델링을 선행할 생각이다.
클래스 다이어그램의 개념
클래스 다이어그램(Class Diagram)은 UML의 주요 구성 요소 중 하나로서, 소프트웨어를 구성하는 클래스의 구성과 관계도를 통해 소프트웨어 내부의 정적 구조를 시각화하여 표현한다. 클래스 다이어그램을 통해 소프트웨어의 구조를 문서화할 수 있고, 이는 시스템의 구성을 파악하고 이해하는 데 도움을 줄 수 있다. 하지만 정적 구조는 동적인 요소를 표현하는 데 한계를 지니고 있으므로, 시퀀스 다이어그램(Sequence Diagram) 등을 통해 각 객체들의 책임과 그에 따른 상호작용에 대한 정보를 보완하는 과정이 필수적이다.
클래스 다이어그램 예시
다음은 예시로 작성한 Car 객체의 설계도이다.
public class Car {
private Long id;
private String name;
private Company company;
private String color;
private Integer price;
public Car(Long id, String name, Company company, String color, Integer price) {
this.id = id;
this.name = name;
this.company = company;
this.color = color;
this.price = price;
}
public Long getId() {
return id;
}
public String getName() {
return name;
}
public Company getCompany() {
return company;
}
public String getColor() {
return color;
}
public Integer getPrice() {
return price;
}
}
이를 아래와 같이 클래스 다이어그램으로 표현할 수 있다.
클래스를 구성하는 field와 method의 목록을 한눈에 파악할 수 있다.
하지만 시각화 자료는 대상이 복잡할 때 보다 위력을 발휘할 것이라고 생각된다. 그래서 현재 내가 진행하고 있는 배달 애플리케이션 프로젝트의 entity들 중에서 제일 처음 설계했던, 그래서 엄청난 애착을 가지고 있지만 그만큼 field가 과다해진 Menu 클래스를 클래스 다이어그램으로 시각화해 봤다.
package personal.delivery.menu;
import jakarta.persistence.*;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import personal.delivery.constant.MenuType;
import personal.delivery.constant.StockStatus;
import personal.delivery.exception.OutOfStockException;
import personal.delivery.shop.entity.Shop;
import java.time.LocalDateTime;
import java.util.List;
@Entity
@Getter
@NoArgsConstructor
@Table(name = "menu")
public class Menu {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "menu_id")
private Long id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "shop_id")
private Shop shop;
@Column(nullable = false, length = 20)
private String name;
@Column(nullable = false)
private Integer price;
@Column(nullable = false)
private Integer salesRate;
@Column(nullable = false)
private Integer stock;
@Enumerated(EnumType.STRING)
@Column(nullable = false)
private StockStatus stockStatus;
@Column(length = 10)
private String flavor;
private Integer portions;
private Integer cookingTime;
@Enumerated(EnumType.STRING)
private MenuType menuType;
@Column(length = 10)
private String foodType;
private Boolean isPopularMenu;
private List<String> menuOptions;
@Column(nullable = false)
private LocalDateTime registrationTime;
private LocalDateTime updateTime;
@Builder
public Menu(Long id, Shop shop, String name, Integer price, Integer salesRate, Integer stock,
String flavor, Integer portions, Integer cookingTime, MenuType menuType,
String foodType, Boolean isPopularMenu, List<String> menuOptions,
LocalDateTime registrationTime, LocalDateTime updateTime) {
this.id = id;
this.shop = shop;
this.name = name;
this.price = price;
this.salesRate = salesRate;
this.stock = stock;
adjustStockState();
this.flavor = flavor;
this.portions = portions;
this.cookingTime = cookingTime;
this.menuType = menuType;
this.foodType = foodType;
this.isPopularMenu = isPopularMenu;
this.menuOptions = menuOptions;
this.registrationTime = registrationTime;
this.updateTime = updateTime;
}
public void useStockForSale(int orderQuantity) {
if (orderQuantity > stock) {
throw new OutOfStockException("판매 불가: 재료 부족 (현재 재고: " + stock + ")");
}
stock -= orderQuantity;
adjustStockState();
salesRate += orderQuantity;
if (salesRate >= 100) {
isPopularMenu = true;
name += "(인기메뉴)";
} else {
isPopularMenu = false;
name.replace("(인기메뉴)", "");
}
}
private void adjustStockState() {
stockStatus = stock > 10 ? StockStatus.AVAILABLE
: stock > 0 ? StockStatus.LOW_STOCK
: StockStatus.OUT_OF_STOCK;
}
}
많은 구성요소들과 어노테이션, 극한의 더티 코드로 인해 클래스의 구성을 파악하기가 어렵다. 따라서 클래스 다이어그램을 통해 시각화해 줄 필요성이 있어 보인다.
이렇게 다이어그램으로 작성하고 나니 확실히 Menu 클래스가 가진 field와 method가 잘 파악된다. 그러나 많은 field 개수만큼이나 그것들의 get 메소드가 너무 의미 없이 나열되어 있는 느낌이 있다. getter는 클래스의 구조를 파악하는 데 크게 중요한 구성요소가 아니기 때문에 하나로 간소화해서 작성을 해 봤다.
getter들을 간소화하니 중요한 요소들에 시각적으로 더 집중할 수 있는 느낌이다. 하지만 getter를 생략하는 것은 클래스 다이어그램 모델링 원칙에는 어긋난다고 한다. 유연성을 발휘할 수 있는 상황에서만 적용하는 편이 좋을 듯하다.
참고 자료
https://songacoding.tistory.com/90
'Development > Software Design' 카테고리의 다른 글
클래스 다이어그램 예시(배달 애플리케이션) (0) | 2023.08.08 |
---|---|
싱글톤 패턴(Singleton Pattern)의 개념과 특징 (2) | 2023.08.03 |
클래스 설계 시 상속 관계, 합성 관계, 위임 관계의 개념(in Java) (2) | 2023.07.23 |