전체 구조 개요
기존의 ATM 및 쇼핑카트 프로젝트에서 확장된 3계층 아키텍처를 기반으로, 객체 지향 원칙을 중심으로 설계된 수강 관리 시스템입니다. 사용자 유형에 따라 분기되는 인터페이스와 데이터 처리 방식을 통합하여 유지보수성과 확장성을 높였습니다.
계층적 구성
- 사용자 인터페이스 계층: 관리자, 교수, 학생 세 가지 유형에 맞춘 독립된 뷰를 제공하며, 공통된 로그인/회원가입 흐름은 반복적인 코드를 줄입니다.
- 비즈니스 로직 계층: 각 사용자 타입에 맞는 인증 및 권한 검증 기능을 중앙 집중적으로 처리합니다.
- 데이터 저장 계층: Python 객체를 파일에 직렬화하여 저장하는
pickle모듈을 활용, 클래스 기반의 데이터 모델을 유지합니다.
공통 인증 메커니즘 설계
다양한 사용자 유형(관리자, 교수, 학생)의 로그인/회원가입 동작을 하나의 인터페이스로 통합하기 위해 반사 기술(Reflection)을 활용했습니다. 문자열로 된 유저 타입 이름을 실제 클래스로 변환하고, 동일한 이름의 메서드를 통해 일관된 인증 처리를 수행합니다.
from interface import auth_inf
from conf import settings
from lib import common
# 현재 로그인 상태 저장 (유저 타입별)
login_status = {
'Admin': None,
'Teacher': None,
'Student': None
}
def user_register(user_type):
display_name = settings.TYPE_LABEL.get(user_type)
while True:
username = input(f'{display_name} ID (q: 취소): ').strip()
if username == 'q':
return False
exists, msg = auth_inf.check_user_exists(username, user_type)
if exists:
print(f'이미 존재하는 {display_name} 계정: {username}')
continue
password = input(f'{display_name} 비밀번호: ')
confirm = input(f'{display_name} 비밀번호 확인: ')
if password != confirm:
print('비밀번호 불일치')
continue
hashed_pwd = common.hash_password(password)
success, msg = auth_inf.register_user(username, hashed_pwd, user_type)
if success:
print(f'{display_name} 계정 생성 완료: {username}')
break
return True
def user_login(user_type):
display_name = settings.TYPE_LABEL.get(user_type)
while True:
username = input(f'{display_name} ID: ').strip()
if username == 'q':
return False
exists, msg = auth_inf.check_user_exists(username, user_type)
if not exists:
print(f'존재하지 않는 {display_name} 계정: {username}')
continue
pwd_input = input(f'{display_name} 비밀번호: ')
hashed_pwd = common.hash_password(pwd_input)
success, msg = auth_inf.authenticate_user(username, hashed_pwd, user_type)
if success:
login_status[user_type] = username
print(f'{display_name} 로그인 성공: {username}')
return True
print('비밀번호 오류')
반사 기반 인증 인터페이스
모든 사용자 유형에 대해 동일한 메서드 이름(verify_pwd)을 갖도록 설계하여 다형성(다형성)을 적용했습니다. 반사 기법을 통해 models 모듈 내의 특정 클래스를 동적으로 가져오고, 그 인스턴스에 접근하여 일관된 인증 절차를 수행합니다.
from db import models
def check_user_exists(username, *, user_type):
target_class = getattr(models, user_type)
instance = target_class.find(username)
if instance:
return True, f"{target_class.__name__} 존재: {username}"
return False, f"{target_class.__name__} 미존재: {username}"
def register_user(username, password, *, user_type, **kwargs):
target_class = getattr(models, user_type)
target_class(username, password, **kwargs)
return True, f"{user_type} 계정 생성 완료: {username}"
def authenticate_user(username, password, *, user_type):
target_class = getattr(models, user_type)
user_obj = target_class.find(username)
if user_obj.verify_password(password):
return True, "인증 성공"
return False, "인증 실패"
def is_active_user(username, user_type):
target_class = getattr(models, user_type)
obj = target_class.find(username)
if hasattr(obj, 'is_active') and not obj.is_active:
return False
return True
def change_password(username, new_pw, *, user_type):
target_class = getattr(models, user_type)
user_obj = target_class.find(username)
user_obj.update_password(new_pw)
return True, "비밀번호 변경 완료"
객체 지향 설계 사례: 데이터 캡슐화
Admin 클래스에서는 민감 정보인 비밀번호를 외부 접근을 막기 위해 캡슐화 처리하였습니다. 내부에서만 접근 가능한 __pwd 속성과, 이를 검증하는 verify_password 메서드를 통해 보안성을 강화했습니다.
class Admin(DBHandler):
def __init__(self, name, pwd):
self.name = name
self.__password = pwd
self.save()
def verify_password(self, input_pwd):
return input_pwd == self.__password
def create_school(self, school_name, location):
School(school_name, location)
def block_user(self, user_class, user_name):
target_obj = user_class.find(user_name)
target_obj.is_active = False
target_obj.save()
def unblock_user(self, user_class, user_name):
target_obj = user_class.find(user_name)
target_obj.is_active = True
target_obj.save()
로그인 후 기능 실행 흐름 최적화
로그인 성공 여부를 조건으로 두 개의 병렬 루프를 사용하여, 사용자 인터페이스와 핵심 기능 사이의 흐름을 명확하게 분리했습니다. 로그인 후 첫 번째 루프 종료 → 두 번째 루프 진입 → 주 기능 제공.
def start_student_interface():
# 1단계: 로그인/회원가입
while not login_status['Student']:
choice = input("""
+----학생 로그인/회원가입----+
| 1. 회원가입 |
| 2. 로그인 |
+------------------------+
선택 (q: 메인으로): """)
if choice == 'q':
return
if choice == '1':
user_register('Student')
elif choice == '2':
if user_login('Student'):
print("로그인 성공!")
break
else:
print("1 또는 2 입력")
# 2단계: 핵심 기능 제공
student_id = login_status['Student']
while True:
action = input(f"""
================== 환영합니다, {student_id} ==================
1. 비밀번호 변경
2. 소속 학교 등록
3. 수강 과목 선택
4. 성적 조회
===================================================
기능 선택 (q: 나가기): """).strip()
if action == 'q':
return
if action in function_map:
function_map[action]()
else:
print("유효하지 않은 선택")
객체의 본질: 데이터 + 행동의 통합
모든 데이터 처리는 models 내 클래스에서 이루어집니다. 예를 들어, 교수가 과정을 개설하면, 해당 과정 객체는 교수 이름을 포함하며, 교수 객체는 과정 목록에 추가됩니다. 이처럼 객체는 자신의 데이터와 관련된 행동(메서드)을 함께 포함하고 있습니다.
class Teacher(DBHandler):
def __init__(self, name, pwd):
self.name = name
self.__password = pwd
self.is_active = True
self.course_list = []
self.save()
def verify_password(self, input_pwd):
return input_pwd == self.__password
def update_password(self, new_pw):
self.__password = new_pw
self.save()
def add_course(self, course_name, price, school_name):
Course(course_name, price, school_name, self.name)
self.course_list.append(course_name)
self.save()
def get_course_details(self):
return [Course.find(name).get_info() for name in self.course_list]
def update_score(self, student_name, course_name, score):
student = Student.find(student_name)
student.scores[course_name] = score
student.save()
인터페이스 계층의 역할
실제 데이터 처리는 models 클래스에 위임되며, 인터페이스 계층은 단순히 객체의 생성, 조회, 메서드 호출을 조율하는 중개자 역할을 합니다.
def create_course_handler(teacher_id, school, course, price):
teacher = models.Teacher.find(teacher_id)
teacher.add_course(course, price, school)
school_obj = models.School.find(school)
school_obj.add_course(course)
return True, f"과정 '{course}' 등록 완료: {school}"