코틀린 최종 프로젝트로 카테고리별로 메모를 관리하는 프로그램을 구현하는 것이다. 간단하게 프로그램 실행 과정을 설명하자면 비밀번호를 설정한 후 로그인을 한다. 로그인을 성공하면 메인 메뉴 화면으로 이동하게 되고 카테고리 관리, 메모 관리, 메모 전체 보기, 종료을 선택할 수 있는 메뉴들이 있다. 이러한 메뉴를 선택하여 카테고리 생성, 삭제, 수정 그리고 카테고리별 메모 생성,삭제 수정을 할 수 있는 프로그램이다.
private var password = ""
private var verifyPassword = ""
lateinit var passwordData: PasswordClass
private val passwordFile = File("PasswordSetting.password")
비밀번호 관리하는 PasswordSetting.password 파일 생성하기 위한 변수
data class PasswordClass(val value: String, val isSetting: Boolean = false) : Serializable
비밀번호를 객체로 관리하기 위해 Serializable 사용
fun inputPassword() {
while (true) {
try {
println()
print("설정할 비밀번호를 입력해주세요 : ")
password = scanner.next()
print("한번 더 입력해주세요 : ")
verifyPassword = scanner.next()
if (checkPassword()) {
setPassword()
mainClass.loginState = LoginState.LOGIN_STATE_SET_PASSWORD
return
} else {
println("패스워드가 일치하지 않습니다. 다시 입력해주세요")
}
} catch (e: Exception) {
println("패스워드를 잘못 입력하였습니다")
}
}
}
fun checkPassword(): Boolean = password == verifyPassword
비밀번호를 입력하고 다시 비밀번호를 입력하여 확인
fun existPasswordFile(): Boolean {
if (passwordFile.exists()) {
val fis = FileInputStream(passwordFile)
val ois = ObjectInputStream(fis)
try {
passwordData = ois.readObject() as PasswordClass
return true
} catch (e: Exception) {
fis.close()
ois.close()
}
}
return false
}
객체로 저장된 파일에서 데이터를 가져오기 위해
FileInputStream와 ObjectInputStream
사용
fun setPassword() {
val fos = FileOutputStream(passwordFile)
val oos = ObjectOutputStream(fos)
passwordData = PasswordClass(password, true)
oos.writeObject(passwordData)
fos.flush()
fos.close()
oos.close()
}
FileOutputStream와 ObjectOutputStream
를 사용하여 비밀번호 설정
enum class LoginState {
LOGIN_STATE_NOT_SET_PASSWORD,
LOGIN_STATE_SET_PASSWORD,
LOGIN_STATE_SUCCESS
}
fun validateLogin() {
while (true) {
try {
println()
print("로그인 하시려면 비밀 번호를 입력해주세요 : ")
val inputPassword = scanner.next()
if (passwordData.value == inputPassword) {
mainClass.loginState = LoginState.LOGIN_STATE_SUCCESS
return
} else {
println("비밀번호를 잘못 입력하였습니다")
}
} catch (e: Exception) {
println("비밀번호를 잘못 입력하였습니다")
}
}
}
파일에 저장된 비밀번호 확인 후 성공 시 LOGIN_STATE_SUCCESS 상태로 바꾼다
처음 입력한 비밀번호와 다시 입력한 비밀번호를 비교하여 서로 같다면 passwordSetting.password
파일이 생성되고 파일 안에 비밀번호와 설정여부
데이터가 저장된다.
처음에는 파일 생성 여부에 따라 로그인 입력 화면으로 이동하도록 구현할 생각이였지만 파일이 생성될 가능성이 있기 때문에 파일을 생성하고 비밀번호가 설정했는지 여부를 판단하여 로그인 할 수 있도록 구현하였다.
저장된 비밀번호 파일을 통해 설정한 비밀번호를 비교하여 로그인 여부를 판단한 후 성공하면 메인 메뉴로 이동하게 된다.
fun inputMainMenuNumber(): Int {
while (true) {
try {
println()
println("1. 메모 카테고리 관리")
println("2. 메모 카테고리 선택")
println("3. 메모 내용 전체 보기")
println("4. 종료")
print("메뉴를 선택해주세요 : ")
val menuItemTemp = scanner.next()
scanner.nextLine()
val menuItem = menuItemTemp.toInt()
if (menuItem !in 1..4) {
println("메인 메뉴를 잘못 입력하였습니다.")
} else {
return menuItem
}
} catch (e: Exception) {
println("메인 메뉴를 잘못 입력하였습니다.")
}
}
}
enum class MainMenuItem(val itemNumber: Int) {
// 메모 카테고리 관리
MAIN_MENU_ITEM_MANAGE_MEMO_CATEGORY(1),
// 메모 카테고리 선택
MAIN_MENU_ITEM_SELECT_MEMO_CATEGORY(2),
// 메모 내용 전체 보기
MAIN_MENU_ITEM_SHOW_ALL_MEMO(3),
// 프로그램 종료
MAIN_MENU_ITEM_EXIT(4)
}
메인 메뉴 아이템 선택하는 부분에서 매직넘버를 피하기 위해 enum class 사용
💡 매직넘버(Magic Number)
를 피해라
매직넘버
란 코드 내에서 하드코딩된 상수 값을 의미한다. 따라서 코드를 읽기 쉽게 하고 유지 보수를 위해 매직넘버를 피해야 한다.
fun createCategory(categoryName: String) {
val fileName = "$categoryName.category"
val file = File(fileName)
if (file.exists()) {
throw Exception("존재하는 카테고리입니다. 다시 등록해주세요.")
} else {
file.createNewFile()
}
}
파일 존재 여부 판단하여 존재 하지 않을 경우
createNewFile()
를 사용하여 카테고리 파일 생성
fun deleteCategory() {
while (true) {
try {
println()
print("삭제할 카테고리 번호를 입력해주세요 : ")
val categoryNumberTemp = scanner.next()
scanner.nextLine()
val categoryNumber = categoryNumberTemp.toInt()
if (mainClass.categoryList.isEmpty()) {
println("등록된 카테고리가 없어 삭제할 수 없습니다")
return
} else if (categoryNumber !in 1..mainClass.categoryList.size) {
println("삭제할 카테고리 번호를 잘못 입력하였습니다.")
} else {
val fileName = "${mainClass.categoryList[categoryNumber - 1]}.category"
val file = File(fileName)
file.delete()
return
}
} catch (e: Exception) {
println("삭제할 카테고리 번호를 잘못 입력하였습니다.")
}
}
}
삭제할 카테고리를 선택하여
delete()
를 사용하여 파일 삭제
fun updateCategory() {
while (true) {
try {
println()
print("수정할 카테고리 번호를 입력해주세요 : ")
val categoryNumberTemp = scanner.next()
scanner.nextLine()
val categoryNumber = categoryNumberTemp.toInt()
if (mainClass.categoryList.isEmpty()) {
println("카테고리를 등록해주세요")
return
} else if (categoryNumber !in 1..mainClass.categoryList.size) {
println("수정할 카테고리 번호를 잘못 입력하였습니다.")
} else {
renameCategory(categoryNumber)
return
}
} catch (e: Exception) {
println("수정할 카테고리 번호를 잘못 입력하였습니다")
}
}
}
// 카테고리 이름 수정
private fun renameCategory(categoryNumber: Int) {
val originalCategoryName = mainClass.categoryList[categoryNumber - 1]
print("${originalCategoryName} -> ")
val newCategoryName = scanner.nextLine()
val fileName = "${mainClass.categoryList[categoryNumber - 1]}.category"
val file = File(fileName)
val newFileName = "${newCategoryName}.category"
file.renameTo(File(newFileName))
mainClass.categoryList[categoryNumber - 1] = newCategoryName
}
수정할 카테고리를 선택하여
renameTo()
를 사용하여 파일 이름 수정
fun inputCategoryListNumber(): Int {
while (true) {
try {
println()
mainClass.getCategoryList()
print("선택할 카테고리 번호를 입력해주세요(0. 이전) : ")
val categoryNumberTemp = scanner.next()
scanner.nextLine()
val categoryNumber = categoryNumberTemp.toInt()
if (mainClass.categoryList.isEmpty()) {
println("카테고리를 등록해주세요")
return 0
} else if (categoryNumber !in 0..mainClass.categoryList.size) {
println("선택할 카테고리 번호를 잘못 입력하였습니다.")
} else {
return categoryNumber
}
} catch (e: Exception) {
println("선택할 카테고리 번호를 잘못 입력하였습니다.")
}
}
}
enum class CategoryListState {
CATEGORY_LIST_STATE_SELECT_CATEGORY,
CATEGORY_LIST_STATE_SHOW_CATEGORY_MENO,
}
카테고리를 선택하는 상태
CATEGORY_LIST_STATE_SELECT_CATEGORY
카테고리를 선택한 상태
CATEGORY_LIST_STATE_SHOW_CATEGORY_MENO
fun inputMemoMenuNumber(): Int {
val fileName = "${mainClass.categoryList[mainClass.selectedCategoryNumber - 1]}.category"
mainClass.getMemoData(fileName)
while (true) {
try {
println()
if (mainClass.memoList.isEmpty()) {
println("등록된 메모가 없습니다.")
println()
} else {
for (idx in 1..mainClass.memoList.size) {
println("$idx : ${mainClass.memoList[idx - 1].title}")
}
println()
}
print("1. 메모보기, 2. 메모등록, 3. 메모수정, 4.메모삭제, 5. 이전 : ")
val memoMenuItemTemp = scanner.next()
scanner.nextLine()
val menuItem = memoMenuItemTemp.toInt()
if (menuItem !in 1..5) {
println("메모 메뉴를 잘못 입력하였습니다.")
} else {
return menuItem
}
} catch (e: Exception) {
println("메모 메뉴를 잘못 입력하였습니다.")
}
}
}
선택한 카테고리에 저장된 메모 관리
enum class MemoMenuItem(val itemNumber: Int) {
// 메모 보기
MEMO_MENU_ITEM_SHOW_MEMO(1),
// 메모 등록
MEMO_MENU_ITEM_CREATE_MEMO(2),
// 메모 수정
MEMO_MENU_ITEM_UPDATE_MEMO(3),
// 메모 삭제
MEMO_MENU_ITEM_DELETE_MEMO(4),
// 이전
MEMO_MENU_ITEM_BACK(5),
}
메모 메뉴에서 해당하는 아이템의 의미를 주어 매직넘버를 피한다
fun showMemo() {
while (true) {
try {
println()
print("확인할 메모의 번호를 입력해주세요 (0. 이전) : ")
val memoNumberTemp = scanner.next()
scanner.nextLine()
val memoNumber = memoNumberTemp.toInt()
if (memoNumber == 0) {
return
} else if (memoNumber !in 1..mainClass.memoList.size) {
println("확인할 메모의 번호를 잘못 입력하였습니다.")
} else {
val memo = mainClass.memoList[memoNumber - 1]
println()
println("제목 : ${memo.title}")
println("내용 : ${memo.content}")
print("이전으로 돌아가려면 0을 입력해주세요 : ")
val numberTemp = scanner.next()
scanner.nextLine()
val number = numberTemp.toInt()
if (number == 0) {
return
}
}
} catch (e: Exception) {
println("잘못 입력해주세요.")
}
}
}
MainClass에서 memoList에서 선택한 메모의 객체를 가져와서 보여준다
fun createMemo(title: String, content: String) {
val fileName = "${mainClass.categoryList[mainClass.selectedCategoryNumber - 1]}.category"
val file = File(fileName)
mainClass.getMemoData(fileName)
val memo = MainClass.Memo(title, content)
mainClass.memoList.add(memo)
writeMemo(file)
}
private fun writeMemo(file: File) {
val fos = FileOutputStream(file)
val oos = ObjectOutputStream(fos)
for (memo in mainClass.memoList) {
oos.writeObject(memo)
}
fos.flush()
fos.close()
oos.close()
}
Memo data class
를 memoList에 담고writeMemo()
메서드를 통해 카레고리 파일에 메모 데이터를 덮어씌운다.
fun updateMemo() {
while (true) {
try {
println()
print("수정할 메모의 번호를 입력해주세요 (0. 이전) : ")
val memoNumberTemp = scanner.next()
scanner.nextLine()
val memoNumber = memoNumberTemp.toInt()
if (memoNumber == 0) {
return
} else if (mainClass.memoList.isEmpty()) {
println("메모를 등록해주세요")
} else if (memoNumber !in 1..mainClass.memoList.size) {
println("수정할 메모 번호를 잘못 입력하였습니다.")
} else {
editMemo(memoNumber)
return
}
} catch (e: Exception) {
println("수정할 메모 번호를 잘못 입력하였습니다")
}
}
}
private fun editMemo(memoNumber: Int) {
val originalMemo = mainClass.memoList[memoNumber - 1]
var newMemoTitle: String
var newMemoContent: String
println()
println("제목 : ${originalMemo.title}")
print("메모의 새로운 제목을 입력해주세요(0 입력시 무시합니다.) : ")
newMemoTitle = scanner.nextLine()
if (newMemoTitle == "0") {
newMemoTitle = originalMemo.title
}
println("내용 : ${originalMemo.content}")
print("메모의 새로운 내용을 입력해주세요(0 입력시 무시합니다.) : ")
newMemoContent = scanner.nextLine()
if (newMemoContent == "0") {
newMemoContent = originalMemo.content
}
val fileName = "${mainClass.categoryList[mainClass.selectedCategoryNumber - 1]}.category"
val file = File(fileName)
for (idx in 0 until mainClass.memoList.size) {
if (mainClass.memoList[idx] == originalMemo) {
mainClass.memoList[idx] = MainClass.Memo(newMemoTitle, newMemoContent)
}
}
writeMemo(file)
}
수정한 부분을 memoList에서 해당하는 메모로 바꾸고 카테고리 파일에 덮어씌운다
fun deleteMemo() {
while (true) {
try {
println()
print("삭제할 메모 번호를 입력해주세요 (0. 이전) : ")
val memoNumberTemp = scanner.next()
scanner.nextLine()
val memoNumber = memoNumberTemp.toInt()
if (memoNumber == 0) {
return
} else if (mainClass.memoList.isEmpty()) {
println("등록된 메모 없습니다.")
} else if (memoNumber !in 1..mainClass.memoList.size) {
println("삭제할 메모 번호를 잘못 입력하였습니다.")
} else {
val fileName = "${mainClass.categoryList[mainClass.selectedCategoryNumber - 1]}.category"
val file = File(fileName)
mainClass.memoList.removeAt(memoNumber - 1)
writeMemo(file)
return
}
} catch (e: Exception) {
println("삭제할 메모 번호를 잘못 입력하였습니다.")
}
}
}
선택한 메모 번호를
removeAt()
를 사용하여 삭제한다
fun showCategoryMemos() {
for (categoryName in mainClass.categoryList) {
println("--------------------")
println(categoryName)
println("--------------------")
mainClass.getMemoData("$categoryName.category")
if (mainClass.memoList.isEmpty()) {
println("등록된 메모가 없습니다")
} else {
for (memo in mainClass.memoList) {
println()
println("제목 : ${memo.title}")
println("내용 : ${memo.content}")
}
}
println()
}
}
categoryList
에 저장된 카테고리 파일명을getMemoData()
메서드를 통해 해당하는 카테고리에 저장된 메모를 출력
ProgramState.PROGRAM_STATE_EXIT -> {
println("프로그램이 종료되었습니다")
exitProcess(0)
}
exitProcess(0)
사용하여 프로그램 종료