의존성 역전 원칙(Dependency Inversion Principle)
의존성 역전 원칙은 서로 다른 클래스 혹은 모듈 간의 결합도와 관련이 있다. 상위 레벨의 클래스가 하위 레벨(세부 구현) 에 의존적이지 않아야 한다. 인터페이스와의 의존성 제거(역전) 을 기본 전제로 두고 있다. 클래스 혹은 메소드를 사용하는 다른 클래스에 대한 종속성을 최소화해야 한다. 자식 클래스에서의 번경이 부모 클래스에게 영향을 주어서는 안된다.
내가 짠 코드는 인터페이스 with Enum class
Enum은 Enumeration을 줄여서 사용하는 단어로 한국말로 열거형이라고 표현한다. 프로그램 내에서 특정한 특질이나 속성을 분류해서 사용하는 값들을 나열할 때 쓰인다. 즉, 상수들을 나열해 놓은 것을 열거형Enum이라고 표현한다. 일반적으로 프로그램을 개발할 때 상수(Constant)로 지정해서 사용하면 좋은 값들이 있다. 예를 들어 최대 값과 같이 값(리터럴)자체가 의미를 가지고 있는 경우이다. Enum은 valueOf() 함수를 통해 값을 찾을 수 있다. valueOf() 메소드는 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
// 독립된 하나의 기능 뼈대만 만듦.
interface AbstractOperation {
fun operate(num1: Int, num2: Int): Int
}
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가 추상화에 의존하고 있으며, 각각의 연산자는 이 추상화를 구체화하고 있다. 따라서 코드는 의존성 역전 원칙을 따르고 있다.
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() 예시
상수 값을 괄호 안에 쓴다. 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
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이다. 숫자로 이루어진 문자열.