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++ 같은 개발 도구를 “리눅스처럼” 설치·관리할 수 있게 해주는 개발 환경
터미널 환경(bash 등) -Windows에서 리눅스 느낌의 터미널과 기본 유닉스 도구들을 제공
*패키지 관리자 pacman
“GCC 설치”, “GDB 설치”, “업데이트” 같은 걸명령 한 줄로처리
예: pacman -S mingw-w64-ucrt-x86_64-gcc
[참고] 패키지 관리자 - 프로그램(패키지)을 설치/업데이트/삭제하고, 필요한 “의존성(같이 필요한 파일·라이브러리)”까지 자동으로 맞춰주는 도구 - 개발에 필요한 패키지 도구들을 다운로드 → 압축풀기 → 경로 맞추기 → 업데이트 관리를 사람이 일일이 하지 않게 해주는 설치 관리자
도서관 시스템은 도서를 공통적으로 고유 식별자, 제목, 저자, ISBN, 출판사, 출판연도, 장르 정보를 포함하는 기본 도서(Book)로 관리하며, 검색 결과에서는 해당 공통 정보를 출력한다. 도서는 제공 형태에 따라 일반도서(PrintedBook)와 전자도서(EBook)로 구분되며, 일반도서는 서가 위치와 대출 가능 권수를 추가로 관리하고 대출 시 권수가 감소하며 반납 시 증가한다. 전자도서는 파일 형식, 접근 URL, 동시 이용 가능 인원 정보를 추가로 관리하며, 이용 시작 시 현재 이용 인원이 증가하고 종료 시 감소하도록 한다.
도서관 시스템의 예상 클래스 다이어그램과 예상 코드를 참조하여 아래 요구사항에서 상속관계의 클래스를 도출하여 클래스 다이어그램을 그리고, 파이썬 코드로 구현하시오.
카페 키오스크 시스템은 판매하는 모든 메뉴항목에 대해 공통적으로 고유 식별자, 메뉴명, 가격, 판매 상태 정보를 관리하며, 메뉴 목록 조회 시 해당 공통 정보를 출력한다. 메뉴는 종류에 따라 커피(Coffee)와 디저트(Dessert)로 구분되며, 커피류는 카페인 함량과 온도(Hot/Ice) 정보를 추가로 관리하고, 디저트류는 칼로리와 보관 방법 정보를 추가로 관리한다. 모든 메뉴는 판매 상태가 ‘판매중’일 때만 주문이 가능하다.
아래의 클래스 다이어그램을 보고 파이썬으로 클래스를 정의하고, 클래스의 인스턴스를 2개 이상 만들어 매소드를 호출 하시오.
클래스 다이어그램
클래스 다이어그램
클래스 설명
- 클래스의 이름은 Book이다. - 클래스 속성은 id, title, .... loanStatus 이다. - 클래스 속성은 외부로부터 정보를 보화기 위해 private (-표시) 으로 정의한다.
- 클래스 매서드는 getter/setter, add(), modify(), ... 등이 있다. - 클래스 매서드는 외부에서 호출 가능해야 하므로 public(+표시) 으로 정의한다. - getter/setter는 속성값을 접근하는 함수로 데코레이터나 getter/setter함수를 정의한다.
- add(), modify(), dispose(), search() 함수는 body에 함수 명을 출력하는 기능을 추가한다. - add() 함수의 예
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
아래의 요구사항을 보고 서로 관련된 데이타와 기능을 encapsulation하여 Entity 클래스를 도출하시오.
요구사항
도서관 시스템 요구사항
OO대학교 도서관 시스템은 학생들이 도서관 자원을 효과적으로 이용하기 위해 시스템을 이용하는 사용자 정보와 도서관에서 소장하는 도서정보를 기본정보로 관리하고, 이를 기반으로 도서 검색, 대출, 반납, 연체 관리 기능을 제공한다. 각 기능의 상세 설명은 아래와 같다.
도서관에서 소장하는 도서 정보는 시스템에서 사용하는 도서의 고유 식별자를 포함하여 제목, 저자, ISBN, 출판사, 출판연도, 장르, 대출 가능 여부 등의 정보를 관리해야 한다. 새로운 도서는 시스템에 등록하고, 등록된 도서는 수정, 삭제와 같은 기능을 지원한다. 또한 등록된 도서는 키워드, 제목, 저자Q, 장르를 조건으로 검색할 수 있으며, 검색된 도서는 제목, 저자, 출판사, 출판연도, 장르 정보를 보여준다.
도서관 시스템을 사용하는 학생은 학생 기본정보를 시스템에서 관리하는데, 학번, 이름, 이메일, 전화번호, 비밀번호, 전공, 선호장르를 저장한다. 학생 정보는 신규입력, 수정, 삭제(탈퇴)가 가능하며 비밀번호 재설정은 현재 비밀번호를 확인후 수정이 가능하다.
도서를 대출하기 위해, 사서는 사용자로부터 사용자 ID와 대출하려는 도서 정보를 전달받아 시스템에 사용자 ID와 도서 ID를 입력하면, 시스템은 누가(사용자ID), 어떤 도서를(도서ID), 언제 대출했는지를 저장한다. 대출 기한은 기본 2주(14일)이며, 최대 5권 대출이 가능하다. 연체중인 사용자는 대출을 할 수 없으며, 연체중인 도서 반납한 후 대출이 가능하다.
도서관 시스템은 매일 아침 연체중인 사용자들에게 연체상태 메일을 보낸다. 사용자가 대출한 도서를 사서에게 반납하면, 사서는 도서 바코드를 찍어 해당 사용자의 대출 상태를 반납완료 상태로 변경한다.
객체 찾기
[Hint]
도서관 시스템에서 관리해야 할 정보의 단위는 무엇일까?
정보의 단위는 도서명, 학생명, 전화번호 수준의 최소 데이타 단위인가? -> 최소단위의 묶음으로
정보의 단위가 시스템, 도서관리, 대출 관리 등 사용자 인터페이스와 업무 로직등이 묶여있는 단위인가? -> 저장할 데이터 단위로 분할
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, 함수)을 찾아 적으시오.
객체지향 프로그래밍에서 클래스를 정의하는 중요하게 적용되는 특징 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 classtoacquire the properties and behavior of another class[1]. 즉, 상속이란 한 클래스가 다른 클래스의 속성과 동작을 가질 수 있도록 하는 메커니즘으로 정의한다. 지금까지 클래스 하나에 대한 특징을 봤다면, 상속은 두개의 클래스간의 관계를 설명하는데 하나의 클래스의 속성과 동작을 다른 클래스에서 재사용 하는 관계를 의미한다.
상속 정의 2. Inheritanceisarelationshipthatdefinesoneclassinterms of another. Itisthemechanismthatpermitstheincorporation of thestructure and behavior of aparentclassintoachildclass.[2] 이 정의에서 상속은 한 클래스를 다른 클래스를 기반으로 정의하는 관계이며, 부모 클래스의 구조와 동작을 자식 클래스에 통합할 수 있게 하는 메커니즘으로 설명된다. 정의 1에서 두 클래스 간의 재상용 관계를 얘기했다면, 정의 2에서는 두 클래스 간의 의미를 parent class와 child 클래스의 관계로 상세화하여 Parent 클래스의 속성과 기능을 자식 클래스에서 재사용 하는 것으로 이해할 수 있다.
상속 정의 3. Inheritanceistheabilityforaclasstodefineageneralization-specializationrelationship, byreusing and refiningthefeatures of aparentclass.[3] 상속은 부모 클래스의 기능을 재사용하고 다듬으면서 일반화-특수화 관계를 정의하게한다. 라고 설명한다. 정의 2에서 Parent 클래스와 Child 클래스를 정의 했다면 정의 3에서는 Parent-Child 클래스의 관계가 Parent 클래스는 Child 클래스보다 일반화된 클래스, Child 클래스는 Parent 클래스에서 특수화 된 클래스로 설명한다.
이 정의들을 정리하면,
Child class는 Parent class의 속성과 메서드를 가진다.
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)이란 " 어떤 대상에서 핵심적인 부분만을 추출하고, 불필요한 세부 사항을 감추는 과정" 을 의미한다. 상속관게에서 추상화 했다는 것은 자식클래스의 특성들을 한단계 상위 레벨로 추상화 하여 부모클래스를 정의한 것이다. 즉, 부모클래스는 추상화 수준이 높고, 자식 클래스는 추상화 된 부모클래스를 한단계 상세화 한다. 이 상세화 하는 과정에서 추상 클래스와 인터페이스 라는 개념이 확장된다.
아래 모델은 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)을 설계하는 기술이다.
이러한 문맥상에서,캡슐화 과정은 흩어져 있는 데이타와 기능들을 보면서 서로 관련있는 것을 하나의 단위 즉 클래스로 묶는 과정 즉, 클래스를 정의하는 과정이라 할 수 있겠다.