개발을 하면서 변화 가능한 유연한 구조를 만들어주는 핵심 기법 중 하나인 객체지향(Object Oriented)의 중요성을 깨달았다.
이를 조금 더 자세하게 공부를 하고 싶어 '개발자가 반드시 정복해야 할 객체 지향과 디자인 패턴' 책을 사서 읽고 정리를 한 것을 블로그에 남기기로 했다. ( 추후에 보았을 때 기억에 남기기 쉽게 하기 위해 )
이번 파트는 Part 01 로 첫번째 시작하는 파트이다.
첫번째 시작하는 파트인 만큼 객체지향이 필요한 이유를 설명한다.
객체지향이 필요한 이유를 설명하기 위해 객체지향을 사용하지 않았을 때와 사용을 했을때를 비교를 하면서 정리를 해보았다.
( 이 책은 참고로 Java 기반으로 예시 코드가 작성되어 있으니 참고 바란다. )
1. 지저분해지는 코드
메뉴 영역에서 메뉴 1과 메뉴2를 누르면 화면 영역에 알맞은 내용이 출력되고, 이 화면에는 공통 버튼을 한 개 가지고 있으며, 그 버튼을 누를때는 각 화면에 맞게 데이터가 변경되는 UI를 갖는 프로그램을 개발한다고 치자.
위의 요구사항을 만들기 위해서 가장 기본이 되는 코드를 다음과 같이 작성을 할 수 있을 것 이다.
위의 코드를 살펴보게 되면 if와 else if를 통해 menu1과 menu2를 클릭을 했는지를 검사를 하고 다음에 elese if를 통해 버튼을 클릭을 한 것을 검사를 하는데 이때 currentMenu가 null 이라면( menu1과 menu2를 클릭하지 않았다면) 바로 리턴(반환)을 하고 만약 currentMenu가 menu1 혹은 menu2의 값을 가진다면 menu1과 menu2에서의 버튼 클릭을 했다는 로직을 가지는 코드다.
여기서 살펴볼 점은 버튼을 클릭을 했을 때 해당 화면이 menu1에서의 버튼을 클릭한 건지 menu2에서의 버튼을 클릭한 건지를 구별하기 위해서 currentMenu 변수를 이용하여 각 화면에 대해 고유 값을 지정을 했다는 점이다.
만약, 위의 코드에서 버튼2가 필요하다고 하면 코드는 아래와 같이 바뀔 것이다.
위에서 만든 코드를 기준으로 해서 button2를 추가한다면 else if 문을 추가하여 button2 인지를 검사를 하고 그 안에서 또 다시 현재 화면의 값을 판별할 수 있는 currentMenu 변수를 통해서 if와 else if문을 이용을 하여 로직을 완성하였다.
즉, 동일한 구조의 코드가 반복하고 중첩된 if else 블록이 생긴다는 것이다.
만약에 위의 코드를 기준으로 메뉴가 5개로 늘어나고 버튼이 5개로 늘어난다면 중첩된 if-else 블록이 엄청나게 늘어날 것이고 10개 이상만 되어도 말도 안되게 중첩된 if-else 블록이 생겨날 것이다.
위의 코드로 메뉴와 버튼이 많은 경우를 구현하는 방법으로 복붙을 이용하여 구현을 할 것이다.
초반에는 힘이 들지 않겠지만 점점 복붙을 하다 보면 지치고 오랜 시간이 걸릴 것이다.
또한 코드를 추가하지 않고 누락하는 경우도 분명 발생할 것이다.
또한 뭔가를 변경해야 할 때 일부를 변경하지 않아 원하는 결과값을 얻지 못하는 경우도 분명 생겨날 것이다.
==> 위에 있는 말들을 종합적으로 한마디로 말을 해본다면 코드를 수정하기 점점 어려워진다는 것을 의미하며, 따라서 새로운 요구 사항이 발생했을 때 그 요구 사항을 반영하는데 오랜 시간이 걸리게 된다는 것이다.
==> 이 말이 바로 "초기에는 새로운 요구 사항을 빠르게 개발해 주었는데, 시간이 지날수록 간단한 요구 사항 조차 제 때 개발이 안되는 상황"이 발생하는 것이다.
2. 수정하기 좋은 코드를 가진 코드
위에서 지저분한 코드들을 객체 지향 방식을 이용을 하여 수정하기 좋은 구조를 가진 코드로 만들어보자. ( 현재는 아직 추상화와 다형성에 배우지 않았지만 객체 지향에서는 추상화와 다형성을 이용해서 변화되는 부분을 관리하기 때문에 추상화와 다형성에 알지는 못하지만 이를 이용하는 코드 이다. )
우리는 현재 메뉴 영역에서 메뉴 1과 메뉴2를 누르면 화면 영역에 알맞은 내용이 출력되고, 이 화면에는 공통 버튼을 한 개 가지고 있으며, 그 버튼을 누를때는 각 화면에 맞게 데이터가 변경되는 UI를 갖는 프로그램을 개발하고 있는 중이다.
생각을 해보게 된다면,
메뉴 1을 선택했을 때와 메뉴2를 선택했을때 비슷하게 동작을 했다.
- 메뉴가 선택되면 해당 화면을 보여준다.
- 버튼 1을 클릭하면 선택된 메뉴 화면에서 알맞은 처리를 한다.
위에 있는 동작들은 메뉴3이나 메뉴4가 추가되더라도 동일하게 동작하는 것들이다.
즉, 모든 메뉴에 대해 "화면을 보여주고, 버튼1을 클릭하면 화면에 반영한다." 라는 공통된 동작을 취한다.
==> 이것의 공통 동작을 포현하기 위해 interface로 ScreenUI 타입을 정의 하였다.
위의 ScrrenUI 인터페이스에서,
- show() 메서드 : 어떤 메뉴 버튼이 클릭 될 때 실행되는 메서드
- handleButton1Click() 메서드 : 버튼 1이 눌렸을 때 실행되는 메서드
==> 메뉴 별로 실제 화면에 보이는 구성 요소와 버튼 1 클릭을 처리하는 코드가 다르므로, 각 메뉴 별로 ScreenUI 인터페이스를 구현한 클래스를 작성해준다.
UI 프로그램이 실행되는 Application 클래스는 ScrrenUI 인터페이스와 Menu1ScreenUI 클래스 및 Menu2ScreenUI 클래스를 이용해서 구현한다.
위의 코드를 살펴보면, Application은 menu1이나 menu2를 클릭하면 soucrceId를 if문을 통하여 비교하여 각 해당하는 조건문에 들어가서 Menu1ScrrenUI 클래스 혹은 Menu2ScreenUI 클래스의 객체를 생성해서 currentScreen 클래스 변수에 할당 한 뒤 이 할당되어 새로운 값을 가진 currentScreen 클래스 변수의 currentScreen.show() 메서드를 호출한다. 그 후에 button1을 클릭한다면 currentScreen.handleButton1Click() 메서드를 호출한다.
위의 코드에서 중요한 점은 button1 클릭을 처리하는 코드는 현재 화면이 메뉴1 화면인지 메뉴 2 화면인지에 상관없이 currentScreen.handleButton1Click()을 실행한다는 점이다.
메뉴 클릭 처리 코드는 화면을 변경하는데 반해, 버튼 클릭 코드는 변경된 화면에 버튼 클릭 결과를 반영하기 위해 사용된다.
==> 두 종류의 버튼 처리 코드는 목적이 다르며, 서로 다른 이유로 변경이 된다.
이렇게 서로 다른 이유로 변경되는 코드가 한 메서드에 섞여 있으면 추후에 코드 가독성이 떨어져서 유지보수하기 어려워질 수 있으니 아래의 사진과 같이 메뉴 클릭 처리와 버튼 클릭 처리 코드를 분리하자.
메뉴/버튼 이벤트 처리 구분 후의 코드를 보게 되면 menu와 button이 직접 해당하는 리스너(menuListener, buttonListener)에 대해 무명 객체 생성을 이용하여 리스너 객체를 직접 할당하여 넣어줬다.
이렇게 메뉴/버튼 이벤트 처리 구분 후의 코드를 가지고 있는데 버튼 2를 추가해달라는 요청이 왔다.
버튼2를 처리해야 하므로 ScreenUI 인터페이스에 새로운 메서드(handleButton2Click())를 추가한다.
ScreenUI 인터페이스가 변경되었으므로 Menu1ScreenUI 클래스 파일과 Menu2ScreenUI 클래스 파일은 handleButton2Click() 메서드를 구현하지 않았다는 컴파일 에러가 발생한다.
때문에 두 클래스에 각각 handleButton2Click() 메서드를 추가로 구현해준다.
지저분한 코드의 경우 Application 클래스에 메뉴 1 관련 코드와 메뉴2 관련 코드가 한 소스 코드에 섞여 있었다.
==> 따라서 메뉴 1에 대한 관련 코드를 분석하거나 수정하려면, 메뉴 1 관련 코드만 있는 경우와 비교해서 Application 소스 코드의 이곳저곳을 더 많이 이동을 해야만 할 것이다. 이때 메뉴의 개수가 증가할수록 소스 위치를 찾는 시간은 점점 길어지게 되며, 이는 개발 시간을 불필요하게 증가시키는 문제를 초래할 것이다.
당연하게도 각 메뉴 별로 당작을 처리하기 위한 중첩된 if-else 블록도 복잡해질 것이다.
즉, 메뉴를 추가하려면 Application 코드 자체에 많은 변경을 주어야 하며, if-else 블록이 복잡해질수록 코드 수정도 복잡해지는 (어려워지는) 문제가 있다.
Application에서 모든걸 구현했던 지저분한 코드 방식과 달리 ScreenUI 인터페이스가 출현한 두번째 방식은 작성하는 클래스 개수가 다소 증개했지만, 메뉴 관련 코드들이 알맞게 분리되었다.
확실하게 Screen UI 인터페이스를 이용하여 각 클래스를 구현한 방식을 보게되면,
메뉴 1 화면과 관련된 코드는 모두 Menu1ScreenUI 소스 코드에 위치해 있다.
따라서 메뉴 1 관련 코드를 분석하는 과정에서 불필요하게 메뉴 2 관련 코드를 의식적으로 피할 필요가 없다는 것이다!
하지만 한 클래스에 모든 코드를 다 넣은 지저분한 코드 방식을 보게되면, 메뉴1 관련 코드와 메뉴2 관련 코드가 섞여 있기 때문에 메뉴1 관련 코드를 분석하다 보면 자연스럽게 메뉴2 관련 코드를 함께 보게 된다.
따라서 개발자는 메뉴1과 상관없는 메뉴2 관련 코드를 보지 않기 위한 노력을 하게 된다.
또 다른 특징으로는 버튼 클릭을 처리하는 코드가 단순화 되었다는 점이다.
Application에서 모두 구현을 한 지저분한 코드 방식에서는 버튼 종류가 추가될 때마다 왼쪽 코드처럼 if-else 블록이 추가되었다.
반면에 두번째 방식인 Screen UI 인터페이스를 이용하여 각 클래스를 구현한 방식을 보게되면, 버튼이 추가될 경우 Application 클래스는 오른쪽 코드처럼 추가한 버튼을 클릭 했는지만을 조건문을 통해 검사하면 되는것이다.
Application과 ScreenUI를 분리했을 때 생기는 장점은 메뉴3을 새롭게 추가할 때 더 잘 드러난다.
그렇기에 메뉴 3과 관련된 클래스 파일을 한 개 만들어주고 이것을 Appication 클래스에 메뉴3 관련 코드를 추가해보자.
위의 코드는 Application에 메뉴 3 관련 코드를 추가한 것인데, 이 코드에서 중요한 점은 버튼 클릭 부분의 코드는 전혀 바뀌지 않았다는 점이다!
Application에서 모두 구현을 한 지저분한 코드 방식과 Screen UI 인터페이스를 이용하여 각 클래스를 구현한 방식 각각 메뉴 추가에 따른 코드 변경의 차이를 아래 사진을 보고 확인해보자.
위의 코드를 통해 차이점을 확실하게 알 수 있는데, 차이점은 다음과 같다.
- 새로운 메뉴 추가 시, 버튼 처리 코드가 영향을 받지 않음
- 한 메뉴 관련 코드가 한 개의 클래스로 모여서 코드 분석/수정이 용이함
- 서로 다른 메뉴에 대한 처리 코드가 섞여 있지 않아 수정이 용이함
==> 즉, 요구사항이 바뀔 때, 그 변화를 좀 더 수월하게 적용할 수 있다는 장점을 얻었다. 이런 장점을 얻기 위해 사용된 것이 바로 객체 지향 기법이다.
객체 지향의 어떤 내용들을 사용햇는지 설명하진 않았지만 객체 지향 기법을 적용하면 소프트웨어를 더 쉽게 변경할 수 있는 유연함(flexibility)을 얻을 수 있게 되고 이는 곧 요구 사항의 변화를 더 빠르게 수용할 수 있다는 것을 뜻한다.
3. 소프트웨어의 가치
소프트웨어의 가치는 사용자가 요구하는 기능을 올바르게 제공하는데 있으며, 아무리 잘 만든 소프트웨어라고 할 지라도 사용자가 요구하는 기능을 제공하지 않는 다면 없는 것만 못할 수도 있다.
이것이 요구하는 기능만 제공하도록 구현하면 되는다는 것을 의미하는 것은 아니며, 요구사항은 언제나 변하기 때문에 추후에 변동될 요구사항에 맞게 소프트웨어는 변화할 수 있어야 한다. 이게 또 다른 소프트웨어의 가치이다.
새로운 요구 사항을 적용하기 어려우면 소프트웨어는 점점 뒤쳐지게 되는데, 이는 결국 소프트웨어의 죽음으로 이어질 수 있다.
물론 수 천줄에 걸친 if-else의 중첩 블록을 사용할 수 있겠지만 수정에 따른 높은 비용(시간)의 기회비용을 소비하게 된다.
이것보다는 변화에 잘 대응할 수 있도록 소프트웨어의 구조를 만들고 더 빠른 시간에 더 적은 노력을 들여서 수정하는 것이 낫다!
변화 가능한 유연한 구조를 만들어 주는 핵심 기법 중의 하나가 바로 객체 지향(Object Oriented)이다.