notepad $PROFILE

VS Code 다운로드 페이지로 가서 본인 OS에 맞게 설치한다. Visual Studio Code+1

  1. C/C++ 컴파일러를 설치한다.
  2. 설치 후 VS Code를 실행하여 예제코드를 실행합니다.
  3. Troubleshooting

1. Visual Studio Code (VS Code) 인스톨하기

1.1 다운로드받기

Microsoft Visual Studio Code: https://code.visualstudio.com/download

 

Download Visual Studio Code - Mac, Linux, Windows

Visual Studio Code is free and available on your favorite platform - Linux, macOS, and Windows. Download Visual Studio Code to experience a redefined code editor, optimized for building and debugging modern web and cloud applications.

code.visualstudio.com

 

아래 화면이 뜨면 (OS가 윈도우라는 전제하에) User Installer를 선택하여 실행파일을 다운로드 받는다. 본 문서 작성시 다운로드 버전은 VSCodeUserSetup-x64-1.107.1.exe 이다.

1.2 인스톨하기

1. 다운로드받은  VSCodeUserSetup-x64-1.107.1.exe 를 실행한다. 

2. 라이선스 계약을 동의하고 '다음' 버튼을 클릭한다.

3. 아래 항목의 체크를 확인한 후 다음버튼을 클릭한다.

4. '설치'버튼을 클릭하여 설치를 시작한다. 

 

5. 설치완료가 되면 아이콘을 클릭하여 프로그램을 실행시킨다.

 

1.3  VS Code 확장(Extension) 설치하기: C/C++

1.3.1 VS Code에서 Extensions(확장) 열기

`

  • Windows/Linux: Ctrl + Shift + X
  • macOS: ⇧ + ⌘ + X

 

 

 

 

 

 

 

 

 

 

 

1.3.2 검색창에 C++ 입력 → “C/C++ (Microsoft)” 설치를 입력하여 아래 c/c++ 확장팩을 설치한다. 

  •    이 확장팩은 C/C++ IntelliSense, 기본 디버깅 연동 기능 등을 제공한다. (컴파일러 아님!)

 

 

1.4  한글확장 (선택)

왼쪽 아이콘 메뉴의 확장 아이콘을 클릭하여 korea로 찾으면 한글 확장팩 설치가 가능하다.

 

2A. C/C++ 컴파일러 설치하기 (windows 기준)

2.1 MSYS2 설치

MSYS2 Windows에서 C/C++ 같은 개발 도구를 “리눅스처럼” 설치·관리할 수 있게 해주는 개발 환경
  1. 터미널 환경(bash 등) - Windows에서 리눅스 느낌의 터미널과 기본 유닉스 도구들을 제공
  2. *패키지 관리자 pacman
    • “GCC 설치”, “GDB 설치”, “업데이트” 같은 걸 명령 한 줄로 처리
    • 예: pacman -S mingw-w64-ucrt-x86_64-gcc
[참고] 패키지 관리자
-
프로그램(패키지)을 설치/업데이트/삭제하고, 필요한 “의존성(같이 필요한 파일·라이브러리)”까지 자동으로 맞춰주는 도구
- 개발에 필요한 패키지 도구들을 다운로드 → 압축풀기 → 경로 맞추기 → 업데이트 관리를 사람이 일일이 하지 않게 해주는 설치 관리자

 

1. MSYS2 공식 사이트(https://www.msys2.org/?utm_source=chatgpt.com) 로 이동

2. 본인 PC의 환경에 맞는 파일 선택하여 인스톨 한다. (본 페이지는  msys2-x86_64-20251213.exe 선택 기준으로 작성) 

 

 

3. 설치가 완료되면, 윈도우즈의 시작 메뉴에서 “MSYS2 UCRT64” 를 실행하여 아래 화면이 나오는지 확인한다. 

 

 

2.2 gcc 설치 (컴파일러 설치)

  • 2.1.1에서 설치한 MSYS2 UCRT64 터미널에서 시작 

1. (권장) 패키지 업데이트

pacman -Syu

 

2. GCC 설치

pacman -S mingw-w64-ucrt-x86_64-gcc

 

3. 설치확인

gcc --version

 

 

 

 4. Windows PATH에 아래 경로를 추가한다. 

  • C:\msys64\ucrt64\bin
  • [참고] C:\msys64는 msys64를 인스톨하면서 위치한 폴더이다. 인스톨시 변경을 했으면 해당 폴더 path로 추가한다. 

 5. VS Code를 실행시킨다. 

 

6. VS Code IDE에서 ctrl + J를 눌러 터미널을 열어 아래를 입력하여 path가 잡혔는지 확인한다.

gcc --version

  

2B. C/C++ 컴파일러 설치하기 (macOS 기준)

1. 터미널(Terminal.app) 실행

 

2. 아래 명령 입력하여 엔터

xcode-select --install
 
 

3. 팝업이 뜨면 Install 진행 → 설치 완료까지 기다린다.

 

4. 설치가 완료되면 아래를 입력하여 설치완료를 확인한다. 

 
clang --version
  • 버전 정보가 나오면 OK입니다.
  • 참고로 macOS에서 gcc를 쳐도 내부적으로 clang을 가리키는 경우가 많습니다:
 

3. 샘플코드 실행시키기

1. VS code에서 메뉴->파일->새파일을 선택하여 test.c 파일을 생성한다. 

2. hello.c에 아래 코드를 입력한다. 

#include <stdio.h>

int main(void) {
    printf("Hello, world!\n");
    return 0;
}

 

 3. 코드를 컴파일 하여 실행시킨다. 
 
  • 아래 화면의 ▷ 버튼을 눌러 C/C++:Compile & Run 을 선택하여 실행한다. 

4. 실행결과를 확인한다. 

 

 

 

 

 

예제. 도서관 도메인에서 상속관계 클래스 찾기

요구사항

도서관 시스템 요구사항 중 도서관리

도서관 시스템은 도서를 공통적으로 고유 식별자, 제목, 저자, ISBN, 출판사, 출판연도, 장르 정보를 포함하는 기본 도서(Book)로 관리하며, 검색 결과에서는 해당 공통 정보를 출력한다. 도서는 제공 형태에 따라 일반도서(PrintedBook)와 전자도서(EBook)로 구분되며, 일반도서는 서가 위치와 대출 가능 권수를 추가로 관리하고 대출 시 권수가 감소하며 반납 시 증가한다. 전자도서는 파일 형식, 접근 URL, 동시 이용 가능 인원 정보를 추가로 관리하며, 이용 시작 시 현재 이용 인원이 증가하고 종료 시 감소하도록 한다.

 

예상 클래스 다이어그램

예상 코드

더보기
class Book:
    def __init__(self, id, title, author, isbn, publisher, publish_year, genre):
        self.__id = id
        self.__title = title
        self.__author = author
        self.__isbn = isbn
        self.__publisher = publisher
        self.__publish_year = publish_year
        self.__genre = genre
 
    def display_info(self):
        print("===========(도서정보)==========")
        print(f"    제목: {self.__title}")
        print(f"    저자: {self.__author}")
        print(f"    ISBN: {self.__isbn}")
        print(f"  출판사: {self.__publisher}")
        print(f"출판년도: {self.__publish_year}")
        print(f"    장르: {self.__genre}")
        print("==============================")
        
 
class PrintedBook(Book):
    def __init__(self, id, title, author, isbn, publisher, publish_year, genre, location, available_copies):
        super().__init__(id, title, author, isbn, publisher, publish_year, genre)
        self.location = location
        self.available_copies = available_copies
 
    def borrow(self):
        if self.available_copies > 0:
            self.available_copies -= 1
            print("일반도서 대출 완료")
        else:
            print("대출 불가")
 
    def return_book(self):
        self.available_copies += 1
        print("일반도서 반납 완료")
 
 
class EBook(Book):
    def __init__(self, id, title, author, isbn, publisher, publish_year, genre, file_format, access_url, concurrent_limit):
        super().__init__(id, title, author, isbn, publisher, publish_year, genre)
        self.file_format = file_format
        self.access_url = access_url
        self.concurrent_limit = concurrent_limit
        self.current_users = 0
 
    def borrow(self):
        if self.current_users < self.concurrent_limit:
            self.current_users += 1
            print("==> 전자도서 이용 시작")
        else:
            print("==> 동시 이용 초과")
 
    def return_book(self):
        if self.current_users > 0:
            self.current_users -= 1
            print("==> 전자도서 이용 종료")
 
 
book1 = PrintedBook(1, "Python", "홍길동", "111", "한빛", 2024, "IT", "A-1", 2)
book2 = EBook(2, "AI", "이몽룡", "222", "길벗", 2025, "IT", "PDF", "www.book.com", 3)
 
book1.display_info()
book1.borrow()
book1.borrow()
book1.borrow()
 
book2.display_info()
book2.borrow()
book2.borrow()
book2.borrow()
book2.borrow()

 

실습. 키오스크 도메인에서 상속관계 클래스 찾기

도서관 시스템의 예상 클래스 다이어그램과 예상 코드를 참조하여 아래 요구사항에서 상속관계의 클래스를 도출하여 클래스 다이어그램을 그리고, 파이썬 코드로 구현하시오. 

카페 키오스크 시스템은 판매하는 모든 메뉴항목에 대해 공통적으로 고유 식별자, 메뉴명, 가격, 판매 상태 정보를 관리하며, 메뉴 목록 조회 시 해당 공통 정보를 출력한다. 메뉴는 종류에 따라 커피(Coffee)와 디저트(Dessert)로 구분되며, 커피류는 카페인 함량과 온도(Hot/Ice) 정보를 추가로 관리하고, 디저트류는 칼로리와 보관 방법 정보를 추가로 관리한다. 모든 메뉴는 판매 상태가 ‘판매중’일 때만 주문이 가능하다.

 

제출1. 클래스 다이어그램

제출 2. 파이썬 코드

  • 1. 제출 1을 파이썬으로 구현한 클래스 코드
    • 정의한 클래스는 클래의 모든 속성을 출력하는 print_all() 매소드를 각각 정의한다. 
  • 2. (위) 1번에서 구현한 클래스 코드의 인스턴스를 각각 만들어서 부모클래스, 자식 클래스들의 print_all() 메소드를 호출하는 코드를 작성한다. 
  • 3. (위) 1번, 2번에서 구현한 클래스에 대해 클래스의 의미에 맞는 메소드 하나를 부모클래스에 추가하고, 자식클래스에서  Method Overriding하는 예를 추가한다. 필요시 super()를 사용한다. 

 

클래스다이어그램 예시

 

코드 예시

더보기
class MenuItem:
    def __init__(self, item_id, name, price, available_stock):
        self._id = item_id
        self._name = name
        self._price = price
        self._available_stock = available_stock

    def order(self, order_qty):
        # 주문전 체크
        if self._available_stock == 0:
            print(f"{self._name}은(는) 품절입니다. 주문할 수 없습니다.")
            return
        elif self._available_stock < order_qty:
            print(f"{self._name}은(는) {self._available_stock}개만 주문 가능합니다. 수량을 줄려주세요.")
            return

        # 주문처리
        self._available_stock -= order_qty
        print(f"{self._name} 주문 완료! 남은 재고: {self._available_stock}")

        if self._available_stock == 0:
            print(f"{self._name}은(는) 품절되었습니다.")

    def print_all(self):
        print()
        print("========== 메뉴 ==========")
        print(f"ID: {self._id}")
        print(f"이름: {self._name}")
        print(f"가격: {self._price}원")
        print(f"재고: {self._available_stock}")


class Coffee(MenuItem):
    def __init__(self, item_id, name, price, caffeine_amount, temperature_type):
        super().__init__(item_id, name, price, available_stock=3)
        self.__affeine_amount = caffeine_amount
        self.__temperature_type = temperature_type

    def print_all(self):
        super().print_all()
        print(f"카페인 함량: {self._caffeine_amount}mg")
        print(f"온도 타입: {self._temperature_type}")


class Dessert(MenuItem):
    def __init__(self, item_id, name, price, calories_kcal, storage_type):
        super().__init__(item_id, name, price, available_stock=3)
        self.__calories_kcal = calories_kcal
        self.__storage_type = storage_type

    def print_all(self):
        super().print_all()
        print(f"칼로리: {self._calories_kcal}kcal")
        print(f"보관 방법: {self._storage_type}")


# 객체 생성
coffee1 = Coffee(item_id=1, name="아메리카노", price=4500, caffeine_amount=150, temperature_type="HOT")
dessert1 = Dessert(item_id=2, name="치즈케이크", price=5500, calories_kcal=320, storage_type="냉장보관")

# 커피주문
# 초기 상태 출력
coffee1.print_all()

# 주문 4번 시도
print("\n[아메리카노 주문]")
coffee1.order(1)
coffee1.order(1)
coffee1.order(2)
coffee1.order(1)   # 품절 후 주문 시도

# 최종 상태 출력
coffee1.print_all()

# 디저트 주문
# 초기 상태 출력
dessert1.print_all()

# 주문 4번 시도
print("\n[아메리카노 주문]")
dessert1.order(1)
dessert1.order(2)
dessert1.order(1)   # 품절 후 주문 시도

# 최종 상태 출력
dessert1.print_all()

예제. Book 클래스 정의하기

아래의 클래스 다이어그램을 보고 파이썬으로 클래스를 정의하고, 클래스의 인스턴스를 2개 이상 만들어 매소드를 호출 하시오.

 

클래스 다이어그램

클래스 다이어그램 클래스 설명

- 클래스의 이름은 Book이다. 
- 클래스 속성은 id, title, .... loanStatus 이다. 
- 클래스 속성은 외부로부터 정보를 보화기 위해 private (-표시) 으로 정의한다. 

- 클래스 매서드는 getter/setter, add(), modify(), ... 등이 있다. 
- 클래스 매서드는 외부에서 호출 가능해야 하므로 public(+표시) 으로 정의한다. 
- getter/setter는 속성값을 접근하는 함수로 데코레이터나 getter/setter함수를 정의한다. 

- add(), modify(), dispose(), search() 함수는 body에 함수 명을 출력하는 기능을 추가한다. 
- add() 함수의 예

    def add (self):
        print(f" add() 입니다.")

 

예상 코드

더보기

1. 데코레이터 @property의 사용

class Book:

    def __init__(self, id, title, author, publisher,
                 publishedYear, genre, ISBN, loanStatus):

        self.__id = id
        self.__title = title
        self.__author = author
        self.__publisher = publisher
        self.__publishedYear = publishedYear
        self.__genre = genre
        self.__ISBN = ISBN
        self.__loanStatus = loanStatus

    # =====================
    # Getter / Setter 정의
    # =====================
    @property
    def id(self):
        return self.__id

    @id.setter
    def id(self, value):
        self.__id = value


    @property
    def title(self):
        return self.__title

    @title.setter
    def title(self, value):
        self.__title = value


    @property
    def author(self):
        return self.__author

    @author.setter
    def author(self, value):
        self.__author = value


    @property
    def publisher(self):
        return self.__publisher

    @publisher.setter
    def publisher(self, value):
        self.__publisher = value


    # setter에서 값 체크하는 예 -exception 발생예
    @property
    def publishedYear(self):
        return self.__publishedYear

    @publishedYear.setter
    def publishedYear(self, value):

        if value < 0:
            raise ValueError("출판 연도는 음수일 수 없습니다.")

        self.__publishedYear = value


    @property
    def genre(self):
        return self.__genre

    @genre.setter
    def genre(self, value):
        self.__genre = value


    @property
    def ISBN(self):
        return self.__ISBN

    @ISBN.setter
    def ISBN(self, value):
        self.__ISBN = value


    @property
    def loanStatus(self):
        return self.__loanStatus

    @loanStatus.setter
    def loanStatus(self, value):
        self.__loanStatus = value


    # =====================
    # Public Method 정의
    # =====================

    def add(self):
        print("add() 입니다.")

    def modify(self):
        print("modify() 입니다.")

    def dispose(self):
        print("dispose() 입니다.")

    def search(self):
        print("search() 입니다.")



# =====================
# 인스턴스 생성
# =====================

book1 = Book(1, "Python OOP", "Kim", "Hanbit",
             2023, "IT", "123456789", "AVAILABLE")

book2 = Book(2, "Data Structure", "Lee", "InfoPub",
             2022, "CS", "987654321", "BORROWED")


# =====================
# getter 사용
# =====================

print("book1 제목:", book1.title)
print("book2 상태:", book2.loanStatus)


# =====================
# setter 사용 (정상)
# =====================

book1.loanStatus = "BORROWED"
print("book1 변경된 상태:", book1.loanStatus)


# =====================
# exception 테스트
# =====================

try:
    book2.publishedYear = -2024
    print("변경 성공:", book2.publishedYear)

except ValueError as e:
    print("update error:", e)


# =====================
# public 메서드 호출
# =====================

book1.add()
book1.modify()
book2.dispose()
book2.search()

 

2. 일반적인 getter/setter 함수의 사용

class Book:

    def __init__(self, id, title, author, publisher,
                 publishedYear, genre, ISBN, loanStatus):

        self.__id = id
        self.__title = title
        self.__author = author
        self.__publisher = publisher
        self.__publishedYear = publishedYear
        self.__genre = genre
        self.__ISBN = ISBN
        self.__loanStatus = loanStatus


    # =====================
    # Getter / Setter 함수
    # =====================

    def get_id(self):
        return self.__id

    def set_id(self, value):
        self.__id = value


    def get_title(self):
        return self.__title

    def set_title(self, value):
        self.__title = value


    def get_author(self):
        return self.__author

    def set_author(self, value):
        self.__author = value


    def get_publisher(self):
        return self.__publisher

    def set_publisher(self, value):
        self.__publisher = value


    def get_publishedYear(self):
        return self.__publishedYear


    # 연도 입력값 체크, 함수의 return값 적용
    def set_publishedYear(self, value):

        if value < 0:
            print("출판 연도 오류")
            return False

        self.__publishedYear = value
        return True


    def get_genre(self):
        return self.__genre

    def set_genre(self, value):
        self.__genre = value


    def get_ISBN(self):
        return self.__ISBN

    def set_ISBN(self, value):
        self.__ISBN = value


    def get_loanStatus(self):
        return self.__loanStatus

    def set_loanStatus(self, value):
        self.__loanStatus = value


    # =====================
    # Public Method 정의
    # =====================

    def add(self):
        print("add() 입니다.")

    def modify(self):
        print("modify() 입니다.")

    def dispose(self):
        print("dispose() 입니다.")

    def search(self):
        print("search() 입니다.")



# =====================
# 인스턴스 생성
# =====================

book1 = Book(1, "Python OOP", "Kim", "Hanbit",
             2023, "IT", "123456789", "AVAILABLE")

book2 = Book(2, "Data Structure", "Lee", "InfoPub",
             2022, "CS", "987654321", "BORROWED")


# =====================
# getter 사용
# =====================

print("book1 제목:", book1.get_title())
print("book2 상태:", book2.get_loanStatus())


# =====================
# setter 사용
# =====================

book1.set_loanStatus("BORROWED")
print("book1 변경된 상태:", book1.get_loanStatus())


# =====================
# 오류 처리 포함 setter
# =====================

result = book2.set_publishedYear(-2024)

if result == False:
    print("update error")
else:
    print("변경 성공:", book2.get_publishedYear())


# =====================
# public 메서드 호출
# =====================

book1.add()
book1.modify()
book2.dispose()
book2.search()

 

실습1. Student 클래스 정의하기

Student Class 파이썬으로 구현하시오.
  • 속성을 private으로 정의 하시오.
  • 각 속성에 대해 외부에서 사용할 수 있도록 @property를 사용하여 getter를 정의 하시오.
  • getter에 print문을 넣고, 인스턴스 생성 후 이를 호출하시오.

클래스 다이어그램

 

 

실습2. 키오스크 시스템의 Coffee 클래스 정의하기

Coffee Class파이썬으로 구현하시오.
  • 속성을 private으로 정의 하시오.
  • 각 속성에 대해 외부에서 사용할 수 있도록 @property를 사용하여 getter를 정의 하시오. (코드1)
  • 각 속성에 대해 외부에서 사용할 수 있도록 getter/setter 함수를 사용하여 정의 하시오. (코드2)
  • getter에 print문을 넣고, 인스턴스 생성 후 이를 호출하시오.

클래스 다이어그램

1. 정수형과 실수형 자료형의 크기를 확인한다.

아래의 코드는 sizeof 연산자를 이용하여 정수형, 실수형 자료형의 size를 출력한다. 

 

1.1 코드를 분석하여 변수의 size와 자료형의 size의 출력을 비교하시오. 

#include <stdio.h>
 
int main(void)
{
    char ch = 9;
    int inum = 1052;
    double dnum = 3.1415;
 
    printf("변수 ch의 크기:%zu \n", sizeof(ch));
    printf("변수 inum의 크기:%zu \n", sizeof(inum));
    printf("변수 dnum의 크기:%zu \n", sizeof(dnum));
 
    printf("char의 크기:%zu \n", sizeof(char));
    printf("short의 크기:%zu \n", sizeof(short));
    printf("int의 크기:%zu \n", sizeof(int));
    printf("long의 크기:%zu \n", sizeof(long));
    printf("long long의 크기:%zu \n", sizeof(long long));
    
    printf("float의 크기:%zu \n", sizeof(float));
    printf("double의 크기:%zu \n", sizeof(double));
    printf("long double의 크기:%zu \n", sizeof(long double));
    
    return 0;
}

 

1.2 sizeof 연산자를 통해 알게 된 size의 의미는 무엇인지 확인하시오. 

더보기
더보기

sizeof 연산자는 해당 자료형 또는 변수, 값 자체가 메모리에서 차지하는 바이트(byte) 크기이다. 예를 들어 sizeof(int) 이면 4를 리턴하고 이는 int 형 변수를 저장하면 4byte를 메모리에서 할당 받음을 의미한다. 

 

1.3.  정수형 (signed, unsighned ), 실수형 데이타를 변수 선언하고 출력하는 코드이다. 코드를 읽으며 자료형과 변수 선언을 익히시오. 

#include <stdio.h>

int main(void)
{
    /* 정수형 */
    char c = 'A';
    signed char sc = -10;
    unsigned char uc = 250;

    short s = -1000;
    unsigned short us = 1000;

    int i = -20000;
    unsigned int ui = 20000;

    long l = -300000;
    unsigned long ul = 300000;

    long long ll = -4000000000;
    unsigned long long ull = 4000000000ULL;

    /* 실수형 */
    float f = 3.14f;
    double d = 3.141592;
    long double ld = 3.141592653589793L;


    printf("char: %c\n", c);
    printf("signed char: %d\n", sc);
    printf("unsigned char: %u\n", uc);

    printf("short: %d\n", s);
    printf("unsigned short: %u\n", us);

    printf("int: %d\n", i);
    printf("unsigned int: %u\n", ui);

    printf("long: %ld\n", l);
    printf("unsigned long: %lu\n", ul);

    printf("long long: %lld\n", ll);
    printf("unsigned long long: %llu\n", ull);

    printf("float: %f\n", f);
    printf("double: %lf\n", d);
    printf("long double: %Lf\n", ld);

    return 0;
}

 

 

2. 문자형 자료형과 데이타를 이해한다.

2.1 아래 코드를 분석하여 출력값을 예상하고, 실행하여 예상값과 비교하시오. 

#include <stdio.h>

int main(void)
{
    char ch1 = 'A', ch2 = 65;
    int ch3 = 'a', ch4 = 97;

    printf("%c %d \n", ch1, ch1);
    printf("%c %d \n", ch2, ch2);
    printf("%c %d \n", ch3, ch3);
    printf("%c %d \n", ch4, ch4);

    return 0;
}

 

2.2. 2.1 코드를 수정하여 'A' to 'E', 'a' to 'e' 의 문자값과 ascii code값을 출력하는 코드를 작성하시오. 

예상 출력

***********************
A to Z
***********************
A 65
B 66
C 67
D 68
E 69

***********************
a to z
***********************
a 97
b 98
c 99
d 100
e 101

 

 

예상 코드

더보기
더보기
#include <stdio.h>

int main(void)
{
    printf("***********************\n");
    printf("A to Z\n");
    printf("***********************\n");
    
    printf("%c %d\n", 'A', 'A');
    printf("%c %d\n", 'B', 'B');
    printf("%c %d\n", 'C', 'C');
    printf("%c %d\n", 'D', 'D');
    printf("%c %d\n", 'E', 'E');

    printf("\n***********************\n");
    printf("a to z\n");
    printf("***********************\n");
    
    printf("%c %d\n", 'a', 'a');
    printf("%c %d\n", 'b', 'b');
    printf("%c %d\n", 'c', 'c');
    printf("%c %d\n", 'd', 'd');
    printf("%c %d\n", 'e', 'e');

    return 0;
}

 

3. 리터럴 상수와 const 상수를 이해한다.

3.1 아래 코드를 이해하고, 정수, 실수, 문자 리터럴의 size값을 예상하여 실제 출력값과 비교하시오. 

#include <stdio.h>

int main(void)
{
    printf("Size of literal 7: %zu Byte\n", sizeof(7));
    printf("Size of literal 7.14: %zu Byte\n", sizeof(7.14));
    printf("Size of literal A: %zu Byte\n", sizeof('A'));

    return 0;
}

 

3.1-a 리터럴 정수, 실수, 문자의 size는 각각 몇 byte로 메모리에 할당되는가? 왜 그런가?

더보기
더보기
Size of literal 7: 4 Byte
Size of literal 7.14: 8 Byte
Size of literal A: 4 Byte

 

정수와 문자는 int 형으로 실수는 double 형으로 할당되기 때문이다.  

 

3.2 아래 코드는 에러가 있는 코드이다. 에러를 수정하여 출력형식에 맞게 출력하는 코드로 수정하시오.

#include <stdio.h>

int main(void)
{  
    double radius = 5.0;
    double area;

    area = PI * radius * radius;

    printf("반지름: %f\n", radius);
    printf("원의 넓이: %f\n", area);

    return 0;
}

 

출력형식

반지름: 5.000000
원의 넓이: 78.539750

 

예상코드

더보기
더보기
#include <stdio.h>

int main(void)
{  
    const double PI = 3.14159;  // 추가코드 const 상수 PI 
    double radius = 5.0;
    double area;

    area = PI * radius * radius;

    printf("반지름: %f\n", radius);
    printf("원의 넓이: %f\n", area);

    return 0;
}

 

5. 형변환(Type Casting)의 자동 형변환과 명시적 형변환을 이해한다. 

 

5.1부터 5.3의 각 코드를 이해하여 결과를 예상하고, 실행시켜 결과값과 비교하시오. 

5.1 [자동형변환]  Case 1. 대입 연산자에서 오른편의 자료형이 왼편에 맞지 않을 때 자동 형변환 

오른쪽 값의 자료형이 왼쪽 변수의 자료형과 다를 때,  오른쪽 값이 왼쪽 변수의 자료형으로 자동 변환됩니다.

#include <stdio.h>

int main(void)
{
    double score = 95.8;
    int result;

    result = score;   // double → int 자동 형변환

    printf("score  = %f\n", score);
    printf("result = %d\n", result);

    return 0;
}

 

예상 결과

  • score는 double형이고 result는 int형입니다.
  • result = score; 에서 오른쪽의 double 값이 왼쪽 int형에 맞게 자동 변환됩니다.
  • 이때 소수 부분은 버려져서 95가 저장됩니다.
score  = 95.800000
result = 95

 

5.2 [자동형변환] Case 2. 정수 승격 (Integral Promotion)

char, short 같은 작은 정수형은 연산할 때 int로 자동 변환됩니다.

#include <stdio.h>

int main(void)
{
    char a = 100;
    char b = 30;

    int c = a + b;   // char → int 변환 후 연산

    printf("a + b = %d\n", c);

    return 0;
}

 

예상결과

  • num1과 num2는 둘 다 char형입니다.
  • 하지만 num1 + num2 연산을 할 때는 두 값이 먼저 int로 자동 변환됩니다.
  • 즉 실제 연산은 int + int로 이루어집니다.
  • 이것을 정수의 승격이라고 합니다.
num1 = 100
num2 = 30
sum  = 130

 

5.3 [자동형변환]  Case 3. 피연산자의 자료형 불일치로 발생하는 자동 형변환

연산하는 두 피연산자의 자료형이 다르면, 더 큰 범위의 자료형에 맞추어 자동 형변환이 일어납니다.

#include <stdio.h>

int main(void)
{
    int count = 3;
    double price = 2.5;
    double total;

    total = count * price;   // count가 int → double로 자동 형변환된 뒤 연산됨

    printf("count = %d\n", count);
    printf("price = %f\n", price);
    printf("total = %f\n", total);

    return 0;
}

 

 

예상결과

  • count는 int, price는 double입니다.
  • 자료형이 다르므로 연산 전에 count가 double로 자동 변환됩니다.
  • 따라서 실제 연산은 double * double로 수행됩니다.
  • 결과도 double형 값으로 저장됩니다.
count = 3
price = 2.500000
total = 7.500000

 

 

5.4 명시적 형변환

아래 코드의 결과값은 0.000000 으로 정수형 나눗셈이 실행된다. 명시적 형변환을 코드에 적용하여 실수형 나눗셈이 실행되도록 코드를 수정하시오. 

#include <stdio.h>

int main(void)
{
    int num1 = 3, num2 = 4;
    double divResult;

    divResult = num1 / num2;

    printf("나눗셈 결과: %f\n", divResult);

    return 0;
}

 

수정후 예상 결과 

나눗셈 결과: 0.750000

 

예상 코드

더보기
더보기
#include <stdio.h>

int main(void)
{
    int num1 = 3, num2 = 4;
    double divResult;

    divResult = (double) num1 / num2;

    printf("나눗셈 결과: %f\n", divResult);

    return 0;
}

리뷰 1. 도서관 도메인에서 클래스 찾기

아래의 요구사항을 보고 서로 관련된 데이타와 기능을 encapsulation하여 Entity 클래스를 도출하시오.

요구사항

도서관 시스템 요구사항

OO 대학교 도서관 시스템은 학생들이 도서관 자원을 효과적으로 이용하기 위해 시스템을 이용하는 사용자 정보도서관에서 소장하는 도서정보기본정보로 관리하고, 이를 기반으로 도서 검색, 대출, 반납, 연체 관리 기능을 제공한다. 각 기능의 상세 설명은 아래와 같다.

도서관에서 소장하는 도서 정보는 시스템에서 사용하는 도서의 고유 식별자를 포함하여 제목, 저자, ISBN, 출판사, 출판연도, 장르, 대출 가능 여부 등의 정보를 관리해야 한다. 새로운 도서는 시스템에 등록하고, 등록된 도서는 수정, 삭제와 같은 기능을 지원한다. 또한 등록된 도서는 키워드, 제목, 저자Q, 장르를 조건으로 검색할 수 있으며, 검색된 도서는 제목, 저자, 출판사, 출판연도, 장르 정보를 보여준다.

도서관 시스템을 사용하는 학생은 학생 기본정보를 시스템에서 관리하는데, 학번, 이름, 이메일, 전화번호, 비밀번호, 전공, 선호장르를 저장한다. 학생 정보는 신규입력, 수정, 삭제(탈퇴)가 가능하며 비밀번호 재설정은 현재 비밀번호를 확인후 수정이 가능하다.

도서를 대출하기 위해, 사서는 사용자로부터 사용자 ID와 대출하려는 도서 정보를 전달받아 시스템에 사용자 ID와 도서 ID입력하면, 시스템은 누가(사용자ID), 어떤 도서를(도서ID), 언제 대출했는지를 저장한다. 대출 기한은 기본 2(14) 이며, 최대 5권 대출이 가능하다. 연체중인 사용자는 대출을 할 수 없으며, 연체중인 도서 반납한 후 대출이 가능하다. 

도서관 시스템은 매일 아침 연체중인 사용자들에게 연체상태 메일을 보낸다.  사용자가 대출한 도서를 사서에게 반납하면, 사서는 도서 바코드를 찍어 해당 사용자의 대출 상태를 반납완료 상태로 변경한다.

 

객체 찾기

[Hint]

  • 도서관 시스템에서 관리해야 할 정보의 단위는 무엇일까?
  • 정보의 단위는 도서명, 학생명, 전화번호 수준의 최소 데이타 단위인가? -> 최소단위의 묶음으로
  • 정보의 단위가 시스템, 도서관리, 대출 관리 등 사용자 인터페이스와 업무 로직등이 묶여있는 단위인가? ->  저장할 데이터 단위로 분할
더보기
더보기

 

클래스명 Book Student BookLoan
속성 id
title
author
publisher
publishedYear
genre
ISBN
loanStatus
id
name
phone
email
password
major
preferredGenre
id
book
user
loan_date
due_date
return_date
status
메소드 add()
modify()
delete()
search ()
add()
modify()
changePassword()
deleteAccount()
borrowBook()
returnBook ()
extendLoan()
checkStatus()

 

객체 모델링 하기(클래스 다이어그램)

도출한 클래스(속성/메소드를 포함)를 ppt나 도구 (https://app.diagrams.net/) 를 이용하여 각각 작성하시오. 

 

클래스 다이어그램 보고 클래스 코드 만들기

UML 클래스 다이어그램으로 모델링한 클래스를 파이썬 클래스로 구현하시오. 

 

더보기
더보기
class Book:
    def __init__(self, id, title, author, publisher, published_year, genre, isbn):
        self.id = id
        self.title = title
        self.author = author
        self.publisher = publisher
        self.published_year = published_year
        self.genre = genre
        self.isbn = isbn
        self.loan_status = "AVAILABLE"

    def add(self):
        print(f"도서 '{self.title}' 추가 완료")

    def modify(self, new_title):
        self.title = new_title
        print("도서 정보 수정 완료")

    def delete(self):
        print("도서 삭제 완료")

    def search(self):
        print(f"도서 검색 결과: {self.title}, {self.author}")

 

class Student:
    def __init__(self, id, name, phone, email, password, major, preferred_genre):
        self.id = id
        self.name = name
        self.phone = phone
        self.email = email
        self.password = password
        self.major = major
        self.preferred_genre = preferred_genre

    def add(self):
        print(f"학생 '{self.name}' 등록 완료")

    def modify(self, new_phone):
        self.phone = new_phone
        print("학생 정보 수정 완료")

    def change_password(self, new_password):
        self.password = new_password
        print("비밀번호 변경 완료")

    def delete_account(self):
        print("계정 삭제 완료")

 

from datetime import date, timedelta

class BookLoan:
    def __init__(self, id, book, user):
        self.id = id
        self.book = book
        self.user = user
        self.loan_date = None
        self.due_date = None
        self.return_date = None
        self.status = "NONE"

    def borrow_book(self):
        if self.book.loan_status == "AVAILABLE":
            self.loan_date = date.today()
            self.due_date = self.loan_date + timedelta(days=14)
            self.status = "BORROWED"
            self.book.loan_status = "BORROWED"
            print(f"{self.user.name}님이 '{self.book.title}' 대출 완료")
        else:
            print("이미 대출 중인 도서입니다.")

    def return_book(self):
        if self.status == "BORROWED":
            self.return_date = date.today()
            self.status = "RETURNED"
            self.book.loan_status = "AVAILABLE"
            print(f"{self.book.title} 반납 완료")
        else:
            print("대출 상태가 아닙니다.")

    def extend_loan(self):
        if self.status == "BORROWED":
            self.due_date += timedelta(days=7)
            print("대출 기간 연장 완료")
        else:
            print("연장 불가 상태입니다.")

    def check_status(self):
        print(f"현재 상태: {self.status}")

 

실습 1. 키오스크 도메인에서 클래스 찾기

요구사항

OO 카페 키오스크 시스템은 고객이 카페 메뉴를 빠르고 편리하게 주문할 수 있도록 지원하는 시스템이다. 이 시스템은 카페에서 판매하는 메뉴 정보를 기본 데이터로 관리하며, 이를 기반으로 메뉴 조회 및 검색, 메뉴 선택, 장바구니 관리, 주문 생성과 주문 내역 저장 기능을 제공한다.

시스템은 각 메뉴에 대해 고유 식별자(Menu ID)를 포함하여 메뉴명, 카테고리(커피, , 음료, 디저트 등), 가격, 메뉴 설명, 판매 상태(판매중, 품절) 등의 정보를 관리해야 한다. 사용자는 키오스크 화면에서 카테고리별로 메뉴 목록을 조회할 수 있으며, 각 메뉴에 대해 메뉴명, 가격, 판매 상태가 표시되어야 한다. 품절 상태의 메뉴는 화면에 표시되더라도 선택이 불가능해야 한다. 또한 사용자는 키워드나 메뉴명, 카테고리 조건을 이용하여 메뉴를 검색할 수 있어야 하며, 검색 결과로는 메뉴명, 가격, 카테고리 정보가 제공되어야 한다.

사용자가 주문을 진행하기 위해 메뉴를 선택하면
, 시스템은 해당 메뉴의 상세 정보를 보여주고 사용자는 수량을 선택할 수 있어야 한다. 선택한 메뉴는 장바구니에 담을 수 있으며, 사용자는 여러 개의 메뉴를 장바구니에 담아 한 번에 주문할 수 있어야 한다. 장바구니에서는 각 항목의 수량을 변경하거나 특정 항목을 삭제할 수 있어야 한다.

사용자가 장바구니 내역을 확인한 뒤 주문을 확정하면, 시스템은 주문을 생성하고 관련 정보를 저장해야 한다. 저장해야 할 정보에는 주문번호(고유 식별자), 주문 항목 목록(메뉴 ID, 메뉴명, 수량, 항목별 금액), 총 주문 금액, 주문 생성 시간(주문 시각), 주문 상태(: 주문접수)가 포함된다.

주문이 정상적으로 생성되면 키오스크는 사용자에게 주문번호와 함께 주문 내역(메뉴명, 수량, 총액)을 화면에 표시해야 한다.

 

1.1 객체 찾기

요구사항을 읽고 객체와 객체의 속성(=data, attributes)과 기능(=operation, method, 함수)을 찾아 적으시오. 

 

1.2 객체 그리기(클래스 다이어그램)

1.1에서 찾은 객체를 https://app.diagrams.net/ (또는 UML 클래스 형태의 손그림, or ppt)로 클래스 다이어그램을 작성하시오. 

 

1.3 클래스 다이어그램 보고 클래스 코드 만들기

1.2에서 작성한 클래스 다이어그램을 기반으로 파이썬 코드를 작성하고, 인스턴스를 하나 생성하여 기능하나를 실행하는 코드를 추가하시오. 

객체지향 프로그래밍에서 클래스를 정의하는 중요하게 적용되는 특징 4가지가 있다. 캡슐화(Encapsulation), 정보은닉(Information Hiding), 상속(Inheritance), 그리고 다형성(Polymorphism) 이다.

 

이번 장에서는 두번째 특징인 정보은닉 개념과 클래스에서 정보은닉이 필요한 의미를 이해한다. 또한, 캡슐화된 클래스에서 정보를 보호하는 코드를 파이썬에서 구현한다. 

정보은닉(Information Hiding) 개념

정보은닉(Information Hiding)은 정보 보호라고도 번역되어 사용되는데, 클래스가 가지고 있는 정보 즉, 속성데이타나 메서드 구현의 세부 정보를 클래스 외부에서 접근하지 못하도록 숨긴다는 의미입니다. 여기서 접근하지 못한다는 의미는 속성값을 클래스 외부 코드에서 임의로 수정하지 못한다는 의미입니다.

 

예를 들어 고양이 클래스의 고양이 품종 속성(cat_breed)를 정보은닉을 하지 않은 경우와 한 경우를 비교해보면 아래의 두가지 case와 같습니다. 

 

 

 

 

Case 1)을 코드로 구현하면 아래와 같은데, Cat 클래스의 인스턴스 Nabi, Momo, Coco는 cat_breed를 "Domestic Short Hair", "Korean Short Hair" 그리고 줄여서 만든 "KoShort"을 규칙에 상관없이 모두 쓸 수 있습니다. 이는 Cat 클래스의 cat_breed의 데이타 규칙이 무너짐을 의미합니다. 

class Cat:
    def __init__(self, name):
        self.name = name
        self.cat_breed = None 
        
# 공개(public) 속성 (직접 접근 가능)
my_cat1 = Cat("Nabi")
my_cat2 = Cat("Momo")
my_cat3 = Cat("Coco")

# 속성에 직접 접근하여 값 변경
my_cat1.cat_breed = "Domestic Short Hair"
my_cat2.cat_breed = "Korean Short Hair"
my_cat3.cat_breed = "KoShort"

# 검증 없이 그대로 저장됨
print("my_cat1 품종:", my_cat1.cat_breed)
print("my_cat2 품종:", my_cat2.cat_breed)
print("my_cat3 품종:", my_cat3.cat_breed)

 

Case 2)를 코드로 구현하면 아래와 같습니다. _allowed_breeds라는 클래스 수준의 변수에 품종명을 저장하고, cat_breed 속성을 update를 하기 위한 setter를 set_cat_breed() 로 정의합니다. Cat 클래스 외부에서 Cat 클래스의 인스턴스 Nabi, Momo, Coco를 만들어 각 인스턴스의 품종을 수정하려면, set_cat_breed( ) 함수 (setter)를 호출해 매개변수를 통해 품종을 입력합니다.

 

그러면,  set_cat_breed( ) 함수에서 입력된 품종값(breed_type)dl _allowed_breeds에 정의된 품종인지를 확인하여 update처리 하거나 fail을 리턴합니다. 이러한 방법으로 Cat 클래스의 품종 속성은 그 데이타가 일관되게 관리될 수 있습니다.

class Cat:
    # 허용 가능한 품종 목록 (클래스 상수처럼 사용)
    _allowed_breeds = ["Domestic Short Hair", "Persian", "Siamese", "Maine Coon"]
    
    def __init__(self, name):
        self.name = name
        self._cat_breed = None # 직접 접근 방지를 위해 _ 사용
    
    def set_cat_breed(self, breed_type):
        # 품종을 규칙에 맞게 설정
        if breed_type in Cat._allowed_breeds:
            self._cat_breed = breed_type
            print(f"[SUCCESS] {self.name} 품종이 '{breed_type}'로 설정되었습니다.")
            return True
        else:
            print(f"[FAIL] '{breed_type}'는 허용되지 않는 품종입니다.")
            return False

my_cat1 = Cat("Nabi")
my_cat2 = Cat("Momo")
my_cat3 = Cat("Coco")

my_cat1.set_cat_breed("Domestic Short Hair")
my_cat2.set_cat_breed("Korean Short Hair")  # 실패 케이스
my_cat3.set_cat_breed("KoShort")            # 실패 케이스

print("my_cat1 품종:", my_cat1._cat_breed)
print("my_cat2 품종:", my_cat2._cat_breed)
print("my_cat3 품종:", my_cat3._cat_breed)

 

 

접근 지정자 (Access Modifier) 

객체지향 설계에서는 클래스의 정보 보호와 무결성 유지를 위해 접근 지정자(Access Modifier)를 정의고, 접근 범위를 명시함으로써 데이터의 성격과 설계 의도에 따라 공개 정보와 내부 보호 정보를 구분합니다. 

 

접근 지정자는 크게 public, protected, private으로 정의하며 특징은 아래와 같습니다.

종류 설명
public 어디서든 접근 가능  (클래스 내부/외부)
protected 해당 클래스서브클래스에서 접근 가능
private 해당 클래스 내부에서 접근 가능

 

객체지향 언어에서는 일반적으로 접근지정자를 제공하여 접근 지정자의 종류에 따라 프로그래밍 언어에서 규칙에 맞지 않게 사용하는 경우 에러메시지를 출력하여 제재를 합니다. 하지만 파이썬의 경우는 이러한 제제가 없고, 개발자에게 자유도를 더 줍니다. 

 

아래는 객체지향 프로그래밍 언어의 종류에 따른 접근지정자의 사용 예 입니다. C++과 JAVA에서는 private, public 키워드를 정의하고 이에 맞춰 속성과 메서드를 정의한 후 private을 클래스 외부에서 사용한 경우 컴파일러에서 에러메시지를 출력합니다. 

하지만 파이썬의 경우 강제적인 제재가 없습니다. 

 

 

 

 

파이썬에서의 접근 지정자 사용

public, protected, private으로 정의된 접근지정자의 종류에 따라 파이썬에서 코드를 작성하는 언어적인 제약은 없는 (오류체크를 하지 않는) 관례가 있다. 

종류 명명 방법 설명
public 일반 이름 name 어디서든 접근 가능 (클래스 내부/외부)
protected _(언더스코어 하나) _name 같은 클래스와 서브클래스에서 접근 가능하다고 관례적으로 표현
private __ (언더스코어 두개) __name - 해당 클래스 내부에서 접근 가능
- 내부적으로 이름이 맹글링되어 cat.__name으로 사용 할 수 없다. 맹글링 규칙은 _클래스명+속성명으로 _Cat__name을 사용하면 접근할 수 있다.  

 

파이썬에서 private의 경우 __(double underscore or dunder)를 사용할 수 있으나, 속성명을 직접 접근하기 보다 아래와 같이 함수를 사용하여 접근하기를 권장한다. 

 

방법 1. @property getter/setter 정의 ->  파이썬 스타일(권장)

@property는 파이썬에서 제공하는 데코레이터(기존 함수(또는 메서드)에 새로운 기능을 추가하거나 동작 방식을 변경하는 기능을 제공하는 문법) 중 하나로 메서드를 속성(attribute)처럼 사용할 수 있도록 한다. 아래 코드의 예에서 def name(self)는 name이라는 이름의 메서드를 정의해서 클래스의 __name 속성을 리턴하는 메서드 인데 @property라는 데코레이터로 선언하여 실제 코드에서는 my_cat.name으로 속성명 처럼 사용할 수 있다. 

방법 2. getter/setter를 직접 정의 -> 전통적인 방식(it’s ok)
두번째 방법은 일반적인 객체지향에서 사용하는 속성값을 return 하거나 속성정보를 수정하기 위한 getter/setter 함수를 정의하는 것이다. 아래 코드의 def get_name() 함수는 name값을 리턴하도록 구현되어 있다. getter의 한 종류가 된다. 
방법 3. 맹글링 된 이름 직접 사용 ->  추천X
방법 3은 맹글링 된 이름을 직접 사용하는 방법으로 데이타 보호를 위해서는 추천하지 않는다.

 

class Cat:
    def __init__(self, name):
        self.__name = name  # private (name mangling)

    @property              
    def name(self):          # 방법 1. @property로 getter/setter 정의 ->  파이썬 스타일(권장)
        return self.__name
    def get_name(self):     # 방법 2. getter/setter를 직접 정의 -> 전통적인 방식(it’s ok)
        return self.__name

# 객체 생성
my_cat = Cat(name="Cat1")

# __name 접근
print(f"case 1. @property 사용: my_cat.name -> {my_cat.name}")
print(f"case 2. getter method 사용: my_cat.get_name() -> {my_cat.get_name()}")
print(f"case 3. Mangled name 사용: my_cat._Cat__name -> {my_cat._Cat__name}")  #방법 3. 맹글링 된 이름 직접 사용 ->  추천X

 

[참고] 데코레이터, getter/setter

방법 1과 2의 get/set을 위한 방법을 같이 정리하면 아래와 같다. 

 
데코레이터
getter/setter
설명
- 파이썬에서 정의
- @property
- @
attributes_name.setter
- 객체지향의 일반적인 언어에서 사용하는 용어
- 속성값을 return하는 메소드를 getter
- 속성값을 update하는 메소드를 setter
예제코드

   

 

Book 클래스에서의 예 

클래스의 캡슐화 과정과 정보은닉을 통한 속성 정의 과정을 아래와 같이 표현할 수 있습니다. 도서관 시스템에서의 요구사항을 통해 Book 클래스라 모델수준에서 도출이 되고, 속성과 메소드가 정의가 되면 이를 파이썬 코드로 변경 할 수 있습니다. 

 

이때, 객체지향에서는 객체의 속성 정보를 private으로 정의하고 클래스 내부에서 관리하며 외부에서는 메소드를 통해 접근하도록 정의합니다. 이를 위해 파이썬 코드에서는 @property 데코레이터로 정의하여 속성을 접근합니다. 

 

 

 

객체지향 프로그래밍에서 클래스를 정의하는 중요하게 적용되는 특징 4가지가 있다. 캡슐화(Encapsulation), 정보은닉(Information Hiding), 상속(Inheritance), 그리고 다형성(Polymorphism) 이다.

 

이번 장에서는 세번째 특징인 상속 개념과 상속을 기반으로 메소드 재정의(Method Overriding), 다중 상속, 추상클래스와 인터페이스의 확장 개념을 이해한다.

상속(Inheritance)의 개념

객체지향에서의 상속은 일반적으로 아래와 같은 정의로 설명이 된다. 
상속 정의 1. Inheritance is a mechanism that permits a class to acquire the properties and behavior of another class[1]. 즉, ­상속이란 한 클래스가 다른 클래스의 속성과 동작을 가질 수 있도록 하는 메커니즘으로 정의한다. 지금까지 클래스 하나에 대한 특징을 봤다면, 상속은 두개의 클래스간의 관계를 설명하는데 하나의 클래스의 속성과 동작을 다른 클래스에서 재사용 하는 관계를 의미한다. 
상속 정의 2. Inheritance is a relationship that defines one class in terms of another. It is the mechanism that permits the incorporation of the structure and behavior of a parent class into a child class.[2] 이 정의에서 ­상속은 한 클래스를 다른 클래스를 기반으로 정의하는 관계이며, 부모 클래스의 구조와 동작을 자식 클래스에 통합할 수 있게 하는 메커니즘으로 설명된다. 정의 1에서 두 클래스 간의 재상용 관계를 얘기했다면, 정의 2에서는 두 클래스 간의 의미를 parent class와 child 클래스의 관계로 상세화하여 Parent 클래스의 속성과 기능을 자식 클래스에서 재사용 하는 것으로 이해할 수 있다. 
상속 정의 3. Inheritance is the ability for a class to define a generalization-specialization relationship, by reusing and refining the features of a parent class.[3]  상속은 부모 클래스의 기능을 재사용하고 다듬으면서 일반화-특수화 관계를 정의하게 한다. 라고 설명한다. 정의 2에서 Parent 클래스와 Child 클래스를 정의 했다면 정의 3에서는 Parent-Child 클래스의 관계가 Parent 클래스는 Child 클래스보다 일반화된 클래스, Child 클래스는 Parent 클래스에서 특수화 된 클래스로 설명한다. 
이 정의들을 정리하면, 
  1. Child class는 Parent class의 속성과 메서드를 가진다.
  2. Parent class와 Child class의 관계는 일반화(generalization) – 특수화(specialization)의 관계로 아래와 같이 설명 할 수 있다. 
    • ­Child class is a Parent class.
    • ­Parent class의 속성과 메서드를 child에 맞게 재정의할 수 있다.

다음은 정의에서 설명한 상속관계를 UML의 클래스 다이어그램으로 나타낸 예 이다. 일반적으로 포유류 중에 고양이, 개, 원숭이... 등 다양한 종류가 있다. 여기서 포유류와 고양이의 관계를 생각하면, 포유류에서 고양이로 갈 수록 특수한 형태, Specialized 된 형태  중 한 종류가 Cat 이다. 그리고, 고양이, 개, 원숭이 등 다양한 종류를 한 단귀 상위로 추상화 하면 포유류 라는 개념이 생긴다. 이를 표현한 관계가 아래 그림의 오른쪽 클래스 다이어그램의 관계이다. 

 

 

[참고] 클래스 다이어그램 읽기

UML의 클래스 다이어그램은 7주차에 객체지향 설계를 학습하면서 조금더 자세히 정리할 예정이다. 여기에서는 지금까지 이해한 개념을 표현하는 정도만 UML 클래스 다이어그램의 표현을 정리합니다. 

 

1. 클래스 표현의 종류 - 캡슐화 과정을 통해 도출된 클래스는 아래와 같이 최소화된 형태부터 상세한 정보를 표현하는 확장된 형태로 표현할 수 있다. 핵심 정보는 클래스 명, 속성명, 메소드명, 속성과 메소드의 접근지정자 형태, type, 메소드의 입력 파라메터를 표현할 수 있다. 

 

2. 상속관계의 표현 - 클래스 간의 관계 중 상속관계는 속이 비어있는 삼각형에 직선으로 관계를 표시한다. 아래 그림의 가운데와 우측을 보면, Mammal 클래스와 Cat 클래스는 상속관계로 정의되는데 삼각형이 가리키는 방향이 Parent 클래스 직선에 연결된 클래스가 Child 클래스이다. 우측 그림에서는 name이라는 속성앞에는 #을, speak( )이라는 오퍼레이션에는 +를 붙였다. +는 public으로 Mammal 클래스 외부에서 해당 오퍼레이션을 호출할 수 있다는 뜻이고, #은 protected로 Mammal을 상속받는 클래스, 그림에서는 Cat 클래스 에서만 사용할 수 있다는 뜻이다. 

 

 

상속 개념 1. Child class는 Parent class의 속성과 오퍼레이션(메서드)를 가진다.

 

자식 클래스는 부모 클래스의 속성과 오퍼레이션을 가진다는 표현을 부모 클래스로부터 속성과 오퍼레이션을 "상속받는다" 라고 다시 표현 할 수 있다. 이 "상속받는다" 라는 개념을  Mammal 클래스와 Cat 클래스의 관계로 다음과 같이 모델(클래스 다이어그램)과 코드 나타낼 수 있다. 

 

개념 모델 (클래스 다이어그램)과 코드 

부모 클래스인 Mammal 클래스와 자식 클래스인 Cat 클래스가 상속관계에 있고, 부모클래스인 Mammal 클래스는 name 속성을 가지고, speak( ) 이라는 오퍼레이션(메소드)을 가진다. 그리고 Cat 클래스는 이 name 속성과 speak( ) 오퍼레이션을 상속받는다. (= 가진다. 재사용한다.)

이 모델을 파이썬 코드로 보면, 아래와 같이 Mammal 클래스와 Cat 클래스로 정의된다. 그리고 9라인의 Cat 클래스의 정의에서 class Cat(Mammal): 로 Mammal 클래스를 상속받는 Cat 클래스를 정의한다. 이는 Cat 클래스에서 Mammal 클래스의 속성과 오퍼레이션을 상속받는다는 뜻이다.

 

Mammal 클래스는 _name이라는 속성을 정의하고 생성자에서 이름을 파라메터로 받아 초기화 한다. 그리고 speak( )에서는 자기 인스턴스의 name을  "I am an aninmal. My name is ... "의 형태로 출력한다. 반면, Cat 클래스에서는 상속 외에 다른 코드를 정의 하지 않았다. 

 

그리고, 12번 라인에서는 Cat 클래스 타입의 인스턴스를 "Kitty"라는 입력값(_name에 저장)을 파라메터로 생성하여 cat 변수에 인스턴스를 넣고, 13번 라인에서 cat.speak( )을 실행시킨다. 그러면, Cat 타입의 cat 인스턴스는 Mammal 클래스로부터 상속받은 _name 속성에 "Kitty"를 할당하고, cat.speak()을 실행했을때 Mammal 클래스의 speak( ) 오퍼레이션이 해당 인스턴스의 _name 속성값과 함께 실행된다.   

class Mammal():
    def __init__(self, name):
        self._name = name
    
    def speak(self):
        return f"I am an animal. My name is {self._name}!"


class Cat(Mammal):
    pass

cat = Cat("Kitty")
print(f"cat.speak() -> {cat.speak()}")

 

즉, "Child class는 Parent class의 속성과 오퍼레이션(메서드)를 가진다." 라는 뜻은 "부모 클래스에서 정의한 속성와 오퍼레이션을 자식클래스에서 다시 정의하지 않아도 똑같이 사용할 수 있다" 라는 의미이다. 

 

상속개념 2. 일반화(generalization) – 특수화(specialization)    

상속의 구현 측면에서의 의미를 이해했다면, 이제는 두 클래스간의 상속관계로 정할때 두 클래스의 의미적 관계를 이해해야 한다. 여기서 언급되는 개념이 일반화-특수화의 관계 다른 표현으로 Is-A의 관계이다. 
 
“Is-a” 관계 (Child is a Parent ) 
위 그림을 기반으로 아래 문장을 도출 할 수 있다.
  • ­Cat is a Mammal.
  • Truck is a Vehicle.
  • Circle is a Shape.
­고양이는 포유류의 한 종류이다. 트럭은 차량의 한 종류이다. 원은 모양의 한 종류이다. 이 관계에서는 ­부모 클래스는 아래 레벨의 자식 클래스들을 일반화하는 형태이고, 자식 클래스는 부모 클래의 종류를 특수화 또는 상세화된 형태가 된다. 

 

이 관계를 코드로 표현하면 아래와 같다.  1~6라인까지는 위에서 언급한 코드와 내용이 같다. 그러나 8라인 부터는 Cat 클래스에 speak( ) 이라는 오퍼레이션을 다시 정의 했다. 이렇게 해서 Cat 클래스는 클래스 명으로도 Mammal클래스의 specialized 된 하위 클래스 이고, 클래스의 정의 측면에서도 _name이라는 속성과 speak( )이라는 오퍼레이션을 상속받고, 특히 speak() 오퍼레이션은 Cat 클래스에 맞게 재정의하여 상세화 한다. 

class Mammal(): 
    def __init__(self, name):
        self._name = name

    def speak(self):
        return f"I am a Mammal. My name is {self._name}!"

class Cat(Mammal):
    def speak(self):
        return f"{self._name} says Meow!"
    

cat = Cat("Kitty")
print(f"cat.speak() -> {cat.speak()}")

 

이해를 돕기 위해 하나의 예를 더 추가하자면, 위의 "Cat is a Mammal." 에서 "Dog is a Mammal."로 Mammal 클래스에서 상속받는 Dog 클래스를 아래와 같이 정의 할 수 있다. 

 

이를 코드로 구현하면 12번 라인과 같이 Dog 클래스가 추가된다. 그리고 Dog 클래스의 speak() 이라는 오퍼레이션을 Mammal로부터 상속 받았고 그 내부를 "return f"{self._name} says Woof!" 와 같이 재정의 할 수 있다. 

class Mammal(): 
    def __init__(self, name):
        self._name = name

    def speak(self):
        return f"I am a Mammal. My name is {self._name}!"

class Cat(Mammal):
    def speak(self):
        return f"{self._name} says Meow!"
    
class Dog(Mammal):
    def speak(self):
        return f"{self._name} says Woof!"
    

cat = Cat("Kitty")
dog = Dog("Buddy")
print(f"cat.speak() -> {cat.speak()}")
print(f"dog.speak() -> {dog.speak()}")

 

이와 같이, 자식 클래스는 부모 클래스의 특수화된 또는 상세화된 형태의 클래스이고, 이를 구현하기 위해 오퍼레이션의 body가 재정의 될 수 있다. 이는 메소드 오버라이딩(Method Overriding)이라고도 하며 아래의 확장 개념에서 다시 설명한다. 

 

추가적으로 메소드 오버라이딩과 함께, 자식 클래스는 부모클래스를 상속받은 후 새로운 속성과 오퍼레이션을 추가 할 수 있다. 그러나 부모클래스에서 정의된 속성과 오퍼레이션을 삭제 할 수는 없다. 이는 Is-A 관계를 훼손하기 때문이다. 

 

[확장 개념]  ­LSP (Liskov Substitution Principle) 

객체지향 설계원칙 중 하나 (SOLID) 중 하나인 LSP는 " 자식 클래스는 부모 클래스를 대체할 수 있어야 한다." 는 뜻이다. Is-a 관계가 만족하면 자식클래스는 부모클래스의 한 예로 대체가능하다. 즉, Cat은 하나의 포유류로 포유류의 다양 의미를 모두 만족시켜 대체 가능 하다. 
SOLID 원칙
원칙핵심  키워드
SRP 책임 분리
OCP 확장 가능 구조
LSP 대체 가능성
ISP 인터페이스 분리
DIP 추상에 의존

 

상속 관계에서 인스턴스 생성 순서

클래스의 상속관계와 그 구현에 대해 알아봤으면 실제로 실행되는 매커니즘을 이해하기 위해 객체(인스턴스)가 생성되는 순서를 살펴보려한다. 부모 클래스와 자식 클래스가 상속 관계일 때, 생성자(__init__)를 부모클래스에서만 정의하는지, 자식클래스에서도 정의하는지에 따라 실행 흐름이 달라진다.

 

1. 자식 클래스에 생성자가 없는 경우 

예를 들어 Dog 클래스가 Mammal을 상속받았지만, Dog에 별도의 __init__()가 없다면 어떻게 될까? 이 경우, 9라인과 같이 Dog 인스턴스를 생성하면, Parent 클래스인 Mammal의 __init__()가 자동으로 실행된다. 즉, 자식 클래스에 생성자가 없으면 부모 생성자를 그대로 사용한다.

class Mammal():
    def __init__(self, name):
        self._name = name
        print(f"{self._name} is a mammal.")        

class Dog(Mammal):
    pass

dog = Dog("Buddy")
 
 

 

2. 자식 클래스에 생성자가 있는 경우

아래 코드의 Cat 클래스처럼 자식 클래스에 __init__()가 정의되어 있다면 실행 순서는 다음과 같다.

1) 21번 라인 Cat("Kitty") 호출

2) 10번 라인 Cat.__init__() 실행
    11번 라인 print

3) 12번 라인 super().__init__(name) 호출 ,
     여기서 super()는 해당 클래스의 부모클래스를 리턴한다.
      즉 Mammal 클래스의 __inti__()을 호출한다. 
      [중요] super().__init__()을 하지 않으면
       Mammal 클래스의 생성자 호출을 하지 않는다
.

4) 2번 라인 Mammal.__init__() 실행
    3번 라인 print
    4번 라인 Mammal의 _name 속성 초기화 완료 후 반환
   12번 라인으로 돌아와 Cat의 __inti__ 완료 후 반환

즉, 자식 생성자 → 부모 생성자 → 자식 생성자 종료 의 순서로 실행된다.

 

예제 코드

class Mammal():
    def __init__(self, name):
        print("Here is Mammal.__init__()")
        self._name = name
        
    def speak(self):
        return f"I am a Mammal. My name is {self._name}!"

class Cat(Mammal):
    def __init__(self, name):
        print("Here is Cat.__init__()")
        super().__init__(name)

    def speak(self):
        return f"{self._name} says Meow!"

class Dog(Mammal):
    def speak(self):
        return f"{self._name} says Woof!"

cat = Cat("Kitty")
dog = Dog("Buddy")
 
 

상속의 개념의 확장

상속의 기본 개념을 이해했다면, 이제 그 확장 개념을 살펴보자.

1. 메서드 오버라이딩 (Method Overriding)

메서드 오버라이딩은 부모 클래스의 메서드를 자식 클래스에서 다시 정의하여 자식 클래스에 맞게 동작을 재정의하는 것이다. 

아래의 예는 Mammal 클래스에서 speak( )을 정의하고, 이를 상속받는 Cat, Dog, Tiger 클래스에서 speak() 오퍼레이션을 메서드 오버라이딩, 즉 재정의 하는 모델이다. 

이를 코드로 표현아면 아래와 같다. 아래의 코드에서 24,25, 218번 라인의  speak( )을 각각 실행하면 그 결과가 어떻게 다른가 비교 해 보자. Cat 클래스의 speak( )은 Mammal 클래스의 speak()을 재정의 한 경우이다. Dog클래스의 speak은 Mammal 클래스의 speak()을 재정의하였으나 그 내부는 구현하지 않는것으로 재정의 하였다. 마지막으로 Tiger 클래스는 내부를 구현하지 않고 상속만 받은 경우이다. 
class Mammal():
    def __init__(self, name):
        self._name = name

    def speak(self):
        return f"I am a Mammal. My name is {self._name}!"

class Cat(Mammal):
    def speak(self):
        return f"{self._name} says Meow!"


class Dog(Mammal):
    def speak(self):
        pass
    
class Tiger(Mammal):
    pass

cat = Cat("Kitty")
dog = Dog("Buddy")
tiger = Tiger("Tony")

print(cat.speak())
print(dog.speak())
print(tiger.speak())
 
이 코드를 실행시켜보면 그 결과는 아래와 같다. 
Kitty says Meow!
None
I am a Mammal. My name is Tony!
 
따라서1) Child 클래스에서 메소드 오버라이딩을 했으면, 그 메소드 실행하고 2) ­Child overriding 없으면, Parent class 메소드 실행 하는 것으로 정리해 볼 수 있다. 
 
 

2. 다중 상속 (Multiple Inheritance)

클래스간의 상속관계에서 2개 이상의 부모 클래스로부터 상속받는 관계를 다중상속이라 한다.  아래 모델의 우측 다중상속의 경우를 보면, 자식 클래스인 Cat 클래스는 Mammal 클래스의 특성과 Pet 클래스의 특성을 상속 받았다. 따라서 Cat 클래스는 포유류의 특성과 애완동물이 갖는 특성을 모두 갖고 있는것으로 이해할 수 있다.  

 

 
class Mammal:
    def __init__(self, name):
        self._name = name
    def move(self):
        return f"{self._name}가 움직인다."

class Pet:
    def __init__(self, owner):
        self._owner = owner
    def show_owner(self):
        return f"주인은 {self._owner}입니다."

class Cat(Mammal, Pet): #다중 상속 - Mammal과 Pet의 기능을 모두 갖는 Cat 클래스
    def __init__(self, name, owner):
        Mammal.__init__(self, name) 
        Pet.__init__(self, owner) 

    def speak(self):
        return f"{self._name} says Meow!"   

cat = Cat("Cookie", "Alice")
print(cat.speak()) 
print(cat.move()) 
print(cat.show_owner())
 
 
Parent Class가 같은 이름의 메소드를 가질 때
다중상속에서 한가지 고려해야 할 상황은 상속 받는 두 부모 클래스가 같은 이름의 오퍼레이션을 정의한 상황이다. 아래 모델에서와 같이 Mammal 클래스와 Pet 클래스가 move()오퍼레이션을 각각 정의했다면, Cat 클래스의 인스턴스 cat 에서 cat.move()를 호출했을 때, Mammal 클래스의 move()가 실행될까? 아니면 Pet 클래스의 move() 가 실행될까?
 

위 모델의 내용을 파이썬 코드로 구현하면 아래와 같다. 

class Mammal:
    def __init__(self, name):
        self._name = name
    
    def move(self):
        return f"{self._name}가 움직인다."
    
class Pet:
    def __init__(self, owner):
        self._owner = owner
    
    def move(self):
        return f"{self._owner}와 함께 움직인다."
    
class Cat(Pet, Mammal): 
    def __init__(self, name, owner):
        Mammal.__init__(self, name)
        Pet.__init__(self, owner)
    
    def speak(self):
        return f"{self._name} says Meow!"

cat = Cat("Cookie", "Alice")
print(cat.move())
 
 
이 코드를 실행시켜 24번 라인의 cat.move()의 결과를 보면 아래와 같다. 
Alice와 함께 움직인다.
 
 
즉, Mammal클래스의 move()가 아닌 Pet 클래스의 move( )가 실행되었다. 이유는 15번 라인의 Cat 클래스 정의시 상속받는 부모클래스의 순서에 있다. 
class Cat(Pet, Mammal):
 
 Pet, Mammal 순으로 상속의 순서가 정해져서 Pet클래스의 move()가  실행되는 것이다. 즉, 부모 클래스들이 같은 이름의 메서드를 가지고 있다면, 상속받은 순서대로 먼저 정의된 클래스의 메서드를 사용한다.
 
그러면 Mammal클래스의 move( )를 수행하려면 어떻게 할까? 
 
1. 상속의 순서를 아래와 같이 변경할 수 있다. 
class Cat(Mammal, Pet): # 순서가 Mammal, Pet으로 바뀜
 
 
2. 또는 Cat 클래스에서 move()를 재정의 한 후 Mammal 클래스의 move()를 직접 지정해 줄 수 있다. 
class Cat(Pet, Mammal):
    def __init__(self, name, owner):
        Mammal.__init__(self, name)
        Pet.__init__(self, owner)

    def move(self):
        return Mammal.move(self)  # 직접 지정
 
 
 이와 같이 다중 상속의 오퍼레이션 충돌의 경우 파이썬은 상속 순서대로 우선순위가 있다. 그러나 C++과 JAVA의 경우 그 대처방안은 아래와 같이 다르다. 
  • C++: 다중 상속을 허용하지만 동일한 메서드가 있으면 자동 선택하지 않고 개발자가 명시적으로 지정
  • Java: 클래스 다중 상속을 금지하여 구조적으로 충돌을 원천 차단하고, 인터페이스 충돌은 반드시 오버라이딩으로 해결.
 

3. 추상 클래스와 인터페이스

상속은 단순 재사용을 넘어 추상화(Abstraction) 개념과 연결된다. 추상화(Abstraction)이란 " 어떤 대상에서 핵심적인 부분만을 추출하고, 불필요한 세부 사항을 감추는 과정" 을 의미한다.  상속관게에서 추상화 했다는 것은 자식클래스의 특성들을 한단계 상위 레벨로 추상화 하여 부모클래스를 정의한 것이다. 즉, 부모클래스는 추상화 수준이 높고, 자식 클래스는 추상화 된 부모클래스를 한단계 상세화 한다. 이 상세화 하는 과정에서 추상 클래스와 인터페이스 라는 개념이 확장된다. 

  • 추상 클래스 – 기능의 일부만 구현하고 나머지는 미구현 한 "미완성 클래스"
  • 인터페이스 - 메서드 Prototype(이름, 파라메터)만 정의하고,  로직은 상속받는 클래스에서 구현 - 

 

3.1 추상 클래스 (Abstract Class)

아래 모델은 Mammal 클래스의 speak( )은 API만 정의하고 그 구현은 자식 클래스에서 추가된다. 하지만 Mammal 클래스의 run() 은 그 내부 구현을 가지고 있다. 즉, 부모클래스에서 구현이 명확한 것은 추가하고, 자식 클래스에 따라 구현이 달라질 가능성이 있는 오퍼레이션은 함수의 prototype (return type, 함수명, 파라메터) 만 정의한한 클래스를 추상 클래스라 한다. 

 

 

이 모델을 파이썬으로 구현하면 아래와 같다. 파이썬에서 추상 클래스는 abc 모듈을 사용하여 추상 클래스를 구현한다. 따라서 1번라인과 같이 abc 표준 라이브러리에서 ABC 추상 클래의 베이스 클래스와 abstractmethod 데코레이터 함수를 import 받는다.  2번 라인과 같이 Mammal 클래스를 ABC 클래스로부터 상속받아 추상클래스를 정의한다. 그리고 speak()의 경우 Mammal 클래스에서는 내부를 구현하지 않고, Cat 클래스에서 구현할 예정이어서 Mammal 클래스에서는 @abstractmethod 데코레이터로 abstract method 임을 표현한다. 이경우 상속받는 Dog, Cat 클래스에서 내부 구현을 하지 않으면 인스턴스 생성시 에러가 발생한다. 

from abc import ABC, abstractmethod

class Mammal(ABC):
    def __init__(self, name):
        self._name = name

    def run(self):
        print(f"{self._name} is runing!!")

    @abstractmethod 
    def speak(self):
        pass

class Dog(Mammal):
    def speak(self):
        print(f"{self._name} says woof!!")

class Cat(Mammal):
    def speak(self):
        print(f"{self._name} says meow!!")


# 객체 생성
dog = Dog("바둑이")
cat = Cat("나비")

dog.speak() 
cat.speak()
dog.run() 
cat.run()
 
 

 

추상클래스의 실제 사용의 예는 아래와 같다. 쇼핑몰 시스템에서 결제 기능은 결제 방식에 따라 달라지지만 결제 정보를 출력하는 기능은 데이타가 같다. 따라서 Payement 클래스를 추상클래스로 정의하여 issu_receipt()기능은 Payment 클래스에서 구현하고, 실제로 pay() 하는 기능은 결제 방식에 따라 자식 클래스에서 구현할 수 있다.  

 

 

3.2 인터페이스 (Interface)

상속관계에서 인터페이스란 부모클래스에서는 기능이 무엇인지 즉, 메서드 Prototype(이름, 파라메터)만 정의하고, 내부 구현은 상속받는 클래스에서 구현하는 경우이다.  즉 추상클래스에서 내부 구현을 하나도 하지 않은 클래스 이다. 

 

아래는 특정 이벤트가 발생했을때 Notification 을 주는 Notifier 클래스의 예이다. 예를 들어 도서관 시스템에서 대출한 도서에 대한 반납일이 도래했을 경우 notification을 주는 기능이 자동 호출 된다면 아래 모델의 부모 클래스인 Notifier 클래스의 notify() 함수를 caller 모듈에서 호출 할 것이다. 그러나 실제로는 생성한 클래스 타입에 따라 EmailNotifier클래스와 SMSNotifier 클래스의 notify()오퍼레이션이 실행된다. 

 

 

 

모든 메서드를 @abstractmethod로 선언하면 인터페이스처럼 사용할 수 있다.

from abc import ABC, abstractmethod


class Notifier(ABC):

    @abstractmethod
    def notify(self, message):
        pass


class EmailNotifier(Notifier):
    def notify(self, message):
        print(f"Email sent: {message}")


class SMSNotifier(Notifier):
    def notify(self, message):
        print(f"SMS sent: {message}")

email = EmailNotifier()
sms = SMSNotifier()

email.notify("Hello World") // 실제로는 email 보내는 로직이 들어가게 된다. 
sms.notify("Hello World")   // 실제로는 sms를 보내는 로직이 들어가게 된다.
 
 
 

 

 

 

객체지향 프로그래밍에서 클래스를 정의하는 중요하게 적용되는 특징 4가지가 있다. 캡슐화(Encapsulation), 정보은닉(Information Hiding), 상속(Inheritance), 그리고 다형성(Polymorphism) 이다.

 

이번 장에서는 첫번째 특징인 캡슐화(Encapsulation) 개념을 이해한다. 또한, 실세계에서 또는 개발 과제의 도메인에서 1) 캡슐화를 통해 클래스를 도출해 나가는 과정을 이해하고 2) 파이썬으로 클래스를 정의하고 인스턴스를 생성하는 과정을 실습한다. 

 

캡슐화(Encapsulation) 개념

캡슐화(Encapsulation)는 "~안에"라는 의미를 부여하는 접두사 'en'과 라틴어로 "작은 상자" 또는 "캡슐"을 의미하는 'Capsula'가 합쳐진 단어로 무언가를 포장하거나 보호하기 위해 감싸는 것을 의미합니다. 이러한 의미의 캡슐화를 객체지향에서는 다음과 정의합니다. 

  • Encapsulation is the process of bundling the data and the methods that operate on that data into a single unit or class.[1]
  • 캡슐화는 데이터를 처리하는 메서드와 데이터를 하나의 단위(클래스)로 묶는 과정이다."

즉, 데이터와 메서드들을 클래스라는 단위로 묶는 과정인데, 이 묶는 과정에서 해당 클래스의 역할과 의미에 맞는 데이터와 메서드들을 묶는것이 중요합니다. 

[참고] 용어 정리 - 클래스에서 데이타와 메서드 
1. 데이터
    - 객체가 가지고 있는 정보로 클래스의 멤버변수로 정의한다. 
    -  속성(Attribute)라고도 한다. 
­    -  예를 들어 고양이 클래스의 색깔, 크기, 품종 등
2. 메서드 (Methods)
­   -  객체가 수행하는 동작을 정의한 함수
   - 오퍼레이션(operation) 이라고도 한다.
­   - 예를 들어 고양이 클래스에서는 meow( ), jump( ), look( ), sleep( )  

 

캡슐화(Encapsulation) 과정

예제 1. 실세계에서의 고양이 클래스 캡슐화

아래의 그림의 왼쪽은 강아지와 고양이의 데이타(타원)와 메소드(둥근사각형)들이 흩어져 있는데 이를 '고양이'라는 개념의 클래스와 연관된 데이타와 메소드를 캡슐화 한 결과가 오른쪽 초록색 사각형 입니다.  

 

 

예제 2. 도서관 시스템에서의 클래스 캡슐화

 

그렇다면 도서관 시스템에서 캡슐화를 할 수 있는 정보를 찾아보겠습니다. 

도서관 시스템 요구사항

OO 대학교 도서관 시스템은 학생들이 도서관 자원을 효과적으로 이용하기 위해 시스템을 이용하는 사용자 정보와 도서관에서 소장하는 도서정보를 기본정보로 관리하고, 이를 기반으로 도서 검색, 대출, 반납, 연체 관리 기능을 제공한다. 각 기능의 상세 설명은 아래와 같다.

도서관에서 소장하는
도서 정보는 시스템에서 사용하는 도서의 고유 식별자를 포함하여 제목, 저자, ISBN, 출판사, 출판연도, 장르, 대출 가능 여부 등의 정보를 관리해야 한다. 새로운 도서는 시스템에 등록하고, 등록된 도서는 수정, 삭제와 같은 기능을 지원한다. 또한 등록된 도서는 검색 가능해야 하는데 키워드, 제목, 저자, 장르를 조건으로 검색할 수 있으며, 검색된 도서는 제목, 저자, 출판사, 출판연도, 장르 정보를 보여준다.


도서관 시스템을 사용하는 학생은 학생 기본정보를 시스템에서 관리하는데
, 학번, 이름, 이메일, 연락처, 비밀번호, 전공, 선호장르를 저장한다. 학생 정보는 신규입력, 수정, 삭제(탈퇴)가 가능하며 비밀번호 재설정은 현재 비밀번호를 확인후 수정이 가능하다.

도서를 대출하기 위해, 사서는 사용자로부터 사용자 ID와 대출하려는 도서 정보를 전달받아 시스템에 사용자 ID와 도서 ID를 입력하면, 시스템은 누가(사용자ID), 어떤 도서를(도서ID), 언제 대출했는지를 저장한다. 대출 기한은 기본 2(14) 이며, 최대 5권 대출이 가능하다. 연체중인 사용자는 대출을 할 수 없으며, 연체중인 도서 반납한 후 대출이 가능하다.  도서관 시스템은 매일 아침 연체중인 사용자에게 연체상태 메일을 보낸다.  사용자가 대출한 도서를 사서에게 반납하면, 사서는 도서 바코드를 찍어 해당 사용자의 대출 상태를 반납완료 상태로 변경한다.

 

위 요구사항은 크게 세단락으로 작성되어 있습니다. 처음 단락은 도서관 시스템의 전반적인 요구사항을 개략적으로 정리한 것이고 두번째, 세번째 단락은 도서정보와 사용자정보에 대한 요구사한을 기술한 것입니다. 다음은 요구사항을 기능별로 크게 구분하여 관련된 데이타와 기능을 캡슐화 하는 과정입니다.

요구사항 추출된 데이타와 기능들 캡슐화
OO 대학교 도서관 시스템은 학생들이 도서관 자원을 효과적으로 이용하기 위해 시스템을 이용하는 사용자 정보와 도서관에서 소장하는 도서정보를 기본정보로 관리하고, 이를 기반으로 도서 검색, 대출, 반납, 연체 관리 기능을 제공한다. 각 기능의 상세 설명은 아래와 같다.
시스템 전체 개념으로 객체화 하기는 개념의  크기가 크다


도서관에서 소장하는
도서 정보는 시스템에서 사용하는 도서의 고유 식별자를 포함하여 제목, 저자, ISBN, 출판사, 출판연도, 장르, 대출 가능 여부 등의 정보를 관리해야 한다. 새로운 도서는 시스템에 등록하고, 등록된 도서는 수정, 삭제와 같은 기능을 지원한다. 또한 등록된 도서는 검색 가능해야 하는데 키워드, 제목, 저자, 장르를 조건으로 검색할 수 있으며, 검색된 도서는 제목, 저자, 출판사, 출판연도, 장르 정보를 보여준다.


도서관 시스템을 사용하는 학생은 학생 기본정보를 시스템에서 관리하는데, 학번, 이름, 이메일, 연락처, 비밀번호, 전공, 선호장르를 저장한다. 학생 정보는 신규입력, 수정, 삭제(탈퇴)가 가능하며 비밀번호 재설정은 현재 비밀번호를 확인후 수정이 가능하다.

도서를 대출하기 위해, 사서는 사용자로부터 사용자 ID와 대출하려는 도서 정보를 전달받아 시스템에 사용자 ID와 도서 ID를 입력하면 , 시스템은 누가(사용자ID), 어떤 도서를(도서ID), 언제 대출했는지를 저장한다. 대출 기한은 기본 2(14) 이며, 최대 5권 대출이 가능하다. 연체중인 사용자는 대출을 할 수 없으며, 연체중인 도서 반납한 후 대출이 가능하다.  도서관 시스템은 매일 아침 연체중인 사용자에게 연체상태 메일을 보낸다.  사용자가 대출한 도서를 사서에게 반납하면, 사서는 도서 바코드를 찍어 해당 사용자의 대출 상태를 반납완료 상태로 변경한다.

 

[참고] 인식의 확장 - 클래스의 종류

클래스를 캡슐화 하는 과정은 개발하고자 하는 시스템의 요구사항에서 시스템의 (데이타를 갖는) 기능 단위를 도출하는 과정이다. 이렇게 도출된 클래스들은 위 과정에서 도출된 Book, BookLoan, Student와 같은 일명 엔티티 클래스들과 함께 다음과 같이 구분될 수 있다. 

  • 1. Entity (도메인 객체)
    목표 도메인에서 시스템의 핵심 개념 표현하며 주로 저장될 데이터들이다. 따라서 이 객체들은 데이터의 상태를 CRUD(Create, Retrieve, Update, Delete)의 기능 또는 (도메인에 따라 요구되는) 해당 객체의 고유 기능 가진다. 이 객체는 주로 DB에 저장되어 오래 유지되는 종류의 객체들이다. 
    • 예: 도서관 시스템에서 Book, User, BookLoan
    • 현실 세계 개념을 모델링
  • 2.Boundary (UI / Interface)
    목표 시스템에서 사용자와 시스템의 접점으로 입력/출력 처리를 담당하는 사용자 인터페이스(User Interface) 클래스들이다. 화면과 메뉴로 나타난다. 
    • 예: MenuUI, ConsoleUI
  • 3. Control (Controller)
    Entity 객체를 사용하여 Boundary 객체에 필요한 데이타를 넘겨주고 받아와서 요구사항에 맞게 기능을 수행하는 클래스이다. 주로 여러 클래스의 기능을 호출하여 실행한다. 
    • 예: UserController - UI 클래스로부터 사용자의 정보를 받아 사용자 Entity 클래스 인스턴스를 생성하여  CRUD한다. 
    • 예: LoanController - UI클래스로부터 도서정보와 사용자 정보를 받아 대출 Entity 클래스 인스턴스를 생성하여 저장하거나 조회한다.
  • 4. Repository (Data Access)
    Entity 클래스를 데이타베이스에 저장하고 읽어오는 역할을 한다. Entity 클래스의 정보에 대한 관리가 역할이 클래스들이다. 학부에서 구현하는 간단한 콘솔베이스 프로그램의 경우 메모리에 리스트로 관리하기도 하고, MySQL과 같은 DBMS에 저장/검색하는 역할로 구현 하기도 한다. 
    • 예: BookRepository, UserRepository
클래스는 코드 단위가 아니라 시스템의 기능을 수행하는 역할(Role) 단위이고, 이 시스템은 서로 다른 책임(역할) 을 가진 클래스들의 협력(메시지 호출) 으로 구현된다. 객체지향은 클래스를 만드는 기술이 아니라, 모듈의 역할(Role) 나누고 (모듈간의) 협력(Collaboration, Message Passing)설계하는 기술이다.
이러한 문맥상에서, 캡슐화 과정은 흩어져 있는 데이타와 기능들을 보면서 서로 관련있는 것을 하나의 단위 즉 클래스로 묶는 과정 즉, 클래스를 정의하는 과정이라 할 수 있겠다.

 

+ Recent posts