본문 바로가기
License/정보처리기사

[정보처리기사] 1과목 소프트웨어 설계 - 애플리케이션 설계(객체 지향 설계)

by ngool 2024. 11. 4.

📌 객체 지향(Object - Oriented) 설계

객체(Object)와 객체 지향(Object - Oriented) 설계

객체란, 데이터와 데이터를 처리하는 함수를 묶어놓은 하나의 소프트웨어 모듈입니다.

객체의 가장 중요한 특징은 아래 세 가지를 반드시 갖고 있다는 것입니다!

  1. 속성
  2. 동작
  3. 고유 식별자

EX)

  • 객체(Object) : 사람
  • 속성(Property) : 이름(홍길동), 나이(30), 성별(남성)
  • 동작(Method) : 걷기(), 말하기(), 코딩하기()

객체 지향 설계란, 소프트웨어를 여러 객체로 나누고, 이 객체들이 서로 상호작용하게 하여 프로그램을 만드는 방법을 말합니다.


객체 지향 구성요소

  • 객체(Object) : 실세계에 존재하거나 생각할 수 있는 것
  • 메서드(Method) : 객체가 수행할 수 있는 동작이나 기능 (메세지를 받아 실행할)
  • 속성(Property) : 객체들의 상태를 나타내는 데이터
  • 클래스(Class) : 하나 이상의 유사한 객체들을 묶어 공통된 특성을 표현한 것
  • 인스턴스(Instance) : 클래스에 속한 각각의 객체
  • 메세지(Message) : 객체에게 어떤 행위를 하도록 지시하는 명령

객체 지향 설계의 원칙(SOLID)

=> 객체 지향 설계를 위한 5가지 원칙으로, 유지보수성확장성에 도움을 줌

  • 단일 책임의 원칙(Single Responsibility Principle)
    : 하나의 클래스는 하나의 책임만, 즉 하나의 기능이나 역할만 담당해야 함
  • 개방-폐쇄의 원칙(Open Close Principle)
    : 클래스는 확장에 대해 열려 있어야 하며, 변경에 대해 닫혀 있어야 함
    (새로운 기능을 추가할 때 기존 코드를 수정하지 말아야 함)
  • 리스코프 치환의 법칙(Liskov Substitution Principle)
    : 서브 타입(상속받은 하위 클래스)은 어디에서나 자신의 기반 타입(상위 클래스)로 교체할 수 있어야 함

    (부모 클래스의 기능을 자식 클래스가 동일하게 수행할 수 있어야 함)
  • 인터페이스 분리의 법칙(Interface Segregation Principle)
    : 클라이언트는 자신이 사용하지 않는 메서드에 의존하지 않아야 함

    (필요 없는 기능은 과감하게 제거되어야 함)
  • 의존성 역전의 원칙(Dependency Inversion Principle)
    : 구체적인 클래스보다는 인터페이스나 추상 클래스에 의존해야 함

📌 객체 지향 설계의 특징

  • 다형성
  • 캡슐화
  • 상속
  • 추상화
  • 정보은닉
  • 관계성(연관화, 집단화, 분류화, 일반화, 특수화)

다형성(polymorphism)

=> 하나의 부모 객체(클래스)가 여러 형태로 동작할 수 있게 하는 능력

  • 코드의 유연성과 재사용성을 높임

오버라이딩(Overiding)

: 상위 객체(클래스)에서 정의한 메서드를 하위 객체(클래스)에서 재정의

오버로딩(Overloading)

: 같은 클래스 내에서 매개변수 유형과 개수를 다르게 하여 같은 이름의 메서드를 여러 개 정의


캡슐화(encapsulation)

=> 객체 속성과 메서드를 하나로 묶고, 실제 구현 내용의 일부를 외부에 감추어 은닉

(정보를 숨기고, 필요한 인터페이스만 밖으로 드러내는 과정)

  • 객체 지향에서 정보 은닉과 가장 관계가 있음

<캡슐화의 장점>

  • 인터페이스 단순화
  • 소프트웨어의 재사용성 향상
  • 변경 발생 시 오류의 파급효과가 적음

상속(inheritance)

=> 상위 객체의 메서드와 속성을 하위 객체가 물려받는 것

  • 추가로 각자의 고유한 특성을 정의할 수 있음
  • 코드의 재사용성을 높이고, 기존 객체를 확장하여 새로운 객체를 쉽게 만들 수 있음

추상화(abstraction)

=> 복잡한 시스템의 중요한 부분만 드러내고, 세부 사항은 감추는 것

(가장 본질적이고 공통적인 부분만 추출하여 표현)

  • 소프트웨어 설계 시 추상화 기법으로는 자료, 제어, 과정 추상화가 있음

정보은닉(Information Hiding)

=> 객체가 가지고 있는 속성과 연산 일부를 감추어 객체의 외부에서는 접근 불가능하게 하는 개념

  • 정보 은닉의 근본 목적은 고려되지 않은 영향(Side-effect) 최소화
  • 모듈들 사이의 독립성을 유지시키는데 도움이 됨
  • 요구사항 변화에 따라 모듈 내부의 자료 구조와 접근 동작들을 수정 가능

관계성(Relationship)

=> 클래스 or 객체 간에서 데이터를 참조하는 관계를 나타내는 기법

  • 연관화 : 객체 간의 일반적인 관계 (is-member-of)
    • 학생 - 교수
  • 분류화 : 객체를 공통된 특성에 따라 그룹화 (is-instance-of)
    • 동물을 포유류, 조류, 어류로 그룹화
  • 집단화 : 전체와 부분의 관계로, 부분은 독립적으로 존재 가능 (is-a-part-of)
    • 팀과 선수
  • 일반화 : 여러 클래스의 공통된 특성을 추출하여 상위 클래스를 만드는 것 (is-a)
    • 개와 고양이의 특성을 조합해 동물 클래스를 만듦
  • 특수화 : 상위 클래스를 기반으로 더 구체적이고 특수한 하위 클래스를 만드는 것 (is-a)
    • 동물 클래스를 특수화하여 개와 고양이 클래스를 만듦

📌 객체 지향 분석

객체 지향 분석이란, 소프트웨어 개발을 위해 비즈니스 문제를 객체와 속성, 클래스와 멤버, 전체와 부분 등으로 나누어서 분석하는 과정을 말합니다.

 

<객체 지향 분석의 특징>

  • 시스템 내 객체들이 상호작용하는 방식을 설계하는 동적 모델링 기법이 사용될 수 있음
  • 세부적인 객체들을 먼저 설계하고 전체 시스템을 통합시키는 상향식 방식
  • 데이터와 행위를 하나로 묶어 객체를 정의하고 추상화시키는 작업
  • 코드 재사용에 의한 프로그램 생산성 향상 및 요구에 따른 시스템의 쉬운 변경이 가능

객체 지향 분석 기법

  • Coad - Yourdon 방법론
    : ER 다이어 그램을 사용하여 객체의 행위를 데이터 모델링하는 데 초점을 둔 방법
  • 럼바우(Rumbaugh) 방법론
     : 객체 모델 → 동적 모델 → 기능 모델로 나누어 순서대로 분석을 수행하는 방법
    • 객체 모델 : 시스템에 필요한 객체를 찾아내어 속성과 관계를 규정하여 다이어그램으로 표시
    • 동적 모델 : 상태 다이어그램, 사건 흐름 다이어그램 등을 통해 객체 간의 상호작용을 시간 순으로 표현
    • 기능 모델 : 시스템의 기능을 자료 흐름도(DFD)를 통해 표현
  • Booch 방법론 : 설계 문서화를 강조하여 다이어그램을 중심으로 개발
  • Jacobson 방법론 : 유스케이스를 모든 모델의 근간으로 활용하는 방법론

📌 디자인 패턴

디자인 패턴이란?

디자인 패턴이란, 소프트웨어 설계에서 자주 발생하는 문제에 대한 일반적이고 반복적인 해결 방법


디자인 패턴 사용의 장점

  • 소프트웨어 구조 파악이 용이함
  • 객체 지향 설계 및 구현의 생산성을 높이는 데 적합
  • 재사용을 위한 개발 시간이 단축

GoF(Gang of Four)의 디자인 패턴

=> 디자인 패턴을 생성, 구조, 행위의 3가지 구분과 23개의 패턴으로 분류

  • 생성(Creational) 패턴
    • 객체를 어떻게 생성하고 초기화할지에 대한 패턴들
  • 구조(Structural) 패턴
    • 클래스와 객체를 더 큰 구조로 조합하는 방법에 대한 패턴들
  • 행위(Behavior) 패턴
    • 객체 간의 상호작용과 책임 분배에 대한 패턴들

GoF의 주요 디자인 패턴

<생성 패턴>

  • Factory method 패턴
    : 객체를 생성하기 위한 인터페이스를 정의하여 어떤 클래스가 인스턴스화 될 것인지는 서브 클래스가 결정하도록 하는 것
    # 문서의 기본 클래스를 정의
    class Document:
        def create_document(self):
            pass
    
    # Word 문서 클래스
    class WordDocument(Document):
        def create_document(self):
            print("Word 문서를 생성합니다.")
    
    # PDF 문서 클래스
    class PdfDocument(Document):
        def create_document(self):
            print("PDF 문서를 생성합니다.")
    
    # 클라이언트 코드
    def document_creator(document_type):
        if document_type == "word":
            return WordDocument()
        elif document_type == "pdf":
            return PdfDocument()
    
    doc = document_creator("word")
    doc.create_document()  # "Word 문서를 생성합니다."
    
  • Prototype 패턴
    : prototype을 먼저 생성하고 인스턴스를 복제하여 사용하는 구조
    • 예시 : 프로그래밍에서 게임 캐릭터가 많아질 때, 모든 캐릭터를 새로 생성하기보다는 기존 캐릭터를 복제(clone)하는 방식을 사용할 수 있음
    import copy
    
    class Character:
        def __init__(self, name, power):
            self.name = name
            self.power = power
    
        def clone(self):
            return copy.deepcopy(self)
    
    # 원본 캐릭터 생성
    warrior = Character("Warrior", 50)
    
    # 원본 캐릭터 복제
    clone_warrior = warrior.clone()
    clone_warrior.name = "Warrior Clone"
    
    print(warrior.name, warrior.power)       # Warrior 50
    print(clone_warrior.name, clone_warrior.power)  # Warrior Clone 50
    
  • Singleton 패턴
    : 한 클래스에 한 객체만 존재하도록 제한하는 패턴 (생성된 객체를 어디에서든지 참조할 수 있도록 함)
    • 예시 : 애플리케이션에서 데이터베이스 연결을 관리하는 Database 클래스가 있다고 가정. 여러 곳에서 이 클래스의 객체를 사용하더라도, 같은 인스턴스 하나만 사용해야 함
    class Database:
        _instance = None
    
        def __new__(cls):
            if cls._instance is None:
                cls._instance = super(Database, cls).__new__(cls)
                print("데이터베이스 연결을 생성합니다.")
            return cls._instance
    
    # 두 개의 데이터베이스 인스턴스 생성 시도
    db1 = Database()
    db2 = Database()
    
    print(db1 is db2)  # True, 두 인스턴스는 동일
    

<행위 패턴>

  • Strategy 패턴
    : 다양한 알고리즘을 캡슐화하여 알고리즘 대체가 가능하도록 한 행위 패턴
    • 예시 : 온라인 상점에서 결제 방식을 선택할 수 있는 경우를 가정. 카드 결제, 페이팔 결제 등 다양한 결제 전략이 존재하고, 사용자는 이를 선택할 수 있음.
    # 결제 전략 인터페이스
    class PaymentStrategy:
        def pay(self, amount):
            pass
    
    # 카드 결제 전략
    class CreditCardPayment(PaymentStrategy):
        def pay(self, amount):
            print(f"Credit Card로 {amount}원을 결제합니다.")
    
    # 페이팔 결제 전략
    class PayPalPayment(PaymentStrategy):
        def pay(self, amount):
            print(f"PayPal로 {amount}원을 결제합니다.")
    
    # 결제 컨텍스트
    class ShoppingCart:
        def __init__(self, payment_strategy):
            self.payment_strategy = payment_strategy
    
        def checkout(self, amount):
            self.payment_strategy.pay(amount)
    
    # 클라이언트 코드
    cart1 = ShoppingCart(CreditCardPayment())
    cart1.checkout(10000)  # Credit Card로 10000원을 결제합니다.
    
    cart2 = ShoppingCart(PayPalPayment())
    cart2.checkout(20000)  # PayPal로 20000원을 결제합니다.
    
    
  • Mediator 패턴
    : 객체들 간의 상호작용을 직접 하지 않고 중재자를 통해 처리하도록 만드는 패턴
    (이로 인해 객체들이 서로를 참조할 필요가 없으며, 객체 간의 의존성을 줄이고 유지보수를 쉽게 할 수 있게 됨)
    • 예시 : 채팅 방에서 여러 사용자가 서로 대화하는 경우 가정. 모든 사용자가 서로 직접 메시지를 보내는 대신, ChatMediator라는 중재자를 통해 메시지를 전달받고 전달함.
    # 중재자 인터페이스
    class ChatMediator:
        def send_message(self, message, user):
            pass
    
    # 채팅 중재자 클래스
    class ChatRoom(ChatMediator):
        def __init__(self):
            self.users = []
    
        def add_user(self, user):
            self.users.append(user)
    
        def send_message(self, message, user):
            for u in self.users:
                if u != user:  # 메시지를 보낸 사용자는 제외하고 전달
                    u.receive(message)
    
    # 사용자 클래스
    class User:
        def __init__(self, name, chat_mediator):
            self.name = name
            self.chat_mediator = chat_mediator
    
        def send(self, message):
            print(f"{self.name}님이 메시지를 보냈습니다: {message}")
            self.chat_mediator.send_message(message, self)
    
        def receive(self, message):
            print(f"{self.name}님이 메시지를 받았습니다: {message}")
    
    # 클라이언트 코드
    chat_room = ChatRoom()
    
    user1 = User("Alice", chat_room)
    user2 = User("Bob", chat_room)
    user3 = User("Charlie", chat_room)
    
    chat_room.add_user(user1)
    chat_room.add_user(user2)
    chat_room.add_user(user3)
    
    user1.send("안녕하세요!")  
    # 출력:
    # Bob님이 메시지를 받았습니다: 안녕하세요!
    # Charlie님이 메시지를 받았습니다: 안녕하세요!