PromleeBlog
sitemap
aboutMe

posting thumbnail
객체지향 분석과 설계
Starting Object-Oriented Analysis and Design

📅

🚀

들어가기 전에 🔗

지금까지 우리는 소프트웨어 개발의 다양한 단계와 방법론, 그리고 설계에 앞선 준비 과정들을 살펴보았습니다.
이제 드디어 소프트웨어 설계의 중요한 패러다임 중 하나인
객체 지향(Object-Oriented)
의 세계로 한 걸음 더 깊이 들어가 보려 합니다.

객체 지향 분석(OOA)과 설계(OOD)는 우리가 주변에서 보는 실제 세상의 사물이나 개념들을 소프트웨어 안의 '객체'라는 단위로 만들고, 이 객체들이 서로 협력하여 문제를 해결하도록 시스템을 구축하는 방식입니다.
오늘은 이 객체 지향 분석과 설계의 기본 개념부터 시작하여, 어떻게 현실 세계의 문제를 객체로 바라보고, 이를 코드로 옮길 수 있는지 함께 알아보겠습니다.

🚀

객체 지향 분석과 설계란 무엇일까요? 🔗

객체 지향 프로그래밍(OOP)을 효과적으로 하기 위해서는 먼저 문제 영역을 객체 지향적인 관점에서 이해하고(분석), 이를 바탕으로 소프트웨어의 구조를 설계하는 과정이 필요합니다.
현실을 모델링하다
현실을 모델링하다
OOA와 OOD는 명확히 구분되기보다는 서로 영향을 주며 반복적으로 수행되는 경우가 많습니다.
이 과정을 통해 우리는 현실 세계의 복잡성을 잘 반영하면서도 유연하고 유지보수하기 좋은 소프트웨어를 만들 수 있습니다.

🚀

1단계: 객체 식별과 클래스 분류 방법 🔗

객체 지향 분석의 첫걸음은 문제 영역에서 의미 있는 객체를 찾아내는 것입니다.
마치 우리가 주변 세상을 인식할 때 개별적인 사물들을 구분하듯이, 소프트웨어가 다루어야 할 문제 영역에서도 주요 '행위자'나 '정보 덩어리'를 객체로 인식해야 합니다.

객체를 식별하는 일반적인 방법 🔗

  1. 명사/명사구 찾기
    요구사항 명세서, 유스케이스, 문제 설명서 등 관련 문서에서 반복적으로 등장하거나 중요한 의미를 가지는 명사나 명사구를 후보 객체로 식별합니다.
    • 예시: "고객은 상품을 장바구니에 담고 주문한다." -> 후보 객체: 고객, 상품, 장바구니, 주문
  2. 실세계의 사물 또는 개념
    문제 영역에서 실제로 존재하는 물리적 사물(예: 자동차, 센서), 장소(예: 강의실, 창고), 조직(예: 회사, 부서), 개념적인 것(예: 계좌, 정책, 예약) 등을 객체로 고려할 수 있습니다.
  3. 역할(Role) 기반 식별
    시스템과 상호작용하거나 시스템 내에서 특정 역할을 수행하는 주체들을 객체로 식별합니다. (예: 사용자, 관리자, 결제 처리기)
  4. 이벤트(Event) 기록
    시스템에서 발생하는 중요한 사건이나 그 사건으로 인해 생성되는 정보(예: 주문 발생, 로그인 시도, 오류 보고)도 객체화될 수 있습니다.
  5. 구조(Structure) 기반 식별
    다른 객체들을 포함하거나 관리하는 '전체-부분' 관계의 구조를 나타내는 객체를 식별합니다. (예: 자동차엔진, 바퀴 등을 포함)

식별된 객체를 클래스로 분류하기 🔗

식별된 수많은 후보 객체들은 공통된 속성과 행동을 기준으로
클래스(Class)
로 그룹화(분류)됩니다.
클래스는 동일한 종류의 객체들을 만들기 위한 '틀' 또는 '설계도'입니다.

CRC 카드 (Class-Responsibility-Collaborator) 기법 활용 🔗

CRC 카드는 각 클래스의 이름(Class), 책임(Responsibility), 그리고 협력하는 다른 클래스(Collaborator)를 간단한 카드에 적어보며 객체와 클래스를 식별하고 설계하는 데 도움이 되는 간단하고 효과적인 기법입니다.
팀원들과 함께 브레인스토밍하며 카드를 만들고 배치해보는 과정을 통해 초기 설계를 구체화할 수 있습니다.
이미지 제안: CRC 카드 예시. 카드 앞면이 세 칸으로 나뉘어 맨 위에는 클래스 이름, 왼쪽 아래 칸에는 책임 목록, 오른쪽 아래 칸에는 협력자 클래스 목록이 적혀 있는 모습. 여러 장의 CRC 카드가 테이블 위에 놓여 있는 모습도 좋음.
CRC 카드
CRC 카드

🚀

2단계: 책임 기반 설계 접근법 (Responsibility-Driven Design, RDD) 🔗

객체 지향 설계에서 중요한 개념 중 하나는 각 객체가 자신만의
책임(Responsibility)
을 가진다는 것입니다.
책임 기반 설계는 시스템의 기능을 수행하기 위해 각 객체(또는 클래스)가 어떤 책임을 가져야 하는지, 그리고 그 책임을 수행하기 위해 다른 어떤 객체와
협력(Collaboration)
해야 하는지를 중심으로 설계를 진행하는 방식입니다.

책임이란? 🔗

객체가 알아야 할 정보(Knowing Responsibilities)와 할 수 있는 일(Doing Responsibilities)을 의미합니다.

책임 할당의 원칙 (GRASP 패턴 중 일부 연관) 🔗

어떤 객체에게 어떤 책임을 할당할지는 매우 중요한 결정입니다.
GRASP(General Responsibility Assignment Software Patterns)와 같은 패턴들이 도움을 줄 수 있지만, 기본적으로 다음과 같은 원칙을 고려합니다.
책임 기반 설계는 각 객체의 역할을 명확히 하고, 객체들 간의 협력 관계를 통해 시스템 전체의 기능을 완성해나가는 유기적인 설계 방식을 지향합니다.

🚀

3단계: 캡슐화, 추상화, 다형성의 효과적 사용 🔗

객체 지향 프로그래밍의 세 가지 중요한 기둥인 캡슐화, 추상화, 다형성은 객체 지향 분석 및 설계 과정에서 자연스럽게 고려되고 적용되어야 합니다.

캡슐화 (Encapsulation) 🔗

캡슐화는 관련된 데이터(속성)와 그 데이터를 처리하는 메서드(행동)를 하나로 묶고, 객체 내부의 세부 구현 내용을 외부로부터 숨기는 것입니다.
외부에서는 오직 공개된 인터페이스(public 메서드)를 통해서만 객체에 접근하고 상호작용할 수 있도록 합니다.
public class BankAccount {
    private String accountNumber; // private으로 데이터 보호
    private double balance;       // private으로 데이터 보호
 
    public BankAccount(String accountNumber, double initialBalance) {
        this.accountNumber = accountNumber;
        this.balance = initialBalance;
    }
 
    // 공개된 인터페이스(메서드)를 통해서만 잔액 조회 가능
    public double getBalance() {
        return balance;
    }
 
    // 공개된 인터페이스를 통해 입금 (내부 로직은 숨겨짐)
    public void deposit(double amount) {
        if (amount > 0) {
            balance += amount;
            System.out.println(amount + "원이 입금되었습니다. 현재 잔액: " + balance);
        } else {
            System.out.println("입금액은 0보다 커야 합니다.");
        }
    }
 
    // 공개된 인터페이스를 통해 출금 (내부 로직 및 잔액 확인은 숨겨짐)
    public boolean withdraw(double amount) {
        if (amount > 0 && amount <= balance) {
            balance -= amount;
            System.out.println(amount + "원이 출금되었습니다. 현재 잔액: " + balance);
            return true;
        } else {
            System.out.println("출금액이 유효하지 않거나 잔액이 부족합니다.");
            return false;
        }
    }
}
BankAccount 클래스에서 balanceprivate이므로 외부에서 직접 수정할 수 없고, 오직 depositwithdraw 메서드를 통해서만 안전하게 변경됩니다.

추상화 (Abstraction): 복잡한 것은 단순하게, 핵심만 보기 🔗

추상화는 복잡한 현실 세계의 사물이나 개념에서 현재 관심 있는
핵심적인 특징이나 기능만을 추출
하여 단순화하고 모델링하는 과정입니다.
불필요한 세부 사항은 숨기고, 문제 해결에 필요한 본질적인 부분에만 집중할 수 있도록 합니다.
// 추상화 예시: 인터페이스를 사용한 도형 그리기
interface Drawable { // "그릴 수 있는" 이라는 추상적인 개념 정의
    void draw();     // 무엇을 그릴지는 구체적인 클래스가 결정
}
 
class Circle implements Drawable {
    private int x, y, radius;
    public Circle(int x, int y, int radius) { this.x=x; this.y=y; this.radius=radius; }
 
    @Override
    public void draw() {
        System.out.println("원 그리기 (중심: " + x + "," + y + " 반지름: " + radius + ")");
    }
}
 
class Rectangle implements Drawable {
    private int x1, y1, x2, y2;
    public Rectangle(int x1, int y1, int x2, int y2) { /* ... */ }
 
    @Override
    public void draw() {
        System.out.println("사각형 그리기");
    }
}
 
public class DrawingEditor {
    public void drawShape(Drawable shape) { // Drawable 인터페이스 타입으로 받음
        shape.draw(); // 구체적인 도형이 무엇인지는 신경 쓰지 않고 "그린다"는 행위만 호출
    }
}
DrawingEditor는 구체적인 Circle이나 Rectangle을 알 필요 없이, Drawable이라는 추상적인 "그릴 수 있는 것"에만 의존하여 draw() 메서드를 호출합니다.

다형성 (Polymorphism): 하나의 이름, 다양한 모습 🔗

다형성은 "여러 가지 형태를 가질 수 있는 능력"을 의미하며, 객체 지향에서는 주로
하나의 인터페이스나 부모 클래스 타입을 통해 서로 다른 여러 자식 클래스의 객체를 동일한 방식으로 다룰 수 있는
특성을 말합니다.
메서드 오버라이딩(Overriding)과 오버로딩(Overloading)을 통해 구현됩니다.
public class Main {
    public static void main(String[] args) {
        DrawingEditor editor = new DrawingEditor();
        Drawable circle = new Circle(10, 10, 5);
        Drawable rectangle = new Rectangle(0,0, 20, 20);
 
        editor.drawShape(circle);    // Circle의 draw() 호출
        editor.drawShape(rectangle); // Rectangle의 draw() 호출
 
        // 새로운 Drawable 구현체가 추가되어도 editor.drawShape() 코드는 변경 불필요
        Drawable triangle = new Drawable() { // 익명 클래스로 Triangle 구현
            @Override
            public void draw() {
                System.out.println("삼각형 그리기");
            }
        };
        editor.drawShape(triangle);
    }
}
이처럼 캡슐화, 추상화, 다형성은 객체 지향 분석 및 설계를 통해 만들어지는 소프트웨어의 품질을 높이는 핵심적인 원리들입니다.

🚀

결론 🔗

오늘은 객체 지향 분석(OOA)과 설계(OOD)의 첫걸음을 내딛어 보았습니다.
문제 영역에서 객체를 식별하고 클래스로 분류하는 방법, 각 객체의 책임을 정의하고 협력 관계를 설계하는 책임 기반 접근법, 그리고 객체 지향의 핵심 원리인 캡슐화, 추상화, 다형성을 효과적으로 사용하는 방법에 대해 알아보았습니다.
다음 시간에는 지금까지 배운 여러 방법론들을 좀 더 넓은 시각에서 비교해보고, 특히 현대 소프트웨어 개발에서 많이 사용되는 반복적, 진화적, 그리고 애자일 개발 방법론에 대해 자세히 알아보겠습니다.

참고 🔗