소프트웨어 설계 1: 소프트웨어 아키텍쳐와 객체지향
22 Jul 2024 #cs
소프트웨어 아키텍쳐 (Software Architecture)
SW를 구성하는 요소 간 관계를 표현하는 시스템의 구조 혹은 구조체입니다.
- 소프트웨어 아키텍쳐 설계 기본 원리: 모듈화, 추상화, 단계적 분해, 정보 은닉
모듈화 (Modularity)
시스템의 기능을 모듈 단위로 나누는 것을 말합니다. 모듈화는 SW의 성능 향상 및 시스템의 수정, 재사용, 유지관리 등이 용이하도록 합니다. 결합도(Coupling)를 최소화하고 응집도(Cohesion)의 최대화하는 것을 목표로 합니다.
추상화 (Abstraction)
문제의 포괄적인 개념을 설계한 후 이를 구체화해나가는 것을 말합니다. 유형은 다음과 같습니다:
- 과정 추상화: 수행 과정 설계 시 전반적인 흐름만 파악할 수 있게 설계
- 자료 추상화: 데이터 정의 시 데이터 구조를 대표할 수 있는 표현만 사용
- 제어 추상화: 이벤트 관련 정의 시 절차나 방법을 대표할 수 있는 표현을 사용
단계적 분해 (Stepwise Refinement)
문제를 중요한 상위 개념에서 하위 개념으로 구체화하는 분할 기법으로, Niklaus Wirth가 제안한 하향식 설계 전략입니다. SW의 포괄적 기능에서 점차 알고리즘, 자료 구조 등 상세한 부분으로 설계해나갑니다.
정보 은닉 (Information Hiding)
모듈 내부에 포함된 정보를 감추어 다른 모듈이 접근 및 변경하지 못하게 하는 기법입니다. 이를 통해 모듈을 독립적으로 수행할 수 있고, 모듈이 변경되어도 다른 모듈에 영향을 주지 않아 수정, 테스트, 유지보수가 용이합니다.
협약 (Contract)에 의한 설계
컴포넌트의 정확한 인터페이스를 명세하는 것입니다. 컴포넌트 설계 시 클래스에 대한 가정을 공유할 수 있게 합니다. 명세에 포함될 조건으로는:
- Precondition (선행 조건): 연산이 호출되기 전에 만족되어야 하는 조건
- Postcondition (결과 조건): 연산이 수행된 후 만족되어야 하는 조건
- Invariant (불변 조건): 연산 실행 중 항상 만족되어야 하는 조건
아키텍쳐 패턴 (Pattern)
아키텍쳐 설계 시 참조할 수 있는 해결 방식 혹은 예제로, 시스템 구조의 기본적인 윤곽을 제시합니다. 각 패턴에는 서브 시스템과 각각의 역할이 정의되어 있습니다.
- 레이어 패턴 (Layers Pattern): 시스템을 계층으로 구성하는 고전적인 패턴입니다.
- 상위 계층은 하위 계층의 클라이언트가, 하위 계층은 서비스 제공자가 됩니다.
- 상호작용은 마주 보는 두 계층 사이에서만 이뤄집니다.
- 대표적 사례: OSI 참조 모델
- 클라이언트-서버 패턴 (Client-Server Pattern): 하나의 서버 컴포넌트와 다수의 클라이언트 컴포넌트로 구성되는 패턴입니다.
- 사용자는 클라이언트에 요청하고, 클라이언트가 서버의 응답을 받아 사용자에게 제공합니다.
- 파이프-필터 패턴 (Pipe-Filter Pattern): 데이터 스트림의 각 단계를 필터로 캡슐화하여, 파이프를 통해 전송하는 패턴입니다.
- 파이프를 통해 이전 시스템의 결과물을 전달 받아 처리한 후, 그 결과를 다음 시스템에 넘겨주는 것을 반복합니다.
- 데이터 변환, 버퍼링, 동기화 등에 사용됩니다.
- 대표적 사례: UNIX의 Shell
- 모델-뷰-컨트롤러 패턴 (Model-View-Controller Pattern): 서브시스템을 모델, 뷰, 컨트롤러로 구조화하는 패턴입니다.
- 사용자는 컨트롤러에 요청하고, 컨트롤러는 기능과 데이터가 보관된 모델에서 처리한 후, 뷰에 결과를 출력합니다.
- 뷰는 여러 개 만들 수 있습니다.
- 대화형 애플리케이션에 적합합니다.
객체 지향 (Object-Oriented)
SW의 각 요소를 객체(object)로 만들고, 객체를 조립해서 SW를 개발하는 기법입니다.
- 구성 요소: 객체, 클래스, 메시지
- 특징: 캡슐화, 상속, 다형성, 연관성
객체 지향의 구성 요소
객체 (Object)
데이터와 함수를 묶어 놓은 SW 모듈입니다.
- 데이터: 속성, 상태 등 객체가 갖고 있는 정보
- 함수: 객체가 수행하는 기능, 데이터를 처리하는 알고리즘. 객체의 상태를 참조/변경하는 수단
클래스 (Class)
공통된 속성과 연산을 갖는 객체의 집합, 객체들의 속성과 연산을 정의하는 틀입니다.
- 인스턴스 (Instance): 클래스에 속한 각 객체
메시지 (Message)
객체 간 상호작용에 사용되는 수단으로, 객체의 연산을 일으키는 외부의 요구사항입니다. 객체가 메시지를 받으면 그에 맞는 연산을 수행하고 예상된 결과를 반환합니다.
캡슐화 (Encapsulation)
객체의 인터페이스를 제외한 세부 내용을 은닉하는 것으로, 외부로부터 오는 접근을 제한합니다. 캡슐화된 객체는 외부 모듈이 변경되어도 영향을 적게 받습니다. 또한 메시지를 주고 받을 때 상대 객체의 세부 내용은 알 필요가 없으므로, 인터페이스가 단순해지고 결합도가 낮아집니다.
객체 지향의 특징
상속 (Inheritance)
어떤 클래스의 모든 속성과 연산을 물려받는 것으로, 상속 되는 클래스를 상위 클래스, 상속 받는 클래스를 하위 클래스라고 합니다. 하위 클래스는 물려받은 속성과 연산을 즉시 자신의 것으로 사용할 수 있습니다. 또한 상속 받은 것 외의 새로운 속성과 연산을 추가해 사용할 수 있습니다.
다형성 (Polymrphism)
메시지에 대해 각 객체의 고유한 방법으로 응답할 수 있는 능력입니다. 이것으로 인해 같은 클래스를 상속한 여러 하위 객체들이 서로 다른 특성을 갖는 객체로 이용될 수 있습니다.
연관성 (Relationship)
두 개 이상의 객체들이 상호 참조하는 관계를 말합니다. 종류로는:
- 연관화 (Association; is member of): 둘 이상의 객체가 상호 관련되어 있는 관계
- 분류화 (Classification; is instance of): 동일한 형 특성을 갖는 객체들을 모아 구성하는 것
- 집단화 (Aggregation, is part of): 관련 있는 객체들을 묶어 하나의 상위 객체를 구성하는 것
- 일반화 (Generalization, is a): 공통 성질로 추상화한 상위 객체를 구성하는 것
- 특수화 (Specialization, is a): 상위 객체를 구체화하여 하위 객체를 구성하는 것
객체지향 분석 (OOA; Object Oriented Analysis)
요구사항과 관련된 객체, 속성, 연산, 관계 등을 정의해 모델링하는 작업으로, 클래스를 식별하는 것이 주 목적입니다. 개발 업무를 객체와 속성, 클래스와 멤버, 전체와 부분 등으로 나눠 분석하는 작업입니다.
객체지향 분석 방법론
- Rumbaugh(럼바우): 객체 모델, 동적 모델, 기능 모델로 나눠 분석함.
- Booch: 미시적(micro) 및 거시적(macro) 개발 프로세스를 모두 사용해 클래스와 객체를 분석하고 클래스를 정의함.
- Jacobson: 유스케이스(use case)를 강조하여 사용.
- Coad & Yourdon: E-R 다이어그램을 사용해 객체의 행위를 모델링. 객체 식별, 구조 식별, 주제 정의, 속성과 인스턴스 연결 정의, 연ㅅ나과 메시지 연결 정의 등의 과정으로 구성.
- Wirfs-Brock: 고객 명세서를 평가해 설계까지 연속적으로 수행. 분석과 설계를 구분하지 않음.
객체지향 설계 원칙 (SOLID)
객체지향 설계에서 유지보수성과 확장성을 높이기 위한 다섯 가지 기본 원칙을 의미합니다. 각 원칙은 소프트웨어 모듈이 어떻게 설계되어야 하는지를 규정합니다.
단일 책임 원칙 (SRP; Single Responsibility Principle)
클래스는 하나의 책임만 가져야 하며, 클래스가 변경되는 이유는 오직 하나뿐이어야 한다는 원칙입니다.
- 예시: 하나의 클래스가 데이터베이스 접근과 UI 처리를 동시에 하지 않도록 분리.
개방-폐쇄 원칙 (Open/Closed Principle, OCP)
소프트웨어 요소는 확장에는 열려 있어야 하고, 변경에는 닫혀 있어야 한다는 원칙입니다. 기존 코드를 변경하지 않고 새로운 기능을 추가할 수 있도록 설계 되어야 합니다.
리스코프 치환 원칙 (Liskov Substitution Principle, LSP)
서브 타입은 언제나 기반 타입으로 교체할 수 있어야 한다는 원칙입니다. 자식 클래스는 부모 클래스의 기능은 수행할 수 있어야 하고, 이를 손상시키지 않고 확장만 수행해야 합니다.
인터페이스 분리 원칙 (Interface Segregation Principle, ISP)
특정 클라이언트를 위한 인터페이스 여러 개가 범용 인터페이스 하나보다 낫다. 클라이언트가 사용하지 않는 메서드가 포함된 거대한 인터페이스 대신, 세분화된 여러 인터페이스를 제공.
의존 역전 원칙 (Dependency Inversion Principle, DIP)
고수준 모듈은 저수준 모듈에 의존해서는 안 되며, 둘 다 추상화에 의존해야 한다. 구체적인 클래스에 의존하지 않고 인터페이스나 추상 클래스에 의존하여 구현.