package com.example.mycalculator
class AddOperation {
fun addOperation(num1 : Double,num2 : Double) = num1 + num2
}
package com.example.mycalculator
class SubtractOperation {
fun substractOperation(num1 : Double,num2 : Double) = num1 - num2
}
package com.example.mycalculator
class MultiplyOperation {
fun multiplyOperation(num1 : Double,num2 : Double) = num1 * num2
}
package com.example.mycalculator
class DivideOperation {
fun divideOperation(num1 : Double,num2 : Double) = num1 / num2
}
package com.example.mycalculator
class Calculator {//각 클래스의 메서드를 사용하는 역할만 하고 있다.
fun addOperation(num1: Double, num2: Double) : Double = AddOperation().addOperation(num1, num2)
fun subtractOperation(num1: Double, num2: Double) = SubtractOperation().substractOperation(num1, num2)
fun multiplyOperation(num1: Double, num2: Double) = MultiplyOperation().multiplyOperation(num1, num2)
fun divideOperation(num1: Double, num2: Double) = DivideOperation().divideOperation(num1, num2)
}
package com.example.mycalculator
fun main() {
var select : Int = 0
var calc : Double = 0.0
var num1 : Double = 0.0
var operator : String = ""
var num2 : Double = 0.0
while(select != 3) {
println("")
println("원하는 계산을 선택하세요")
println("[1] 새로 계산하기 [2] 이어서 계산하기 [3] 그만두기 ")
select = readLine()!!.toInt()
if(select == 1) {
println("첫번째 수를 입력하세요.")
num1 = readLine()!!.toDouble()
println("수행할 연산자를 고르세요 : + - * /")
} else if (select == 2) {
num1 = calc
println("")
println("첫번째 수는 $calc 입니다.")
println("")
println("추가로 수행할 연산자를 고르세요 : + - * /")
} else if (select ==3) {
println("계산기를 종료합니다.")
break
}
operator = readLine()!!
println("두번째 수를 입력하세요.")
num2 = readLine()!!.toDouble()
when(operator) {
"+" -> {
calc = Calculator().addOperation(num1,num2)
}
"-" -> {
calc = Calculator().subtractOperation(num1,num2)
}
"*" -> {
calc = Calculator().multiplyOperation(num1,num2)
}
"/" -> {
if(num2 == 0.0) {
println("0으로 나눠지지 않습니다.")
} else {
calc = Calculator().divideOperation(num1, num2)
}
}
}
println("${num1} ${operator} ${num2} = ${calc} 입니다.")
}
}
결합도 : 하나의 클래스가 다른 클래스와 얼마나 많이 연결 되어 있는지에 대한 표현.
결합도가 낮다. = 조금 연결되었다.
=> 결합도가 낮은 프로그램일 수록 유지보수가 쉬워진다.
응집도 : 클래스에 포함된 내부 요소들이 하나의 책임/목적을 위해 연결되어있는 연관된 정도.
응집도가 높다 = 변경대상/범위가 명확해진다.
=> 응집도가 높은 프로그램일수록 유지보수가 쉬워진다.
위의 3가지의 정의들을 놓고 생각해 봤을 때, Lv2의 Class Calculator는 혼자서 사칙연산의 기능을 전부 책임지고 있기 때문에 응집도는 높으나, 단일 책임 원칙에는 위배되고 있었다.
단일 책임 원칙을 지키는 것은 그렇게 어렵지 않다. 클래스 당 하나의 기능만을 구현하면 되기 때문이다. 이렇게 분리 시켜 놓은 기능들은 변화가 필요할 때 수정을 하면 다른 부분에 영향이 적게 갈 것이다.
Lv3에서 단일 책임 원칙에 의해 각각의 연산 기능들을 분리해서 결합도를 낮췄다. 이렇게 결합도를 낮추면 지금은 기능이 복잡하지 않기 때문에, 문제가 생겼거나 변화를 줘야할 때 쉽게 찾아서 유지보수가 가능하지만, 더 많은 기능들이 생긴다면, 흩어져 있는 기능들을 일일이 찾아가며 수정을 해야할 것이다.
그래서 Class Calculator를 통해 높은 응집도를 유지하면서도, 각 기능을 분산시켜 결합도를 낮춰서 유지보수하기에 유리하도록 만들 수 있게 되었다.
package com.example.mycalculator
abstract class AbstractOperation {//추상클래스
abstract fun operation(num1 : Double, num2 :Double) :Double
}
package com.example.mycalculator
class AddOperation : AbstractOperation() {//사칙연산클래스 더하기
override fun operation(num1: Double, num2: Double): Double {
return num1 + num2
}
}
package com.example.mycalculator
class SubtractOperation : AbstractOperation() {//사칙연산클래스 빼기
override fun operation(num1: Double, num2: Double): Double {
return num1 - num2
}
}
package com.example.mycalculator
class MultiplyOperation : AbstractOperation() {//사칙연산클래스 곱하기
override fun operation(num1: Double, num2: Double): Double {
return num1 * num2
}
}
package com.example.mycalculator
class DivideOperation : AbstractOperation() {//사칙연산클래스 나누기
override fun operation(num1: Double, num2: Double): Double {
return num1 / num2
}
}
package com.example.mycalculator
class Calculator(private val operation: AbstractOperation) {
//상위모듈인 AbstractOperation을 의존하는 Calculator클래스
//어쨌든 새로 만든 펑션이므로 생성자 설정과 반환타입 설정을 잘 해줘야한다.
fun operation(num1 : Double,num2 : Double) : Double {
return operation.operation(num1, num2)
}
}
package com.example.mycalculator
fun main() {
var select : Int = 0
var calc : Double = 0.0
var operator : String = ""
var num1 : Double = 0.0
var num2 : Double = 0.0
//사칙연산 클래스를 변수에 담았다.
val add = AddOperation()
val subtract = SubtractOperation()
val multiply = MultiplyOperation()
val divide = DivideOperation()
//변수에 담은 사칙연산 클래스를 Calculator에 주입해서 사용하도록 했다.
val addOperator = Calculator(add)
val subtractOperator = Calculator(subtract)
val multiplyOperator = Calculator(multiply)
val divideOperator = Calculator(divide)
while(select != 3) {
println("")
println("원하는 계산을 선택하세요")
println("[1] 새로 계산하기 [2] 이어서 계산하기 [3] 그만두기 ")
select = readLine()!!.toInt()
if(select == 1) {
println("첫번째 수를 입력하세요.")
num1 = readLine()!!.toDouble()
println("수행할 연산자를 고르세요 : + - * /")
} else if (select == 2) {
num1 = calc
println("")
println("첫번째 수는 $calc 입니다.")
println("")
println("추가로 수행할 연산자를 고르세요 : + - * /")
} else if (select ==3) {
println("계산기를 종료합니다.")
break
}
operator = readLine()!!
println("두번째 수를 입력하세요.")
num2 = readLine()!!.toDouble()
when(operator) {
"+" -> {
calc =addOperator.operation(num1,num2)
}
"-" -> {
calc = subtractOperator.operation(num1,num2)
}
"*" -> {
calc = multiplyOperator.operation(num1,num2)
}
"/" -> {
if(num2 == 0.0) {
println("0으로 나눠지지 않습니다.")
} else {
calc = divideOperator.operation(num1,num2)
}
}
}
println("${num1} ${operator} ${num2} = ${calc} 입니다.")
}
}
추상클래스는 abstract 키워드로 선언한다. 필수적으로 구현해야할 메서드가 있을 때 사용한다.(단일 상속만 가능)
추상클래스나 추상메서드,추상프로퍼티는 상속을위해 open키워드가 필요없다.
(다만 open class와 다르게 단독으로 객체가 되어 사용할 수 없다.)
추상클래스는 인터페이스와 다르게 프로퍼티의 초기화도 가능하다. 즉, 공통 프로퍼티와 메서들을 미리 만들어 둘 수 있다. (여기서는 필요없어서 굳이 쓰지는 않았다.)
추상클래스에서 abstract로 정의한 프로퍼티나 메소드들은 자식클래스에서 반드시 재정의 되어야한다.
※참고로 추상클래스와 인터페이스가 다형성을 구현하는데 유용하다는 점에서 자주 비교가 되는데, 단일모듈에서는 인터페이스 사용을 지양하는 것이 좋다. 인터페이스는 애초에 다른 모듈을 위한 의사소통방식이다.
추상화는 핵심적인 요소를 강조하고 불필요한 세부사항을 숨기는데 초점이 맞춰져 있고, 상속은 이미 존재하는 클래스의 기능을 확장,수정(재사용성 및 유지보수용이)하는데 초점이 맞춰져있다.
따라하는 도중에 중간 리뷰 시간이 약속되어서 마무리를 하지 못했다.
package com.example.mycalculator
class Calculator(private val operation: AbstractOperation) {
fun operation() {
operation.operation(num1 = 0.0, num2 = 0.0)
}
}
이 상태에서 일단 오류는 없어서 계산을 돌렸는데, 메인에서 펑션을 실행하면 전부 0.0값이 나왔다. 나는 저 num1 = 0.0 , num2= 0.0 부분이 메인 펑션과 operation을 이어주는 부분이라고 생각했는데, fun opration( )에 생성자를 걸어야 메인 펑션에서 기능을하는거지, 위의 코드는 그냥 num1= 0.0이라고 고정값을 준거나 다름없다고 팀장님이 알려주셨다.
=> 팀장님의 조언으로 fun operation에 생성자를 걸어주니, 이 모습을 완성할 수 있었다.
package com.example.mycalculator
class Calculator(private val operation: AbstractOperation) {
fun operation(num1 : Double,num2 : Double) : Double {
return operation.operation(num1, num2)
}
}
+그리고 이름을 너무 다 비슷하게 설정해 놓아서 헷갈리는 것도 한 몫하는 것 같다. 설명하기도 애매해지고.. 개발자는 이름 설정하는데 고민이 한세월이라더니, 좀 더 좋은 이름을 붙일 수 있도록 노력해야겠다..