0. 설계 개요

  • 설계

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

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

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

1. 설계 기본 개념

1.1. 서브 시스템, 컴포넌트, 모듈

  • 분할과 정복 (Divide and Conquer)
    • 가장 기본적인 소프트웨어 설계 전략
    • 큰 문제를 소 단위로 나누어 처리 후 결합
    • 분할 계층: 시스템 → 서브시스템 → 패키지 → 클래스 → 메서드
핵심 개념설명
서브시스템기능 단위인 컴포넌트의 집합
컴포넌트모듈·패키지로 구성된 실행 단위 (런타임 배포)
모듈클래스·패키지로 구성된 구현 단위 (정적 구조)

1.2. 설계 관점

3가지 설계 관점설명
할당 관점하드웨어 설치, 작업 할당, 데이터 저장 관점
컴포넌트 관점실행 시 동작하는 요소와 상호작용
모듈 관점코드 단위인 모듈과 그 관계

1.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) 결합

낮을수록 좋다

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

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

(2) 응집

높을수록 좋다

응집 종류설명강도
우연적 (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. 의존성 주입 (DI)

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

5.2. 제어 역전 (IoC)

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

5.3. 컴포넌트 DIP의 장점

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