'객체 지향 프로그래밍'은 코드를 작성하는 방법, 코드를 정리하는 방법이다. 더 나아가 데이터에 대한 생각, 구조 방식이라고도 볼 수 있다! C++, C#, Java와 같은 유명한 언어들이 객체 지향에 대한 거대한 커뮤니티를 갖고 있다.
또한 JavaScript, Python과 같은 객체 지향 언어가 아닌 언어도 객체 지향 언어 방식을 지원하고 있다.
즉, 객체 지향 프로그래밍(OOP)를 배우면, 그 기술을 다양한 많은 언어에 적응할 수 있다. Java에서 TypeScript로, C#으로 이동하는게 편해질 수 있다. OOP의 쉬운 예를 들어보도록 하자.
자, 일단 비디오 게임을 만든다고 상상해보자. 이 경우 플레이어 객체(Object)가 필요하다. 각 플레이어 객체는 각각 다른 데이터를 갖을 것이고, JavaScript로 이를 표현하면 이런 모습일 것이다.
딱 한명의 플레이어만 만든다면, 이렇게 코딩해도 무방할 것이다. 하지만, 숫자가 늘어나면 문제가 생기기 시작할 것이다. 플레이어들이 늘어나면 다음과 같은 패턴 그리고 이슈들이 나타날 것이다.
발견되는 패턴들로 예를 들면
이 플레이어들은 모두 'name', 'health', 'skill' 같은 동일한 속성(Property)을 갖고 있다. 다른 점은 데이터 뿐이다.
바로 여기서 생기는 이슈는 플레이어를 구성하는 모습이 어떠해야한다 하는 일종의 '구조'에 대한 고민이 없다. 실수를 해서 '기술'을 넣는 걸 빼먹거나 속성을 쓰는데 오타를 내거나 할 수도 있다. 심지어 오타같은 스펠링 에러(Spelling Errors)들은 찾기도 힘들다.
예를 들면,
여기 코드 어딘가에 잡기 어려운 정말 바보같은 철자 오류가 있다.
딱 봐서는 찾기가 어렵다. 이렇게 코드 복제를 해서 플레이어를 생성하는 것의 문제점은
새로운 속성 예를 들어 '경험치'를 추가하고 싶다면, 그리고 해당 속성을 모든 플레이어들에게 주고 싶다면, 한땀 한땀 하나씩 '경험치' 속성을 추가해야한다는 것이다!
이는 당연히 좋은 방법이 아니다. 더 좋은 방법은 내가 데이터만 넣으면 적용해주는! 플레이어 객체를 아웃풋으로 얻을 수 있는! 함수가 있는 일종의 '플레이어 팩토리'를 만드는 것이다.
이렇게 하면, 복제를 할 필요도 없고, 스펠링 에러도 피할 수 있을 것이다. 또한, '경험치' 속성을 모든 플레이어들에게 주고 싶다면 그냥 '플레이어 팩토리'에서 속성 하나만 추가하면 된다!
딱 하나만 바꾸면, 모든 플레이어들이 한번에 업데이트 되는 것이다. 바로 이것이 OOP의 초석을 사용할 때이고, 이는 바로 Class의 개념이다.
Class(클래스)는 객체를 위한 팩토리와 같은 것이다. 같은 속성을 갖고 있지만, 데이터는 다른 경우! Class(클래스)는 일종의 구조, 설계도를 만들어 준다. 플레이어 객체가 어떻게 보여야 하는지에 대한 도면을 정의해준다.
클래스를 사용하면 다음과 같이 코드가 변한다. JavaScript와 Python으로 예를 들어보자.
새로운 플레이어를 추가하고 싶을 때 다음과 같이 작성하면 된다.
코드가 엄청 쉬워졌다는 것(= 아름다워졌다는 것)을 느낄 수 있을 것이다. 이전처럼 코드를 복사할 필요가 없다! 또한 그들의 속성(Property)에 접근할 수도 있다.
보다시피. 클래스는 일종의 금형 설계도 같은 것이다. 동일한 속성을 가지고 있지만 각기 다른 데이터를 갖고 있는 수많은 플레이어 객체들을 생산할 수 있다. 클래스를 쿠키 찍어내는 '금형', '몰드'라고 생각해도 된다! 원하는 모습으로 찍어낼 수 있는 템플릿인 것이다. 예를 들면, 'Elon'이라는 플레이어을 생성해보자.
'Elon'을 플레이어 클래스의 인스턴스 혹은 객체로 지칭한다. 여기서 '인스턴스' 혹은 '객체'는 '쿠키 금형'을 사용한 후 얻을 수 있는 '쿠키'에 속한다. 다시 JavaScript 클래스를 보면 'Constructor'이라는 것이 있다.(Python에는 init이 존재한다.)
정상적인 함수이지만, 클래스 내에 있기 때문에 함수라 부르지 않고 Method(메서드)라고 부른다. 클래스에서 새 객체를 만들 때 'Constructor'와 'init' 메서드는 모두 Javascript와 Python에 의해 자동으로 호출된다. Constructor 메서드에서 클래스를 어떻게 구성할 것인지 어떤 속성을 갖게될 것인지 정할 수 있다. Constructor은 함수처럼 인수를 받을 수 있다.
새로운 플레이어 'Elon'를 작성한다면 이것은 Constructor의 첫번째 인수(플레이어의 이름)가 될 것이고, 그 다음 health, skill 등을 작성한다.
또한, 보다시피 모든 플레이어가 0xp(경험치 0)으로 시작하게끔 프로그래밍 할 수도 있다. 여기서 'this'라는 단어는 플레이어 클래스 내부의 속성 및 메서드를 지칭하는 방법이다.
위 코드를 보면 XP(경험치)라는 속성을 가지고 있고, 초기값은 'this.xp = 0;'이므로 0이다. 보다시피 객체를 위한 일종의 공장, 금형의 역할을 하는 것이다.
우리는 클래스 안에 메서드라는 것이 있는 것을 알게 되었다. 바로 이 메서드가 클래스를 더 높은 단계로 진화시켜준다. 'Constructor'나 'init'이 아니더라도 클래스는 수많은 종류의 메서드를 가질 수 있다. 예를들면, 이러한 메서드를 만들 수 있다.
만약 플레이어 'Elon'이 인사를 하는 기능을 실행하려면 Elon.sayHello를 또는 전투게임을 만든다면, 플레이어가 맞았을 때 무슨일이 생기는 지 'takeHit'과 같은 메서드를 코딩할 수도 있다.
이와같이 메서드는 클래스를 보다 영리하게 만들어준다. 이전에는 클래스로 데이터를 정리하기만 했지만, 이젠 메서드 덕분에 데이터에 엑세스하고 조작할 수 있는 인터페이스를 만들 수 있게 되었다.
이제 가장 중요한 OOP의 핵심 개념 상속(Inheritance)을 살펴보자.
'상속'은 코드 중복을 줄이고, 코드를 재사용 가능한 조각으로 나눌 수 있다. 오프라인 세상에서 '상속'이라 함은 부모님이 돌아가시고, 그 자산을 자녀들이 상속받는 것을 뜻한다.
자녀(Child) 클래스가, 부모(Parent) 클래스의 속성을 갖게 된다.
예를 들면, 심즈같은 시뮬레이션 게임을 만든다면 '인간'을 위한 클래스를 다음과 같이 만들어야 할 것이다.
'Human'에 속하는 'Baby' 클래스도 만들 수 있고
'Teenager' 클래스도 만들 수 있다.
이는 '인간'이고, 'this.emotional = true;'의 영향으로 성격이 있는 속성을 갖고 있다. 여기서는 중복된 곳이 있다. 다음 이미지에서의 모든 클래스들이 조금씩 다 다르지만 이들은 모두 '인간'에 속한다.
'name'이 있고, 2개의 'arms'와 'legs'를 갖고 있다.
바로 이때 '상속' 개념이 필요하다.
생성하는 모든 클래스에 'this.name', 'this.arms'와 'this.legs'를 각각 쓰는 대신 'this.name', 'this.arms'와 'this.legs'를 가진 클래스에서 '확장'하면 된다.
그리고 그 클래스는 'Human'이 될 것이다. 코드로 구현해보면 다음과 같은 모습일 것이다.
'Baby' 클래스나 'Teenager' 클래스가 'Human'에서 '확장' 한다고 하면 'Baby', 'Teenager' 클래스가 'Human' 클래스의 속성을 갖고 추가적으로 그들만의 다른 속성을 갖기 원한다는 뜻이다. 즉 'Baby' 인스턴스는 'name', 'arms', 'legs'를 갖고, 더해서 'this.cute = true;'와 cry()를 할 수 있는 속성을 갖게 될 것이다. 하지만 이 코드는 아직 작동하지 않는다. 작동하지 않는 이유를 생각해보면 'Human' Constructor가 인수에 따라 'Human'의 'name'을 정하기 때문이다.
우리는 'Human'을 바로 만들지 않고
'Baby' 그리고 'Teenager'을 만들 거다.
'Baby', 'Teenager' 클래스에서 인간의 Constructor 메서드를 호출하려면, 'Baby', 'Teenager' 클래스의 Constructor에서 Super method 라는 걸 호출해야 한다.
이렇게 하면 잘 작동할 것이다.
OOP를 사용하면 정말 편해질 것이다. OOP가 왜 이렇게 인기 많고, 많은 개발자들이 좋아하는 지 이해가 될 것이다.
이것이 OOP의 전부는 아니다. 이는 OOP의 작은 Intro에 불과하고, OOP는 대부분의 프로그래밍 언어가 지원하는 기본적인 개념 중 일부다. OOP에 대해서 자세히 알고 싶으면 private, public, protected methods & properties를 배워야하고 abstract classes, static methods, interfaces 등등을 배워야 한다.
이번 포스트에서는 'Encapsulation', 'Abstraction', 'Polymorphism' 같은 단어를 사용되지 않고 최대한 OOP의 실용적인 측면을 배웠다.
다음 포스트에서는 OOP에 대해 더 자세히 들어가보도록 하겠다.