치춘짱베리굿나이스

자바스크립트에서의 객체지향 (1) 객체지향 기본 본문

Javascript + Typescript/이론과 문법

자바스크립트에서의 객체지향 (1) 객체지향 기본

치춘 2022. 7. 26. 20:14

객체지향

올 것이 왔다

자바스크립트 프로그래밍을 하면서 그 편의성 때문에 클래스를 종종 이용하는데, 물론 잘 알고 쓰는 것은 아니다

심지어 자바스크립트의 클래스는 정석적인 클래스도.. 아니었다고 한다 (하하)

간단하게나마 정리하고 적어도 왜 쓰는지는 알고 쓰는 것이 좋겠다

객체지향?

기존에는 프로그램을 명령어들의 집합이라고 생각했다면, 객체 지향 프로그래밍은 모든 데이터를 객체 (object) 취급한다

프로그래밍에서 필요한 모든 데이터들을 추상화시켜 상태와 행위를 가진 객체로 만들고, 객체들을 이리저리 조합하여 객체간 상호작용을 통해 프로그램을 구성한다

말 그대로 객체를 지향하는 프로그래밍인데, 지향이라고 하니까 좀 헷갈릴 수 있다

객체를 주축으로 사용하는 프로그래밍 방법론이라고 생각하자

객체지향의 특징

  • 큰 문제를 작게 쪼개 생각하기보단, 작은 문제들을 해결할 수 있는 객체들을 만든 뒤 이 객체들을 조합하여 큰 문제를 해결하는 ‘Bottom-up’ 방식 프로그래밍을 지향
  • 각 객체들은 다른 객체들과 독립적으로 움직이며, 객체 외부에서 접근이 가능한 인터페이스를 제한하여 (private, protected 등) 잘못 사용되지 않도록 방지할 수 있다
    • 객체의 독립성을 높이면, 한 객체의 코드나 로직을 수정했을 때 그 객체를 사용하는 다른 객체는 코드를 거의 수정할 필요가 없어 유지관리 비용이 낮다
    • 객체의 외부 접근 가능 인터페이스를 제한하면 객체 내부의 값이 변형될 가능성이 낮아지기 때문에 신뢰성이 높아진다

순차지향 (Sequential)

코드의 순서를 중점으로 보며, 코드의 흐름과 순서에 기반해서 프로그래밍한다

어느 코드가 어떤 순서로 실행될 지 명확하게 보이기 때문에 직관적이지만, 구조가 없이 위에서 아래로 순서대로 흐르기만 하기 때문에 어딘가 코드 중간 지점으로 돌아가고 싶을 땐 goto 문을 남발해야 하는 등 규모가 커질 수록 복잡한 코드가 되기 쉽다

또한 이미 위에서 작성한 코드를 밑에서 다시 작성하는 등 오히려 코드의 불필요한 중복을 늘리고, 엄청나게 복잡해지기 때문에 ‘직관성' 이라는 장점마저 사라지게 된다

이를 보완하기 위한 기법이 ‘절차지향' 이다

절차지향 (Procedural)

반복될 가능성이 있는 모듈을 함수 (Procedure) 단위로 쪼개, 함수의 호출과 실행에 기반해서 프로그래밍한다

순차지향은 순서에 따라서 프로그램이 흘러간다면, 절차지향은 어느 정도 코드를 쪼개 함수로 묶고, 반복적인 코드는 for, while 등의 반복문으로 묶어준다

순차지향보단 확실히 깔끔해졌고, 여전히 코드의 흐름도 읽기 쉽지만, 함수와 변수를 묶어서 생각할 수 없다는 문제가 있다

특정 함수에서 어떤 변수나 상수가 사용되어도 이를 외부에서 수정하거나 접근할 수 없으므로 관리가 어려웠고, 이들을 한번에 묶기 위해 등장한 개념이 ‘객체' 와 ‘객체지향' 이다

객체지향의 요소들

클래스

객체지향 프로그래밍 (OOP) 에서 특정 객체를 생성하기 위해, 그 객체의 공통적인 속성행위를 변수와 메서드를 정의하는 틀

 

예를 들면 hello, world! 를 출력하는 프로그램의 핵심 특징은

  • 콘솔에 무언가를 출력하는 행위 (method)
  • 출력하려는 문장이 hello, world! 라는 속성 (property)

이 두 가지라고 볼 수 있다

 

1 + 1을 계산하는 기능 등은 이 프로그램을 구현하기 위해서는 불필요한 기능이니 제하고, 위의 2개가 우리가 만들고자 하는 프로그램의 주된 관심사가 되므로, 두 개를 묶어 이름을 붙이면 (추상화) 그것이 클래스가 되는 것

클래스는 쿠키틀, 인스턴스는 쿠키라고 많이들 비교한다

 

속성 (property) 와 행위 (method) 구별하기

  • 메서드가 함수로 된 프로퍼티이고, 따라서 호출 가능한 프로퍼티가 메서드라고 할 수 있다
  • 자바스크립트의 객체는 key-value 쌍으로 이루어진 프로퍼티 집합과 같고, 프로퍼티들 중 value가 호출가능한 함수의 형태를 띌 경우 그것이 method가 되는 것이다

객체

클래스에서 정의한 것을 토대로 실체를 갖고 생성된 것을 말한다

인스턴스의 상위 개념으로, 쿠키를 굽는 행위를 예시로 들어보면 어떤 모양의 무슨 쿠키를 구울 것인지 정의한 쿠키틀이 클래스고, 그 쿠키틀로 찍어져 만들어진 쿠키가 객체이다

 

함수 (메서드) 와 변수들을 아우르는 포괄적인 개념이다

OOP 관점에서, 클래스의 타입으로 선언되었을 때 이를 객체라고 부른다

‘객체' 지향 프로그래밍답게, 이 녀석들이 유기적으로 연결되어 하나의 프로그램을 이룬다

인스턴스?

설계도 (클래스) 를 바탕으로 실제로 메모리상에 구현된 실체를 의미한다

객체가 ‘클래스로 만들어질 것' 이라면, 인스턴스는 ‘클래스로 만든 것’ 이라고 할 수 있다

위의 쿠키와 쿠키틀 비유에서 인스턴스는 실제로 구워져 나온 초코 쿠키, 바닐라 쿠키, 건포도 쿠키… 라고 할 수 있겠다

 

객체의 개념에 포함되는 하위 개념으로, 프로그램에서 실제로 사용하는 것은 이쪽이다

OOP 관점에서, 객체가 실제로 메모리에 할당되었을 때 그것을 인스턴스라 부른다

하나의 클래스로부터 여러 개의 인스턴스를 생성할 수 있고, 각자 서로 다른 메모리 공간에 할당된다

클래스와 실제 객체 사이의 관계를 설명할 때 인스턴스라는 단어를 많이 쓴다

  • 객체는 클래스의 인스턴스
  • 객체간의 링크는 클래스간 연관관계의 인스턴스
  • 실행 프로세스는 프로그램의 인스턴스

개념적인 것과 실제로 메모리상, 또는 시스템상에 구현된 것의 차이라고 볼 수 있다

따라서 용어 자체를 클래스와 객체에 매몰되어 생각할 필요가 없다

너무 헷갈리는데 사실 엄격하게 객체와 인스턴스를 분리해서 생각하긴 원래 어렵다고 한다

객체지향의 특징

추상화 (Abstraction)

현실 세계에서 특정한 대상을 관찰하고, 핵심적이고 특징적인 공통점을 뽑아내는 과정이다

이 그림을 보면 우리는 고양이라는 사실을 한번에 알아볼 수 있다

길고 얇은 꼬리, 뾰족한 귀, 유선형 몸매 등 고양이의 특징이 충분히 나타나 있기 때문에, 굳이 털 색이나 이목구비, 팔다리 등을 구체적으로 묘사하지 않아도 고양이라고 알아보기엔 충분하다

 

프로그래밍에서의 추상화는 구현하고자 하는 프로그램에서 핵심적인 특징들을 뽑아내고, 그 특징들 중 비슷한 것끼리 묶어 이름을 붙이는 것을 말한다

= 클래스를 설계하는 것 그 자체를 추상화라고 할 수 있다

별로 연관없거나 중요성이 낮은 부분은 최대한 숨기고, 인터페이스와 로직을 분리하며, 같은 주제를 가진 속성이나 행위를 묶어 하나의 클래스로 만드는 것이 추상화이다

캡슐화 (Encapsulation)

관련있는 속성과 행위끼리 묶되, 외부에서 접근이 필요한 부분을 제외하고는 내부로 숨기는 것

캡슐화를 하면

  • 외부에서 접근가능한 메서드나 변수에 제한이 있기 때문에, 외부에서 해당 객체를 사용하다가 오류가 발생해도 상대적으로 문제 발생 가능 범위가 좁다
    • 외부에서 숨겨진 메서드나 변수에 접근할 수가 없기 때문에, 해당 로직이 변경되었을 가능성이 없기 때문
    • 따라서 디버깅할 땐 외부에 노출된 부분만 디버깅해도 된다
  • 관련된 속성 및 행위끼리 묶이기 때문에, 어느 로직에서 어떤 객체가 책임을 갖고 있는지 알기가 명확해 마찬가지로 디버깅이 쉽다
    • 높은 응집도 (cohesion - 비슷한 속성 및 행위끼리 묶임) 와 낮은 결합도 (coupling - 클래스 서로가 서로에게 영향을 거의 끼치지 않음) 를 가졌다고 할 수 있다
  • 숨기고 싶은 정보들을 접근제한자를 통해 적절히 숨기고, 내부에서만 함수 등을 통해 추가적인 로직 설계가 가능하여 정보 은닉이 가능하다

개념은 어렵지만 사실상 클래스를 정의하는 것 자체가 캡슐화다

어느 기능 (행위) 와 특성 (속성) 을 넣을 것인지, 무엇을 숨길 지 정의하는 것 자체가 이미 캡슐화라고 할 수 있다

상속 (Inheritance)

절차지향에서도 라이브러리를 통해 남이 짜놓은 코드를 내 코드에서도 동작시킬 수 있었다

하지만 그 코드를 내 입맛대로 수정하다 보면 원래 의도하던 동작이 아닌 전혀 다른 동작을 할 수도 있다

상속은 부모 클래스의 속성과 행위를 자식 클래스에게 유전시켜, 자식 클래스가 부모 클래스의 모든 특징들을 물려받을 수 있도록 한다

케익 레시피에서 공통적인 부분은 따오고, 원료 함량을 조절하여 파운드케익이나 롤케익 레시피를 만드는 등을 상속이라고 할 수 있다

그렇게 되면 홀 케이크 레시피는 부모 클래스, 파운드케익 레시피는 자식 클래스가 되는 것이다

 

여기서 더해 자식 클래스는 부모 클래스에서 유전받은 특징들을 수정하거나 더 많은 특징을 추가하여 기능을 다양화할 수 있는데, 이를 구체화 (Specialize) 라고 한다

부모자식 관계를 갖는 것은 계층형 구조 (Hierachical structure) 를 갖는 것이고, 계층 아래로 내려갈 수록 (자식, 손자, 증손자…) 클래스의 내용이 구체화되며 고유의 특징들이 많이 생겨나게 된다

동물 → 포유류 → 고양이과 → 고양이, 사자, 호랑이 등 아래로 내려올 수록 갈기, 무늬, 체구 등 고유의 특징들이 많아지는 것처럼 말이다

상속은 이럴 때 사용한다

  • 남이 만든 클래스를 (패키지나 라이브러리 등으로) 가져와 사용해서 값을 수정하기 어려운 경우
  • 부모 클래스를 수정하고 싶지만 다양한 곳에서 해당 클래스를 사용하고 있거나 상속받고 있기 때문에 섣부른 수정이 어려운 경우

부모 클래스에서 값이 수정되면 상속받은 자식 클래스도 같이 수정된다

 

상속을 할 때는

  • is-a 관계 ([자식클래스]는 [부모클래스]이다) 가 성립할 때 해주는 것이 좋다
    • has-a 관계 ([자식클래스]는 [부모클래스]의 일부이다) 가 성립할 때 상속을 하지 말자
  • 다형성을 고려하여 설계하자
  • 코드 재사용성 측면만 고려하여 무리하게 상속을 하면 혼란을 줄 수 있다

다형성 (Polymorphism)

다형성이란 한자어 그대로 ‘여러 형태가 존재한다' 라는 뜻이다

OOP 관점에서 다형성은 하나의 속성이나 행위가 상황에 따라 다른 의미로 해석될 수 있음을 뜻한다

정말정말 중요한 개념이라고 한다

 

다형성의 종류

  • 서브타입 다형성 (Subtype Polymorphism)
    • 포함 다형성 (Inclusion Polymorphism) 이라고도 부른다
    • 상위 클래스의 메서드를 하위 클래스가 상속받은 뒤, 재정의하여 사용하는 것
    • 재정의를 오버라이딩 (override) 이라고 한다
    • 예를 들어, 고양이 클래스의 ‘야옹' 을 출력하는 메서드를 호랑이 메서드가 상속받아 ‘어흥' 을 출력하도록 오버라이드 할 수 있다
    • 런타임 다형성이며, 그 이유는 다형성 함수의 호출 결정이 런타임에 의해 결정되기 때문이다
    • 컴파일 타임에 호출이 결정되는 것이 아닌 런타임때 가상 테이블을 통해 호출이 결정된다는 뜻
  • 매개변수 다형성 (Parametric Polymorphism)
    • 패러미터화 다형성이라고도 부른다
    • 데이터 타입이나 함수를 범용적으로 작성하여, 타입에 얽매이지 않고 같은 동작을 할 수 있도록 돕는 성질이다
    • C++에서 Template를 쓰는 것을 생각해보자
    • 서브타입 다형성과 다르게 컴파일 타임 다형성으로, 함수 호출이 컴파일 타임에 결정된다
  • 임시 다형성 (Ad-hoc Polymorphism)
    • 매개변수 다형성이랑 어찌 보면 대비되는 개념이라고 생각할 수 있겠다
    • 오버로딩이랑 같다
    • 매개변수 다형성은 함수가 매개변수의 타입에 얽매이지 않고 같은 동작을 수행할 수 있는 성질이라면, 임시 다형성은 같은 이름을 가진 함수가 타입에 따라 다르게 행동할 수 있도록 하는 성질이다
    • C++에서 연산자 오버로딩이 자주 사용되는데, int에서 + 연산은 단순히 숫자 두개를 더하는 연산자겠지만 string에서 + 연산은 문자열 두개를 이어붙이는 연산자로 쓰인다
    • 컴파일 타임 다형성으로, 함수 호출이 컴파일 타임에 결정된다가
  • 강제 다형성
    • 타입이 강제 형변환되는 것을 의미한다

 

다형성을 가능하게 하는 것들

  • 오버라이딩 (overriding)
    • 상위 클래스의 메서드를 하위 클래스가 상속받은 뒤 재정의하는 것
    • 상위 클래스의 참조 변수가 어떤 하위 클래스의 인스턴스를 참조하는지에 따라 동작이 달라진다
  • 오버로딩
    • 같은 이름의 함수를 여러 개 정의하고, 각각의 매개변수의 타입과 개수를 다르게 하여 매개변수에 따라 다르게 호출할 수 있도록 하는 것
    • 같은 함수명이라도 인자의 개수나 인자의 타입에 따라 다른 함수가 호출된다
    • 프로그래밍을 하면서 경고문구 등에서 종종 ‘오버로드를 찾을 수 없습니다' 이런 식으로 나오기도 하는데, 이게 오버로딩 때문이다 (선언된 오버로드 중 넣은 인자값에 맞는 오버로드를 찾을 수 없다는 의미)
  • 상속
    • 어찌 보면 상속 덕에 오버라이딩이 일어날 수 있다고 생각할 수도 있겠다

여담

객체지향도 깊게 들어가면 매우매우매우매우매우매움ㄴ우ㅐㅁㄴ우맨ㅇ 어려워지지만… 오늘은 한번 기본적인 것부터 차근차근 공부하기 위해 짧게 정리해 보았다

다음 글에서는 자바스크립트에서 객체지향이 어떻게 동작하는지 프로토타입 기반 언어 관점에서 작성해보겠다


참고 자료

코딩교육 티씨피스쿨

[Java] 클래스, 객체, 인스턴스의 차이 - Heee's Development Blog

객체 지향 프로그래밍이 뭔가요? (꼬리에 꼬리를 무는 질문 1순위, 그놈의 OOP)

객체지향 프로그래밍 (OOP) 이란?

[객체지향] Object-Oriented Programming 핵심 개념의 이해

OOP의 네가지 특징(추상화/캡슐화/상속/다형성)

modern-cpp-tutorial/The Four Polymorphisms in C++.md at master · utilForever/modern-cpp-tutorial

Comments