0. 설계 개요

  • 설계

    • 요구 분석(“무엇을 만들 것인가”) 이후 “어떻게 실현할 것인가”를 결정하는 활동
  • 설계 종류

    • 아키텍처(구조) 설계: 시스템 전체 구조, 모듈의 역할과 인터페이스 정의
    • 상세 설계: 모듈 내부의 알고리즘, 데이터 명세화
  • 적용 설계 원리

    • 전통적: 분할 정복, 추상화, 모듈화 등
    • 객체지향: 정보 은닉, 인터페이스 분리, 의존관계 역전 등

1. 설계 기본 개념

1.1. 분할과 정복

  • 분할과 정복 (Divide and Conquer)
    • 가장 기본적인 소프트웨어 설계 전략
    • 큰 문제를 소 단위로 나누어 처리 후 결합
    • 분할 계층: 시스템 → 서브시스템 → 패키지 → 클래스 → 메서드

1.2. 아키텍처 설계

핵심 개념설명
모듈클래스·패키지로 구성된 구현 단위 (정적 구조)
컴포넌트모듈·패키지로 구성된 실행 단위 (런타임 배포)
서브시스템기능 단위인 컴포넌트의 집합
3가지 설계 관점설명
모듈 관점코드 단위인 모듈과 그 관계
컴포넌트 관점실행 시 동작하는 요소와 상호작용
할당 관점하드웨어 설치, 작업 할당, 데이터 저장 관점
  • 아키텍처 설계 과정

    1. 설계 목표(품질 목표) 설정
    2. 시스템 스타일(아키텍처 스타일) 결정
    3. 서브시스템 기능 및 인터페이스 명세
    4. 설계 리뷰 (피드백 반영)
  • 아키텍처적으로 중요한 요구사항 (ASR)

    • 설계 목적(비즈니스 목적), 품질 속성, 최우선 기능, 아키텍처 관심사, 제약사항
  • 아키텍트 필요 역량

    • 리더십, 의사소통, 협상, 기술적 능력, 프로젝트 기술, 분석 능력

2. 품질 목표

  • 이해관계자의 요구를 반영하여 품질 속성을 결정한다.
ISO 25010 품질 속성영문
성능Performance
상호운영성Interoperability
사용용이성Usability
신뢰성Reliability
보안성Security
유지보수성Maintainability
이식성Portability

3. 전통적인 설계 원리

  • 핵심 요소
    • 효율성(처리 시간·공간)
    • 단순성(유지보수성에 가장 중요)

3.1. 추상화 (Abstraction)

  • 목적과 관련된 정보에 집중하고 나머지는 무시
  • 복잡도를 줄여 문제 분할의 근본
  • 종류: 자료 추상화 / 절차·동작 추상화

3.2. 캡슐화 (Encapsulation)

  • 속성(attribute)과 행위(method)를 하나로 묶고 일부를 정보 은닉(information hiding)
  • 내부 변경에 대한 외부 영향을 최소화

3.3. 모듈화 (Modularization)

  • 문제를 소프트웨어 구성 요소 수준으로 분할
  • 모듈 = 클래스, 패키지 (독립적인 기능 단위)
  • 추상화 → 캡슐화 → 모듈화 순으로 통째로 구성된 시스템을 잘 모듈화된 시스템으로 완성

(1) 결합도 (Coupling)

낮을수록 좋다

결합 종류설명강도
내용 결합 (Content)다른 모듈의 내부를 직접 참조강함 ↑
공통 결합 (Common)전역 변수를 공유-
제어 결합 (Control)제어 흐름을 결정-
스탬프 결합 (Stamp)필요 없는 복합 자료구조 전달-
데이터 결합 (Data)단순 타입 매개변수 전달약함 ↓

개선 방향: 모듈 간 인터페이스 수를 줄이고, 간단한 정보를 매개변수로 전달

(2) 응집도 (Cohesion)

높을수록 좋다

응집 종류설명강도
우연적 (Coincidental)요소 간 의미적 관계 없음약함 ↑
논리적 (Logical)같은 범주로 논리적 분류-
시간적 (Temporal)특정 시간에 함께 처리-
절차적 (Procedural)수행 순서와 관련-
교환적 (Communicational)동일 입출력 데이터 사용-
기능적 (Functional)하나의 기능에 모두 기여-
정보적 (Informational)모든 기능이 같은 데이터에 실행강함 ↓

모듈은 단일 책임을 가져야 한다.

4. 객체지향 설계 원리 (SOLID)

Martin이 제안한 5가지 원칙

원칙키워드핵심 한 줄 요약
SRP단일 책임클래스는 변경 이유가 하나뿐이어야 한다
OCP개방 폐쇄확장엔 열려 있고, 수정엔 닫혀 있어야 한다
LSP리스코프 교체자식 클래스는 부모 클래스를 대체할 수 있어야 한다
ISP인터페이스 분리사용하지 않는 인터페이스를 강제 구현해서는 안 된다
DIP의존관계 역전구체 클래스가 아닌 추상화(인터페이스)에 의존해야 한다

4.1. SRP — 단일 책임의 원리

Single Responsibility Principle

  • 클래스는 하나의 책임(기능)만 가져야 한다
  • 변경 이유를 하나로 제한 → 응집도 ↑, 결합도 ↓

4.2. OCP — 개방 폐쇄의 원리

Open Close Principle

  • 확장(상속)에는 열려 있고, 수정에는 닫혀 있어야 한다
  • 추상 클래스·인터페이스로 변화 부분을 추상화하고 다형성으로 기능 확장
  • Open: 하위 클래스를 만들어 새 기능 추가
  • Close: 기존 코드는 수정하지 않음

4.3. LSP — 리스코프 교체의 원리

Liskov Substitution Principle

  • 상위 타입의 객체를 하위 타입으로 치환해도 프로그램이 정상 동작
  • 상속은 코드 재사용이 아니라 행위의 일관성 보장
  • LSP 위반 시 OCP도 연쇄 위반
// LSP 위반 예시
class Bicycle extends Transportation {
    public void startEngine() {
        throw new RuntimeException("자전거는 엔진이 없어요!"); // 위반!
    }
}

4.4. ISP — 인터페이스 분리의 원리

Interface Segregation Principle

  • 클라이언트가 사용하지 않는 인터페이스를 강제 구현해서는 안 됨
  • 비만 인터페이스(fat interface)를 기능별로 분리하여 필요한 것만 구현

SRP: 클래스 내부 책임 분리 / ISP: 인터페이스 책임 분리

4.5. DIP — 의존관계 역전의 원리

Dependency Inversion Principle

  • 고수준 모듈이 저수준 모듈에 직접 의존하지 않고, 둘 다 추상화(인터페이스)에 의존
  • 변화하기 쉬운 구체 클래스보다 변화하기 어려운 인터페이스에 의존
  • 전통 관계: DataAnalyzer → BubbleSort
  • 역전 관계: DataAnalyzer → SortAlgorithm ← BubbleSort

5. 컴포넌트 구성 원리

  • 핵심 원칙
    • 같이 변경·재사용·배포되는 것끼리 묶어 컴포넌트 구성
    • 컴포넌트 간에도 DIP 적용: 인터페이스를 통해 종속성 관리
    • 컴포넌트 내부: 높은 응집도 / 컴포넌트 간: 낮은 결합도

5.1. 의존성 주입 (Dependency Injection)

  • 객체가 필요한 의존 객체를 직접 생성하지 않고 외부에서 주입 받는 방식
  • DIP를 실제로 구현하는 가장 대표적인 방법
// DI 적용 예시
class OrderService {
    private PaymentMethod paymentMethod;
    public OrderService(PaymentMethod paymentMethod) { // 외부 주입
        this.paymentMethod = paymentMethod;
    }
}

5.2. IoC (Inversion of Control, 제어 역전)

  • 인스턴스 생성과 의존 관계 연결을 개발자가 아닌 외부 프레임워크(DI 컨테이너) 가 담당
  • DI 컨테이너가 객체를 생성하고 필요한 의존성 주입
  • IoC를 구현하는 디자인 패턴 = 의존성 주입(DI)

5.3. 컴포넌트 DIP의 장점

  • 독립적 배포: 하위 컴포넌트 교체 시 상위 컴포넌트 재컴파일 불필요
  • 병렬 개발: 인터페이스(규격)만 정해지면 각 팀이 동시 개발 가능
  • 테스트 용이: 실제 컴포넌트 대신 Mock 컴포넌트로 테스트 가능