의존성 역전 원칙 계산기 : lv4 과제 제출 코드

박미소·2023년 12월 10일
0

코틀린

목록 보기
2/44
  • 의존성 역전 원칙(Dependency Inversion Principle)

    의존성 역전 원칙은 서로 다른 클래스 혹은 모듈 간의 결합도와 관련이 있다. 상위 레벨의 클래스가 하위 레벨(세부 구현) 에 의존적이지 않아야 한다. 인터페이스와의 의존성 제거(역전) 을 기본 전제로 두고 있다. 클래스 혹은 메소드를 사용하는 다른 클래스에 대한 종속성을 최소화해야 한다. 자식 클래스에서의 번경이 부모 클래스에게 영향을 주어서는 안된다.

  • 내가 짠 코드는 인터페이스 with Enum class

    Enum은 Enumeration을 줄여서 사용하는 단어로 한국말로 열거형이라고 표현한다. 프로그램 내에서 특정한 특질이나 속성을 분류해서 사용하는 값들을 나열할 때 쓰인다. 즉, 상수들을 나열해 놓은 것을 열거형Enum이라고 표현한다. 일반적으로 프로그램을 개발할 때 상수(Constant)로 지정해서 사용하면 좋은 값들이 있다. 예를 들어 최대 값과 같이 값(리터럴)자체가 의미를 가지고 있는 경우이다. Enum은 valueOf() 함수를 통해 값을 찾을 수 있다. valueOf() 메소드는 Enum이 제공하는 강력한 기능이기에 유용하게 사용할 수 있다.



Enum with Interface - Enum 활용 방법(과제에 참고했던 부분)


객체지향 프로그래밍에서 중요한 인터페이스인데, Enum은 클래스이기 때문에 인터페이스를 사용해 오버라이딩 할 수 있다. Enum을 정의하고 메소드를 가지게 하는 방법이다.

Enum은 인터페이스를 상속받을 수 있기 때문에 값 외에도 함수도 포함할 수 있는 장점이 있다.


interface OperatorExecutor {
    fun apply(num1: Int, num2: Int): Int
}

enum class Operator(val symbol: String) : OperatorExecutor {
    PLUS("+") {
        override fun apply(num1: Int, num2: Int): Int {
            return num1.plus(num2)
        }
    },
    MINUS("-") {
        override fun apply(num1: Int, num2: Int): Int {
            return num1.minus(num2)
        }
    };

    companion object {
        fun symbolOf(symbol: String): Operator {
            return values()
                    .firstOrNull{it.symbol == symbol}
                    ?: throw NoSuchElementException("정의되지 않은 기호")
        }
    }
}

fun main() {
    println(Operator.symbolOf("+").apply(1, 1))
    // 2

    println(Operator.symbolOf("-").apply(1, 1))
    // 0
}

OperatorExcutor 인터페이스를 상속한 Operator는 연산 기호(+,-)에 따라 연산식을 구현하도록 정의했다. valueOf()는 문자 값(PLUS, MINUS)으로 Operator를 찾기 때문에 기호로 Operator를 찾을 수 있는 symbolOf() 함수도 추가해 Operator의 활용성을 높였다.

출처: https://7942yongdae.tistory.com/185




계산기 과제 제출 코드




  • AbstractOperation.kt
// 독립된 하나의 기능 뼈대만 만듦.
interface AbstractOperation {
    fun operate(num1: Int, num2: Int): Int
}
  • Calculator.kt
package com.example.calculator4

enum class Calculator(val symbol: String): AbstractOperation {
    PLUS("+") {
        override fun operate(num1: Int, num2: Int): Int {
            return num1.plus(num2)
        }
    },
    MINUS("-") {
        override fun operate(num1: Int, num2: Int): Int {
            return num1.minus(num2)
        }
    },
    MULTIPLY("*") {
        override fun operate(num1: Int, num2: Int): Int {
            return num1.times(num2)
        }
    },
    DIVIDE("/") {
        override fun operate(num1: Int, num2: Int): Int {
            return num1.div(num2)
        }
    };

    companion object {
        fun symbolOf(symbol: String): Calculator {
                return values()
                .firstOrNull(){it.symbol == symbol}
                ?: throw NoSuchFieldException("정의되지 않은 기호")

        }
    }
}
fun main() {
    println(Calculator.symbolOf("+").operate(1,4))
    println(Calculator.symbolOf("-").operate(4,1))
    println(Calculator.symbolOf("*").operate(10,5))
    println(Calculator.symbolOf("/").operate(4,2))
	println(Calculator.symbolOf)
}
// Const 상수로 PLUS = "+"(symbol) 값 지정.
// Calculator.PLUS.symbol => +
// Calculator.DIVIDE => DIVIDE
// 인터페이스 AbstractOperation을 상속한 Calculator는 연산 기호에 따라 연산식을 구현한다.
// var value = Calculator.values() //[Lcom.example.calculator4.Calculator;@6aaa5eb0
// println(value[0]) // PLUS
// println(Calculator.valueOf("PLUS")) // PLUS
// println(Calculator.symbolOf("+")) // PLUS
// operate를 찾을 수 있는 symbolOf() 함수도 추가해 Calculator의 활용성을 높였다.

동행 객체 부분 설명:

companion object: 이 부분은 Calculator 클래스의 동반 객체를 선언한다. 이 동반 객체는 클래스의 인스턴스를 생성하지 않아도 호출할 수 있는 메소드나 프로퍼티를 가질 수 있다.

symbolOf(symbol: String): Calculator: 동반 객체 내에서 선언된 함수로, 주어진 기호(symbol)에 해당하는 Calculator 열거형 상수를 반환한다.

values(): 열거형 클래스의 모든 상수를 배열로 반환하는 함수이다. 여기서는 Calculator의 모든 상수를 배열로 얻어온다.

.firstOrNull { it.symbol == symbol }: 배열에서 조건을 만족하는 첫 번째 요소를 찾거나, 없으면 null을 반환한다. 여기서는 주어진 기호(symbol)와 일치하는 Calculator 상수를 찾는다.

?: throw NoSuchFieldException("정의되지 않은 기호"): firstOrNull이 null을 반환하면, NoSuchFieldException을 던져서 해당 기호가 정의되지 않았음을 나타낸다.

따라서, Calculator.symbolOf("+")와 같이 사용할 수 있어서 주어진 기호에 해당하는 Calculator 열거형 상수를 얻을 수 있다.

ask to chatgpt -> 이것이 클래스 간 의존성 역전 원칙에 해당 되나?
의존성 역전 원칙(Dependency Inversion Principle, DIP)은 SOLID 원칙 중 하나로, 고수준 모듈은 저수준 모듈에 의존해서는 안 되며, 둘 모두 추상화에 의존해야 한다는 원칙이다.
여기서는 Calculator enum class가 상위 수준(high-level) 모듈에 해당하며, operate 함수가 추상화에 해당한다. 각각의 연산자(PLUS, MINUS, 등)는 추상화에 따라 구체적인 구현을 제공하고 있다.
이 코드에서는 Calculator가 추상화에 의존하고 있으며, 각각의 연산자는 이 추상화를 구체화하고 있다. 따라서 코드는 의존성 역전 원칙을 따르고 있다.


  • main.kt
package com.example.calculator4

import java.lang.Exception

fun main() {
    //계산기 시작
    while (true) {
        println("\n<계산기 START>")

        println("첫번째 숫자를 입력하세요.")
        try {
            var choiceFirstNum = readLine()!!.toDouble()

            println("원하는 연산기호를 선택해주세요.\n1. +\n2. -\n3. * \n4. /")
            println("번호를 고르세요:")
            var choiceNum: Int = readLine()!!.toInt()
            if (choiceNum == 0) {
                return break
            }

            var mySymbol: String = inputSymbolInfo(choiceNum).toString()

            println("두번째 숫자를 입력하세요.")
            var choiceSecondNum = readLine()!!.toDouble()

            var myFirstNum: Double = inputNumbers(choiceFirstNum)
            var mySecondNum: Double = inputNumbers(choiceSecondNum)

            displayCalculation(myFirstNum, mySecondNum, mySymbol)
            println("\n다시 계산을 실행하겠습니까? 1. 실행  2. 종료")

            var selectRestart: Int = readLine()!!.toInt()
            if (selectRestart == 1) {
                continue
            } else if (selectRestart == 2) {
                println("<계산기 END>")
                break
            } else {
                println("입력 오류로 재실행됩니다.")
            }
        } catch (e: Exception) {
            println("입력 오류로 재실행됩니다.")
            continue
        }
    }
}

fun inputSymbolInfo(choiceNum: Int): Any? {
            return when(choiceNum) {
                1 -> Calculator.PLUS.symbol
                2 -> Calculator.MINUS.symbol
                3 -> Calculator.MULTIPLY.symbol
                4 -> Calculator.DIVIDE.symbol
                else -> return null
            }
        }

fun inputNumbers(number: Double): Double {
    return number
}


fun displayCalculation(myFirstNum:Double, mySecondNum:Double, mySymbol:String){
    var preResult = Calculator.symbolOf(mySymbol).operate(myFirstNum,mySecondNum)
    var result = String.format("%.2f",preResult)  //소수점 둘째자리까지. 셋째자리에서 반올림 함.
    println("==계산 결과==")
    println("${myFirstNum} ${mySymbol} ${mySecondNum} = ${result}")

}


valueOf( ) - companion object 내에서 사용함

간단한 valueOf() 예시

상수 값을 괄호 안에 쓴다. value값 찾는 두가지 방법.

enum class Numbers(val value: Int) {
	MAX(9),    
    MIN(0);
}

fun main() {
	println(Numbers.MAX.value)
    // 9 - Numbers 클래스의 프로퍼티 Max의 상수 값에 직접 접근(.)
    
	val maxNumber = Numbers.valueOf("MAX")
    println(maxNumber.value)
    // 9 - valueOf() 메소드 안 프로퍼티를 지정해 상수 값 찾기
    println(maxNumber.name)
    // Max
    println(maxNumber)
    // MAX
}

Enum은 클래스이기 떄문에 예시처럼 프로퍼티를 정의해 값을 가질 수 있게 할 수 있다. 정의한 Enum 값은 직접 값(value)에 접근할 수도 있고, valueOf() 메소드를 사용해 값을 찾아 쓸 수도 있다.


enum class Direction {
    NORTH, SOUTH, WEST, EAST
}


// EnumClass is the name of enum class
EnumClass.valueOf(value: String): EnumClass
EnumClass.values(): Array<EnumClass>

println(Direction.valueOf("NORTH"))
println(Direction.values().joinToString())

// output:
// NORTH
// NORTH, SOUTH, WEST, EAST

Enum class 는 정의된 enum constant 를 리스트화 할수 있고 이름으로 enum constant 를 얻을 수 있는 synthetic methods 를 가진다.

출처: https://iosroid.tistory.com/66



계산기에 사용한 String.format() 메소드

fun main() {
	val dNum:Double = 454.14600

	println(String.format("%.0f", dNum/100).toDouble() * 100)	// 500.0
	println(String.format("%.2f", dNum))	// 454.15
	println(String.format("%.8f", dNum))	// 454.14600000
}

float타입 변수를 format 표현식으로 자유롭게 가공한다. 소숫점의 자릿수를 정할수도 있고 반올림 할 자릿수를 정할 수도 있다. String.format()메소드의 리턴타입은 String이다. 숫자로 이루어진 문자열.

출처: https://meoru-tech.tistory.com/58

0개의 댓글