수강 신청 시스템의 객체 지향 설계 전략

전체 구조 개요

기존의 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}"

태그: python 객체 지향 설계 반사 기술 다형성 캡슐화

6월 1일 02:20에 게시됨