객체 지향형 프로그래밍이란 무엇일까
여러가지 생각이 든다 보통은 많은 사람들은 상속, 클래스, 다형성, 추상화 등을 예시로 들면서 말을 한다
맞는 말이다 그러나 너무 어렵다..
그래서 코딩을 갓 시작한 입장에서 객체 지향이 어떤 건지 느껴가면서 서술해보려고 한다
아래 코드는 계산기를 구현한 것이다
fun main() {
println("입력하고 싶은 값과 연산자를 띄워쓰기 단위로 입력해주세요 ex > 10 + 5")
//BufferedReader 가 코테 할때 조금더 빠른 걸 보여 줘서 쓰긴 썻는데 왜 빠른지 잘 모르 겠습니다
//while에 true를 넣고 중간에 break로 반복문 탈출 처리
val br = BufferedReader(InputStreamReader(System.`in`))
//몇 번 연산 했는지 체크
var i: Int = 0
//연산을 여러번 했을 경우 저장할 변수
var tempNum: Int = 0
while (true) {
try {
//수 연산자 수 순으로 띄워쓰기 기준으로 입력
val tempBr = br.readLine().split(" ")
//1번째 연산 이후에 새로 담을 변수 지정
var newNum: Int
if (i == 0) {
var num1 = tempBr[0].toInt()
var num2 = tempBr[2].toInt()
//연산자 검사 로직
var operatorString: String = tempBr[1]
var operator:Char = ' '
//연산자를 String으로 받았으므로 최초 연산자만 뽑아오도록 설계
for(j in 0 until operatorString.length) {
if(operatorString[j] == '+' || operatorString[j] == '-' || operatorString[j] == '*' || operatorString[j] == '/' || operatorString[j] == '%') {
operator = operatorString[j]
}
}
//위에 로직에서 연산자가 처리되지 않을 경우 연산자를 입력 받도록 설계
while (operator == ' ') {
println("연산자를 다시 입력해주세요, 연산자는 최초 입력된 연산자로 계산이 됩니다")
var operatorResult = br.readLine().toString()
for (k in 0 until operatorResult.length) {
if (operatorResult[k] == '+' || operatorResult[k] == '-' || operatorResult[k] == '*' || operatorResult[k] == '/' || operatorResult[k] == '%') {
operator = operatorResult[k]
}
}
}
//임시 저장공간에 계산 값을 저장
tempNum += operatorFunction(true, num1, operator, num2, tempNum)
} else {
var operatorString: String = tempBr[0]
var operator:Char = ' '
for(j in 0 until operatorString.length) {
if(operatorString[j] == '+' || operatorString[j] == '-' || operatorString[j] == '*' || operatorString[j] == '/' || operatorString[j] == '%' || operatorString[j] == '=') {
operator = operatorString[j]
}
}
while (operator == ' ') {
println("연산자를 다시 입력해주세요, 연산자는 최초 입력된 연산자로 계산이 됩니다")
var operatorResult = br.readLine().toString()
for (k in 0 until operatorResult.length) {
if (operatorResult[k] == '+' || operatorResult[k] == '-' || operatorResult[k] == '*' || operatorResult[k] == '/' || operatorResult[k] == '%' || operatorResult[k] == '=') {
operator = operatorResult[k]
}
}
}
//연산자가 = 일 경우 반복문 탈출
if (operator == '=') {
println("총 계산 값 $tempNum")
break
}
newNum = tempBr[1].toInt()
tempNum = operatorFunction(false, tempNum, operator, newNum, tempNum)
}
i++
} catch (e: NumberFormatException) {
//NumberFormatException 에 대한 Exception 처리
println("숫자가 잘못 입력 되었습니다")
}
}
}
//계산을 처리 하는 함수
fun operatorFunction(isFirst: Boolean, num1:Int, operator:Char, num2:Int, tempNumMain:Int):Int {
var tempNum = tempNumMain
if(isFirst){
when (operator) {
'+' -> {
tempNum += AddOperation().result(num1, num2)
println(tempNum)
}
'-' -> {
tempNum += SubstractOperation().result(num1, num2)
println(tempNum)
}
'*' -> {
tempNum += MultiplyOperation().result(num1, num2)
println(tempNum)
}
'/' -> {
tempNum += DivideOperation().result(num1, num2)
println(tempNum)
}
'%' -> {
tempNum += RemainOperation().result(num1, num2)
println(tempNum)
}
else -> {
println("해당 값은 연산자가 아닙니다")
}
}
return tempNum
}else {
when (operator) {
'+' -> {
tempNum = AddOperation().result(num1, num2)
println(tempNum)
}
'-' -> {
tempNum = SubstractOperation().result(num1, num2)
println(tempNum)
}
'*' -> {
tempNum = MultiplyOperation().result(num1, num2)
println(tempNum)
}
'/' -> {
tempNum = DivideOperation().result(num1, num2)
println(tempNum)
}
'%' -> {
tempNum = RemainOperation().result(num1, num2)
println(tempNum)
}
else -> {
println("해당 값은 연산자가 아닙니다")
}
}
return tempNum
}
}
위 코드를 보면 뭔가 가독성도 떨어지고 메인 함수 안에서 각각의 검사 코드들이 수두룩 하다(일부러 붙여넣기 한 이유가 있다)
사실 저 코드가 현재 실행하는 데는 크게 문제가 없다 (지금은 아마 다른 클래스 파일이 없어서 실행이 되지 않을 것이다)
그러나 추후에 다른 기능이 들어오고 이 코드를 유지 보수 하려고 하려면 굉장히 많은 시간이 필요할 것이다
또한 유지 보수를 한다고 한들 저 코드가 정상적으로 돌아가지 않는다면 처음부터 다시 손봐야 하는 낭패를 볼 수 있다
이런 부분은 조금이나마 해결해 줄 수 있는 프로그래밍 기법이 객체 지향형 프로그래밍이라고 생각한다( 물론 이론적으로 완벽하지는 않지만 어느정도 틀린 말은 아니라고 본다)
객체 지향 이란
- 간단하게 말하면 각 역할 별로 코드를 묶어서 관리 및 유지 보수 하는 것이리고 생각한다, 상속, 클래스, 다형성, 추상화등의 기술을 사용하여 객체 지향 프로그래밍을 한다
사실 프로그래밍에 정답은 없듯이 객체 지향도 정답이 업다고 생각한다 왜냐하면 어떤 억할군으로 어떻게 배정하는지에 따라서 달라 질 것
같기 때문이다
예를 들어 필자는 위에 코드 중에서 검사 하는 부분이 중복된다고 생각하여 따로 검사를 구현하는 객체를 만들까 한다
//검사 하는 기능을 간단하게 추상적으로 구현하는 인터페이스 구현
interface ValidationInterface {
fun validate(inputArgument:List<String>, count:Boolean): Boolean
}
//검사 하는 기능을 받아서 전달하는 클래스 구현
class Validator {
fun validation(validator: ValidationInterface, argument: List<String>, count:Boolean):Boolean{
return validator.validate(argument, count)
}
}
위와 같은 식으로 위에 큰 틀을 잡고 Validator 클래스가 인터페이스를 함수에서 받으면서 메인 함수등 사용해야 하는 곳에서 조금 더 유용
하게 사용하도록 했다
//연산자를 입력 받을 경우 추상화된 인터페이스에서 메서드를 재정의 하여 정상적인 연산자를 검사하도록 구현
class OperatorValidator: ValidationInterface {
override fun validate(inputArgument: List<String>, count: Boolean): Boolean {
if (!count) {
for (i in inputArgument[0]) {
return i in arrayOf('+', '-', '*', '/' ,'%')
}
}else{
for(i in inputArgument[0]) {
return i in arrayOf('+', '-', '*', '/', '%', '=')
}
}
return false
}
}
//숫자를 입력 받을 경우 추상화된 인터페이스에서 메서드를 재정의 하여 정상적인 숫자를 검사하도록 구현
class NumberValidator: ValidationInterface {
override fun validate(inputArgument: List<String>, count: Boolean): Boolean {
try{
if(count){
inputArgument[0].toInt()
return true
}
inputArgument[0].toInt()
inputArgument[1].toInt()
return true
}catch (e:NumberFormatException){
return false
}
}
}
그리고 계산기에서 검사가 필요한 연산자와 숫자를 받아서 검사할 수 있는 클래스를 인터페이스를 상속을 받아서 구현 하게끔 진행을 하였
다
fun validation(validator: ValidationInterface, argument: List<String>, count:Boolean):Boolean{
return validator.validate(argument, count)
그러면 위와 같이 ValidationInterface 타입을 매개변수로 받는 함수에서도 NameValidator 같은 함수를 사용할 수 있다(상속을 받았기
때문에 부모의 역할도 할 수 있기 때문에) -> 자세한 부분은 객체지향형 프로그래밍 [ 2 ] 에서 다룰 예정이다
따라서 위와 같은 방법으로 하나에 클래스 안에 하나의 역할만 주어서 역할을 하는 것이 객체 지향형 프로그래밍의 시작이라고 생각을 한다