Kotlin 파일 시스템 조작 시스템 설계 및 구현

이 문서에서는 파일 시스템을 제어하는 명령어 기반 콘솔 애플리케이션의 재구성된 아키텍처를 다룹니다. 기존의 전역 변수 사용과 난잡한 클래스 간 의존성 문제를 해결하기 위해, 책임 분리 원칙과 싱글턴 패턴을 활용한 모듈화된 구조로 개선되었습니다.

명령 처리 엔진 (CommandLineTool)

애플리케이션의 진입점으로, 사용자 입력을 수신하고 파싱하여 처리기로 전달합니다. lazy 초기화를 통해 인스턴스 생성을 지연시키며, 단일 인스턴스만 존재하도록 보장합니다.

package kFile

import java.util.Scanner

class CommandLineTool private constructor() : CallBack {
    companion object {
        val instance: CommandLineTool by lazy { CommandLineTool() }
    }

    private val fileHandler = FileManager(this)
    private val parser = CommandParser(fileHandler)

    fun start() {
        println("파일 시스템 관리자에 오신 것을 환영합니다. 명령어를 입력하세요:")
        while (true) {
            displayPrompt(fileHandler.currentDirName)
            processInput()
        }
    }

    private fun displayPrompt(dirName: String) {
        print("[${dirName}]# ")
    }

    private fun processInput() {
        val scanner = Scanner(System.`in`)
        val input = scanner.nextLine().trim()
        val tokens = input.split(" ", limit = 4)

        parser.parse(tokens.size, input, tokens)
    }

    override fun notify(message: String?) {
        println(message)
    }
}

명령어 분석기 (CommandParser)

사용자의 입력을 분석하고, 적절한 작업을 FileManager에 위임합니다. 명령어의 구조(단일, 이항, 삼항)에 따라 분기 처리하며, 잘못된 명령어는 알림 메시지를 반환합니다.

package kFile

class CommandParser(private val handler: FileManager) {

    fun parse(tokenCount: Int, rawCommand: String, tokens: List<String>) {
        when (tokenCount) {
            1 -> handleSingleCommand(rawCommand)
            2 -> handleTwoArgumentCommand(tokens[0], tokens[1])
            3 -> handleThreeArgumentCommand(tokens[0], tokens[1], tokens[2])
            else -> notify("지원되지 않는 명령어 형식입니다.")
        }
    }

    private fun handleSingleCommand(cmd: String) {
        when (cmd) {
            "ls" -> handler.listFiles()
            "pwd" -> handler.showCurrentPath()
            "exit" -> System.exit(0)
            else -> notify("알 수 없는 명령어입니다.")
        }
    }

    private fun handleTwoArgumentCommand(cmd1: String, cmd2: String) {
        when (cmd1) {
            "ls" -> if (cmd2 == "-l") handler.listWithSize() else notify("잘못된 옵션입니다.")
            "mkdir" -> handler.createDirectory(cmd2)
            "touch" -> handler.createFile(cmd2)
            "cd" -> if (cmd2 == "..") handler.goUp() else handler.changeDirectory(cmd2)
            "cat" -> handler.displayContent(cmd2)
            "rm" -> handler.remove(cmd2)
            else -> notify("지원되지 않는 명령어입니다.")
        }
    }

    private fun handleThreeArgumentCommand(cmd1: String, src: String, dest: String) {
        when (cmd1) {
            "mv" -> handler.move(src, dest)
            "cp" -> handler.copy(src, dest)
            "rename" -> handler.rename(src, dest)
            else -> notify("지원되지 않는 명령어입니다.")
        }
    }

    private fun notify(message: String) {
        CallBack.instance.notify(message)
    }
}

콜백 인터페이스 (CallBack)

시스템 내부 컴포넌트 간의 통신을 위한 표준 인터페이스로, 결과 메시지를 출력하거나 상태를 공유하는 데 사용됩니다.

package kFile

interface CallBack {
    fun notify(message: String?)
}

파일 시스템 핸들러 (FileManager)

실제 파일 및 디렉터리 조작을 담당하는 핵심 클래스입니다. 현재 경로 정보를 추적하고, 파일 생성, 삭제, 이동, 복사, 목록 조회 등의 기능을 제공합니다.

package kFile

import java.io.File
import java.io.IOException
import java.text.DecimalFormat

class FileManager(private val notifier: CallBack) : FileOperationListener {
    private var currentPath = "/Users/sifeng/Desktop"
    var currentDirName = "Desktop"
    private val pathStack = mutableListOf<String>()

    override fun listFiles() {
        val dir = File(currentPath)
        dir.listFiles()?.forEach { file ->
            notifier.notify(file.name)
        }
    }

    override fun listWithSize() {
        val dir = File(currentPath)
        val df = DecimalFormat("#.###")
        dir.listFiles()?.forEach { file ->
            val sizeMB = file.length().toDouble() / (1024 * 1024)
            val formattedSize = df.format(sizeMB)
            notifier.notify("${file.name}     ${formattedSize} MB")
        }
    }

    override fun showCurrentPath() {
        notifier.notify(currentPath)
    }

    override fun createDirectory(name: String) {
        val dir = File(currentPath, name)
        if (dir.mkdirs()) {
            notifier.notify("디렉터리 생성 완료")
        } else {
            notifier.notify("디렉터리 생성 실패")
        }
    }

    override fun createFile(name: String) {
        val file = File(currentPath, name)
        if (file.createNewFile()) {
            notifier.notify("파일 생성 완료")
        } else {
            notifier.notify("파일 생성 실패")
        }
    }

    override fun changeDirectory(name: String) {
        pathStack.add(name)
        currentPath = "$currentPath/$name"
        currentDirName = "$currentDirName/$name"
    }

    override fun goUp() {
        if (pathStack.isNotEmpty()) {
            val lastPart = pathStack.removeAt(pathStack.size - 1)
            val removeLen = lastPart.length + 1
            currentPath = currentPath.substring(0, currentPath.length - removeLen)
            currentDirName = currentDirName.substring(0, currentDirName.length - removeLen)
        }
    }

    override fun displayContent(filename: String) {
        val file = File(currentPath, filename)
        if (file.exists()) {
            try {
                notifier.notify(file.readText())
            } catch (e: IOException) {
                notifier.notify("파일 읽기 중 오류 발생")
            }
        } else {
            notifier.notify("파일이 존재하지 않습니다.")
        }
    }

    override fun remove(filename: String) {
        val file = File(currentPath, filename)
        if (file.exists()) {
            if (deleteRecursively(file)) {
                notifier.notify("삭제 성공")
            } else {
                notifier.notify("삭제 실패")
            }
        } else {
            notifier.notify("해당 파일이나 디렉터리가 존재하지 않습니다.")
        }
    }

    private fun deleteRecursively(file: File): Boolean {
        if (file.isDirectory) {
            file.listFiles()?.forEach { child ->
                deleteRecursively(child)
            }
        }
        return file.delete()
    }

    override fun move(source: String, destination: String) {
        val srcFile = File(currentPath, source)
        val destDir = File(currentPath, destination)
        val destFile = File(destDir, source)
        if (srcFile.renameTo(destFile)) {
            notifier.notify("이동 완료")
        } else {
            notifier.notify("이동 실패")
        }
    }

    override fun rename(oldName: String, newName: String) {
        val oldFile = File(currentPath, oldName)
        val newFile = File(currentPath, newName)
        if (oldFile.renameTo(newFile)) {
            notifier.notify("이름 변경 완료")
        } else {
            notifier.notify("이름 변경 실패")
        }
    }

    override fun copy(source: String, destination: String) {
        val src = File(currentPath, source)
        val destDir = File(currentPath, destination)
        val dest = File(destDir, source)
        try {
            copyRecursively(src, dest)
            notifier.notify("복사 완료")
        } catch (e: IOException) {
            notifier.notify("복사 실패: ${e.message}")
        }
    }

    private fun copyRecursively(src: File, dest: File) {
        if (src.isDirectory) {
            if (!dest.exists()) dest.mkdirs()
            src.listFiles()?.forEach { child ->
                val childDest = File(dest, child.name)
                copyRecursively(child, childDest)
            }
        } else {
            src.copyTo(dest, overwrite = true)
        }
    }
}

파일 작업 인터페이스 (FileOperationListener)

파일 시스템 조작의 모든 행동을 정의하는 추상화된 인터페이스로, 클래스 간의 결합도를 낮추고 확장성을 높입니다.

package kFile

interface FileOperationListener {
    fun listFiles()
    fun listWithSize()
    fun showCurrentPath()
    fun createDirectory(name: String)
    fun createFile(name: String)
    fun changeDirectory(name: String)
    fun goUp()
    fun displayContent(filename: String)
    fun remove(filename: String)
    fun move(source: String, destination: String)
    fun rename(oldName: String, newName: String)
    fun copy(source: String, destination: String)
}

실행 시작

애플리케이션은 단일 진입점에서 시작되며, 싱글턴 인스턴스를 통해 전체 시스템을 활성화합니다.

package kFile

fun main() {
    CommandLineTool.instance.start()
}

태그: Kotlin 파일 시스템 명령어 라인 싱글턴 패턴 파일 처리

5월 28일 02:49에 게시됨