본문 바로가기

개발공부/Java

패스트캠퍼스 환급챌린지 23일차 : 한 번에 끝내는 컴퓨터 공학 & 인공지능 복수전공 초격차 패키지 강의 후기

반응형

본 포스팅은 패스트캠퍼스 환급 챌린지 참여를 위해 작성하였습니다.

 

Ch 8. 쓰임새(Use Case)와 구조 설계 

 

 

자바 객체 지향 설계를 통한 효율적인 시스템 구축: Use Case와 객체 협업 

이번에는 Use Case(사용 사례)를 중심으로 시스템의 기능을 정의하고, 객체들이 어떻게 서로 협업(Collaboration)하며 역할을 분담하는지, 그리고 이 과정에서 위임(Delegation), 결합도(Coupling), 응집도(Cohesion)와 같은 중요한 설계 원칙들이 어떻게 적용되는지 자세히 살펴보겠습니다.

1. Use Case와 시나리오: 사용자 관점에서 기능 정의하기 

소프트웨어 개발에서 Use Case는 소프트웨어 시스템의 사용 시나리오를 의미합니다. 이는 사용자가 시스템을 통해 달성하고자 하는 목표를 설명하며, 시스템이 사용자에게 제공하는 기능적 가치를 명확히 합니다. Use Case는 종종 복수형으로 사용되어 소프트웨어가 유용할 수 있는 다양한 상황을 나타냅니다. Use Case 개념은 1987년 Ivar Jacobson이 처음 제시했습니다.

사용자 스토리(User Story)는 Use Case와 유사하게 기능 정의에 사용되는 비공식적이고 자연어적인 설명입니다. "나는 <역할>로서 <기능>을 할 수 있고, 이를 통해 <이점>을 얻는다"와 같은 일반적인 템플릿을 사용합니다. 이러한 접근 방식은 개발 팀이 사용자의 관점에서 소프트웨어의 가치를 이해하는 데 도움을 줍니다.

Use Case의 중요성:

  • 명확한 기능 요구사항: 시스템이 무엇을 해야 하는지 사용자 관점에서 명확하게 정의합니다.
  • 개발 방향성 제시: 개발 팀이 어떤 기능을 구현해야 하는지 구체적인 목표를 설정할 수 있습니다.
  • 테스트 계획의 기초: 각 Use Case는 시스템의 테스트 시나리오를 만드는 데 활용될 수 있습니다.

1.1. 쇼핑몰 시나리오 정의

예를 들어, 간단한 쇼핑몰 시스템의 Use Case를 정의하고 시나리오를 구체화해 봅시다.

메인 시나리오: "고객이 상품 카탈로그에서 상품을 선택하고 주문하는 과정" 이 시나리오를 구현하기 위해 다음과 같은 단계들을 고려할 수 있습니다:

  1. 상품 카탈로그 준비: 판매할 상품과 가격, 수량 등을 준비합니다.
  2. 고객 방문 시뮬레이션: 고객이 순차적으로 쇼핑몰을 방문하여 상품을 살펴봅니다.
  3. 판매 개시: 쇼핑몰에서 상품 판매를 시작합니다.
  4. 주문 (고정 가격): 고객이 고정 가격 상품을 주문합니다.
  5. 할인 시작: 특정 상품에 대해 할인을 시작합니다.
  6. 주문 (할인 가격): 고객이 할인된 가격의 상품을 주문합니다.

2. 객체 협업으로 Use Case 구현하기: 역할 배분과 위임 

객체 지향 프로그래밍의 핵심은 단순히 클래스와 객체를 만드는 것을 넘어, 이 객체들이 서로 어떻게 메시지를 주고받으며 협력하여 하나의 Use Case를 완성하는지에 있습니다. 이 과정에서 위임(Delegation)이라는 개념이 중요하게 작용합니다.

위임(Delegation)은 객체 지향 프로그래밍에서 한 객체(수신자)의 멤버(속성 또는 메소드)를 다른 원래 객체(보내는 객체)의 컨텍스트에서 평가하는 것을 의미합니다. 즉, 특정 책임을 보내는 객체에서 수신하는 객체로 넘기는 것입니다. 이는 어떤 객체가 특정 작업을 직접 수행하는 대신, 다른 객체에게 그 작업을 맡기는 방식으로 이해할 수 있습니다. 위임은 어떤 객체 지향 언어에서든 명시적으로 수행될 수 있습니다.

기능재사용 구조체(mechanism)에 의해 구현(realizes)됩니다. 이는 우리가 정의한 Use Case의 '기능'을 클래스, 메소드, 그리고 객체 간의 위임을 통해 '구현'한다는 것을 의미합니다.

2.1. 쇼핑몰 시스템의 객체 협업 설계

이전 포스팅에서 다룬 Customer, Product (추상 클래스), FixedPricedProduct, DailyPricedProduct 클래스를 활용하여 ShoppingMall과 Order 클래스를 추가하여 쇼핑몰 시나리오를 구현해봅시다.

클래스 설계 변경 (Product 객체를 Order에 포함):

이전에는 Order 클래스가 productname (String)과 price (int)를 직접 멤버 변수로 가졌지만, 이를 Product 객체를 포함하도록 변경하여 데이터의 응집도를 높입니다.

Product 클래스 계열 (Product 추상 클래스와 하위 클래스):

// Product.java (추상 클래스)
public abstract class Product {
    private String name;
    protected int price;

    public Product(String name, int price) {
        this.name = name;
        this.price = price;
    }

    public String getName() {
        return this.name;
    }

    public abstract int getPrice(); // 추상 메소드: 하위 클래스에서 구현 필수
}

// FixedPricedProduct.java
class FixedPricedProduct extends Product {
    public FixedPricedProduct(String name, int price) {
        super(name, price);
    }

    @Override
    public int getPrice() {
        return this.price;
    }
}

// DailyPricedProduct.java
class DailyPricedProduct extends Product {
    private boolean nearTheDeadline;

    public DailyPricedProduct(String name, int price, boolean nearTheDeadline) {
        super(name, price);
        this.nearTheDeadline = nearTheDeadline;
    }

    public void setNearTheDeadline(boolean nearTheDeadline) {
        this.nearTheDeadline = nearTheDeadline;
    }

    @Override
    public int getPrice() {
        if (nearTheDeadline) {
            return (int) (this.price * 0.5); // 마감 임박 할인
        }
        return this.price;
    }
}

Customer 클래스:

public class Customer {
    private String name;
    private String number;
    private String email;

    public Customer(String name, String number, String email) {
        this.name = name;
        this.number = number;
        this.email = email;
    }

    public String getName() { return name; }
    public String getNumber() { return number; }
    public String getEmail() { return email; }

    @Override
    public String toString() {
        return "고객 정보: [이름: " + (name != null ? name : "익명") +
               ", 전화번호: " + (number != null ? number : "모름") +
               ", 이메일: " + (email != null ? email : "모름") + "]";
    }
}

Order 클래스: Customer 객체와 Product 객체를 멤버 변수로 포함합니다.

public class Order {
    private Customer customer;
    private Product product; // Product 객체를 멤버로 가짐

    public Order(Customer customer, Product product) {
        this.customer = customer;
        this.product = product;
    }

    public Customer getCustomer() {
        return customer;
    }

    public Product getProduct() {
        return product;
    }

    @Override
    public String toString() {
        // Order 객체가 Product 객체에 getName()과 getPrice() 호출을 위임하여 정보 얻음
        return "주문 정보: [" + customer.toString() +
               ", 상품: " + product.getName() +
               ", 최종 가격: " + product.getPrice() + "원]";
    }
}

ShoppingMall 클래스 (객체 협업의 중심):

ShoppingMall 클래스는 상품 카탈로그를 관리하고, 고객의 주문을 처리하는 역할을 합니다. 여기서는 HashMap을 사용하여 상품을 key-value 형태로 관리할 수 있습니다.

import java.util.HashMap;
import java.util.Map;

public class ShoppingMall {
    private Map<String, Product> productCatalog; // 상품 카탈로그

    public ShoppingMall() {
        productCatalog = new HashMap<>();
        prepareCatalog(); // 상품 카탈로그 초기화
    }

    // 상품 카탈로그 준비 메소드 (Use Case 1)
    private void prepareCatalog() {
        productCatalog.put("book1", new FixedPricedProduct("켄트 벡의 Tidy First?", 19800)); //
        productCatalog.put("food1", new DailyPricedProduct("맛있는 떡볶이", 6000, false)); //
        productCatalog.put("food2", new DailyPricedProduct("영주 사과", 2000, false)); //
        productCatalog.put("food3", new DailyPricedProduct("제주 당근 1kg", 10000, false)); //
        System.out.println("상품 카탈로그 준비 완료.");
    }

    // 상품 선택 및 주문 처리 메소드 (Use Case 4, 6)
    public Order placeOrder(Customer customer, String productId, boolean applyDiscount) {
        Product selectedProduct = productCatalog.get(productId);

        if (selectedProduct == null) {
            System.out.println("죄송합니다, 해당 상품은 존재하지 않습니다: " + productId);
            return null;
        }

        // 할인 적용 (DailyPricedProduct인 경우)
        if (applyDiscount && selectedProduct instanceof DailyPricedProduct) {
            ((DailyPricedProduct) selectedProduct).setNearTheDeadline(true); // 마감 임박으로 설정하여 할인 적용
            System.out.println(selectedProduct.getName() + " 상품에 할인이 적용되었습니다.");
        }

        Order newOrder = new Order(customer, selectedProduct);
        System.out.println("새로운 주문이 생성되었습니다: " + newOrder.toString());
        return newOrder;
    }

    public Product getProduct(String productId) {
        return productCatalog.get(productId);
    }

    // 특정 상품에 할인 시작 (Use Case 5)
    public void startDiscount(String productId) {
        Product product = productCatalog.get(productId);
        if (product instanceof DailyPricedProduct) {
            ((DailyPricedProduct) product).setNearTheDeadline(true);
            System.out.println(product.getName() + " 할인 판매가 시작되었습니다!");
        } else {
            System.out.println(product.getName() + "는(은) 할인 대상 상품이 아닙니다.");
        }
    }
}

메인 애플리케이션 (시나리오 실행):

public class ShoppingScenario {
    public static void main(String[] args) {
        // 1. 상품 카탈로그 준비는 ShoppingMall 생성자에서 수행
        ShoppingMall myMall = new ShoppingMall(); //

        // 2. 고객 방문 시뮬레이션
        Customer customerA = new Customer("강민준", "010-1111-2222", "mjkang@example.com");
        Customer customerB = new Customer("박서연", "010-3333-4444", "sypark@example.com");

        System.out.println("\n--- 판매 개시 ---"); // 3. 판매 개시

        // 4. 주문 (고정 가격 상품)
        System.out.println("\n" + customerA.getName() + " 고객의 주문:");
        myMall.placeOrder(customerA, "book1", false); // 책 주문

        // 5. 할인 시작
        System.out.println("\n--- 할인 이벤트 시작 ---"); //
        myMall.startDiscount("food1"); // 떡볶이 할인 시작

        // 6. 주문 (할인 가격 상품)
        System.out.println("\n" + customerB.getName() + " 고객의 주문:");
        myMall.placeOrder(customerB, "food1", true); // 할인된 떡볶이 주문 (할인 적용)
        myMall.placeOrder(customerB, "food2", false); // 사과 주문 (할인 미적용)

        System.out.println("\n--- 시나리오 종료 ---");
    }
}

위 시퀀스 다이어그램은 Order 객체가 Product 객체에게 getName()getPrice() 호출을 위임하여 상품 정보를 얻는 과정을 보여줍니다. Order 객체는 상품 정보를 직접 가지고 있지 않고, Product 객체에게 요청하여 그 정보를 활용합니다.

2.2. Composition over Inheritance (상속보다 구성)

객체 지향 설계에서 "상속보다 구성(Composition over Inheritance)" 원칙은 클래스가 기본 클래스나 부모 클래스로부터 상속하는 것보다 다른 클래스의 인스턴스(객체)를 포함하여(구성하여) 다형적 동작과 코드 재사용을 선호해야 한다는 원칙입니다.

이 원칙은 Order 클래스가 Product 클래스를 멤버(Member)로 가지는 방식으로 구현되었습니다. 이는 Order가 Product의 기능을 상속받는 것이 아니라, Product 객체를 '포함'하여 그 기능을 '활용'하는 형태입니다. 이렇게 하면 시스템의 유연성이 높아지고, 불필요한 상속 계층을 피할 수 있습니다. 인터페이스는 이러한 다형적 동작을 촉진할 수 있습니다. 식별된 인터페이스를 구현하는 클래스는 필요에 따라 비즈니스 도메인 클래스에 구축되고 추가됩니다. 따라서 시스템 동작은 상속 없이도 구현됩니다.

3. 결합도(Coupling)와 응집도(Cohesion): 좋은 설계의 지표 

객체 지향 설계를 할 때, 결합도(Coupling)응집도(Cohesion)는 소프트웨어 모듈의 품질을 평가하는 중요한 척도입니다.

3.1. 결합도 (Coupling)

결합도는 소프트웨어 모듈 간의 상호 의존성 정도를 나타냅니다. 즉, 두 루틴(메소드) 또는 모듈이 얼마나 밀접하게 연결되어 있는지, 그리고 모듈 간의 관계 강도를 측정하는 척도입니다.

  • 낮은 결합도(Loose Coupling): 모듈 간의 의존성이 낮을수록 좋습니다. 한 모듈의 변경이 다른 모듈에 미치는 영향이 적어 유지보수가 쉽고 재사용성이 높아집니다.
  • 높은 결합도(Tight Coupling): 모듈 간의 의존성이 높을수록 좋지 않습니다. 한 모듈의 변경이 다른 모듈에 광범위한 영향을 미쳐 오류 발생 가능성이 높고, 재사용이 어렵습니다.

3.2. 응집도 (Cohesion)

응집도는 모듈 내부의 요소들이 얼마나 서로 관련되어 있는지를 나타냅니다. 하나의 클래스 내에 있는 메소드와 데이터가 얼마나 하나의 통일된 목적이나 개념을 위해 함께 속해 있는지를 측정하는 척도입니다.

  • 높은 응집도(High Cohesion): 모듈 내부의 요소들이 밀접하게 관련되어 하나의 명확한 목적을 수행할 때 좋습니다. 이는 클래스의 목적이 명확해지고, 코드를 이해하고 관리하기 쉬워집니다.
  • 낮은 응집도(Low Cohesion): 모듈 내부의 요소들이 서로 관련성이 적거나 여러 가지 이질적인 목적을 수행할 때 좋지 않습니다. 이는 클래스의 역할이 모호해지고, 유지보수가 어려워집니다.

좋은 객체 지향 설계는 낮은 결합도와 높은 응집도를 지향합니다. Order 클래스가 Product 객체를 멤버 변수로 포함함으로써, Order 클래스는 상품의 세부 정보(이름, 가격 계산 로직)에 직접 의존하지 않고 Product 객체에 그 책임을 위임합니다. 이는 Order와 Product 간의 결합도를 낮추고, 각 클래스의 응집도를 높이는 결과로 이어집니다.

 

오늘의 실습

오늘의 느낀점

자바 처음 배울 때 설계 배운다고 UseCase  배운 적이 있다. 시퀀스 다이어그램이니 뭐니 머리로 들어오지도 않는데, UML 이용해서 그려보고 했던 기억이 새록새록난다. 그게 벌써 13년전이네 ㅎㅎㅎ; 그 후로 실무에서도 간간히 쓰이긴 했지만, 사실 배운대로 돌리기엔 시간도 없고 내가 주로 근무했던 곳들은 마감일자에 쫓겨서 일하던 곳들이 대부분이어서 설계를 조금 등한시 했다. 그러다 보니 중간에 설계적 미스가 나면 땜빵식으로 때우기 그만이었는데, 체계잡힌 설계로 구현을 하니까 아무래도 좀 더 쉬워진 것 같다! 

업무적으로 진행하지 않더라도 이제 작업하기전에 설계부터 꼼꼼히 하고 진행해야겠다! 

 

 

 

 

 

 

https://fastcampus.info/4n8ztzq

 

(~6/20) 50일의 기적 AI 환급반💫 | 패스트캠퍼스

초간단 미션! 하루 20분 공부하고 수강료 전액 환급에 AI 스킬 장착까지!

fastcampus.co.kr

 

반응형