본문 바로가기

코딩에 도움이 되는 요소/객체 지향

객체 지향형 프로그래밍이란 무엇일까[ 1 ]

객체 지향형 프로그래밍이란 무엇일까

 

여러가지 생각이 든다 보통은 많은 사람들은 상속, 클래스, 다형성, 추상화 등을 예시로 들면서 말을 한다

 

맞는 말이다 그러나 너무 어렵다..

 

그래서 코딩을 갓 시작한 입장에서 객체 지향이 어떤 건지 느껴가면서 서술해보려고 한다

 

아래 코드는 계산기를 구현한 것이다

 

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 ] 에서 다룰 예정이다

 

따라서 위와 같은 방법으로 하나에 클래스 안에 하나의 역할만 주어서 역할을 하는 것이 객체 지향형 프로그래밍의 시작이라고 생각을 한다