본문 바로가기
개발 지식 A+/FE

[JS] 객체 지향 프로그램

by ddubbu 2020. 12. 10.
728x90
반응형

컴퓨터 프로그램을 여러개의 독립된 단위, "객체"들의 모임으로 파악하고자 하는 프로그래밍 패러다임

by 위키백과


프로그래밍 패러다임

프로그래밍 스타일에 대한 이론적 개념으로 그 중 3가지를 소개하겠다.

 

  1. 절차 지향 프로그래밍(Procedural)
    순차적 처리에 초점
  2. 객체 지향 프로그래밍 (Object-Oriented)
    기능별로 필요한 데이터(=속성)와 행위(=메소드)를 하나의 덩어리(=객체)로 묶어서 진행한다.
  3. 함수형 프로그래밍 
    함수를 수행해도 함수 외부 값이 변경되는 것을 지양. 단순히 출력되는 what에 초점.


JavaScript는 Prototype 기반의 객체 지향 언어인데, OOP 디자인으로 구현 가능하도록 많은 발전이 있었다.

 

 

OOP (Object-Oriented Programming)

 

기본 구성 요소

Class : 객체를 생성하는 공장 (속성과 기능을 정의)

Instance : Class를 통해 생성된 객체

더보기

한 집단에서 같은 기능을 갖고 있는 객체가 있다. 이를 절차 지향 프로그래밍으로 구현 시 같은 작업을 반복해야하는 귀찮음이 있다. 이에, 집단의 속성과 메소드를 쉽게 정의해주는 효자같은 Class가 등장했다. 

 

절차 지향 프로그래밍

같은 작업을 반복한다.

// 절차 지향 프로그래밍

var redPen = {
  brand: "모나미",
  color: "red",
  price: 1000,
  draw: function(message)=>{
    console.log(message);
  }
}

var bluePen = {
  brand: "모나미",
  color: "blue", // 여기만 바뀜
  price: 1000,
  draw: function(message)=>{
    console.log(message);
  }
}

var yellowPen = {
  brand: "모나미",
  color: "yellow", // 여기만 바뀜
  price: 1000,
  draw: function(message)=>{
    console.log(message);
  }
}

....

 

객체 지향 프로그래밍

코드도 간결해졌다. 유지보수도 보다 쉬울 것이다.

// 객체 지향 프로그래밍
class Pen공장{
  constructor(brand, color, price){
    this.brand: brand,
    this.color: color,
    this.price: price,
  }
  draw: function(message)=>{
  console.log(message);
  }
}

var redPen = Pen공장("모나미", "red", 1000);
var bluePen = Pen공장("uni", "JETSTREAM", 2000);
var yellowPen = Pen공장("모나미", "yellow", 1000);

 

OOP 특징 & 장점

  1. Encapsulation : 속성, 메소드를 캡슐화 (wrapping)
    복잡도 줄이고, 재사용성을 높임 (위 더보기란틀 통해 간결미를 경험해보세요)
  2. Inheritance : 부모(porototype, 원형) 의 기능을 물려받음
    독립적인 객체 간의 관계를 형성하여 메소드 및 속성 상속 가능.
    Super Class(부모) -- Sub Class (자식)
  3. Abstraction
    내부원리를 이해하지 않고도, 최소한의 Interface만으로 사용 가능함. 사용자가 임의 변경 가능성 줄어듦
  4. Polymorphism(다형성)
    특정 기능을 선언(설계) 부분과 구현(동작) 부분으로 분리한 후 구현부분을 다양한 방법으로 만들어 선택해서 사용할 수 있는 특성이다. 참고자료의 좋은 예시를 들어보겠다. 스마트폰(=Super Class) 의 터치패드는 터치를 통해 다양한 앱을 구동시킨다. 하지만, 메세지 앱(Sub Class)으로 들어가면, 같은 터치 패드이지만 "키보드" 기능으로 변경된다. 게임 앱(Sub Class)에서는 "방향키"로써 작동한다. 다형성은 2가지 타입이 있다.

    Overriding
    Super Class의 기능(=터치패드)을 같은 이름으로 갖지만,
    각 Sub Class 에 맞게끔(=키보드, 방향키) 재정의

    Overloading
    하나의 클래스에 같은 이름의 메소드를 여러개 가질 수 있다. 단, 메서드 인자들은 달라야한다.

 

 

Instantiation Patterns 

js class 문법(ES6)이 나오기 전까지의 클래스 정의 및 인스턴스 생성 방법들을 살펴보도록 하겠다. 아래는 인스턴스 생성 패턴 4가지를 색연필 공장에 비유해서 작성해보았다. 속성은 색상, 메소드는 글쓰기 기능이 있다고 가정한다.


Functional 

var Pen공장 = function(color) {
  var instance = {};
  instance.color = color;
  instance.draw = function(message){
    console.log(message);
  }
  return instance;
}

//인스턴스 생성
var redPen = function Pen공장("red");
redPen.draw("이것은 빨간펜입니다"); // "이것은 빨간펜입니다"

 

Functional Shared

var extend = function(childObj, parentObj){
  for(var key in parentObj){
    childObj = parentObj[key];
  }
}

var parent = {};
parent.draw = function(message){
  console.log(message);
}

var Pen공장 = function(color) {
  var instance = {};
  instance.color = color;
  
  extend(instace, parent);
  return instance;
}

 

Functional 방식은 인스턴스를 생성할 때마다 메소드를 정의하기 때문에 메모리를 더 차지한다. 하지만, Functional Shared 방식은 parent method의 주소만을 참조하기 때문에 메모리 효율이 좋다.

 

Prototypal

var parent = {};
parent.draw = function(message){
  console.log(message);
}

var Pen공장 = function(color) {
  var instance = Object.create(parent);
  instance.color = color;
  
  return instance;
}

//인스턴스 생성
var redPen = function Pen공장("red");
redPen.draw("이것은 빨간펜입니다"); // "이것은 빨간펜입니다"

Object.create(prototype) : prototype을 상속받은 객체 생성

 

Pesudoclassical 

var Pen공장 = function(color){
  this.color = color;
}

Pen공장.prototype.draw = function(message){
  console.log(message);
}

// 인스턴스 생성 방법이 조금 다르다.
// new 키워드를 사용한다.
var redPen = new Pen공장("red");

Class (ES6)

그리고, ES6 syntax와 함께 등장한 class keyword를 통해 구현이 가능하다. 여타 다른 언어들과 사용법이 비슷하다.

class Pen공장 {
  constructor(color){
    this.color = color;
  }
  draw = function(message){
    console.log(message);
  }
}

// 인스턴스 생성
var redPen = new Pen공장("red");

 

 

Prototype Property vs Prototype Object vs Prototype Link 

프로토타입 프로퍼티, 프로토타입 객체, 프로토타입 링크... 프로프로프로... 머리가 뱅뱅 도는가?

 

상속 개념을 설명하기에 앞서, JavaScript의 주요 개념인 Prototype에 대해 알아보자. 생활코딩 영상에서 이해가 쉽게 그림으로 도식화 해주셨다. 하지만 어려운 개념이므로 한번에 이해하기가 어렵다는 점 참고 바란다. (필자도 완벽하게 이해를... ㅎ)

 

Prototype Object

우선, JavaScript에서 함수는 객체이기도하다 (1급 객체). 이게 무슨 소리일까? 우리가 익숙한 함수 선언식 방법 function 함수이름( ){} 혹은 Function 생성자로 var 함수이름 = new Function( ); 새 함수 객체를 만들 수 있다. (by MDN Function)

이때 생성되는 것은 두가지로 함수식 자체와 그 함수가 객체로서의 원본, 함수 Prototype Object 이다. 이 두가지는 서로 연관이 있으므로 다음 그림처럼 연결되어 있다. Function에서는 prototype 속성(property)으로 연결, Function.prototype에서는 constructor를 통해 참조하고 있다. 

function Person(name, first, second){
  this.name = name;
  this.first = first;
  this.second = second;
}

console창에서 확인한 관계도
생활코딩 JavaScript 객체 지향 프로그래밍 - 15. prototype vs proto

 이로써 우리는 porototype이란 함수가 선언될 때 원본 객체, prototype object를 지칭하는 것임을 알게되었다. 그럼, __proto__ (prototype link 라고도 부르기도 함) 는 무엇일까?

 

 

Prototype Chain을 위한 Prototype Link (__proto__)  

new 연산자를 통해 객체 인스턴스를 생성하면 (좀 더 자세한 내용은 아래 더보기란을 클릭하세요)

인스턴스.__poroto__ = 생성 시 사용한 함수의 객체 원본(Function.prototype)으로 연결되어있다.

더보기

new 연산자는 사용자 정의 객체 타입 인스턴스를 생성한다. by MDN

구문

new constructor[ (arguments) ]

 

설명

  1. 함수를 작성하여 객체 타입을 정의한다.
  2. new 연산자로 해당 객체의 인스턴스가 생성된다.

선미 설명
위에서 배운 prototype과 연결지어서 설명해보겠다. new 키워드는 constructor 함수식을 통해 초기화된 객체를 생성한다. 즉 new 함수(...)를 실행시키면, 함수.prototype.constructor를 통해 객체를 생성한다. 이때의 객체를 인스턴스라고 부르며 해당 함수 프로토타입 객체를 상속받았다.

function Person(name, first, second){
  this.name = name;
  this.first = first;
  this.second = second;
}

var kim = new Person('kim', 10, 20);

console창에서 확인한 __proto__

 

생활코딩 JavaScript 객체 지향 프로그래밍 - 15. prototype vs proto

 

 

그럼 여기서 추측할 수 있는 사실 하나는, 우리는 __proto__ 를 통해 상위 객체를 타고 올라갈 수 있음을 뜻한다.  노란색 인스턴스 객체에서 sum이라는속성을 찾아가기위해 __proto__ 를 타고가는 여정을 빨간색 화살표를 통해 볼 수 있다. (그 여정의 끝은 최상위 객체 Object 라는 객체 함수 프로토타입 일 것이다.)

생활코딩 - 16.3. 생성자 함수를 통한 상속 : 부모와 연결하기

지금까지 prototype 기반 언어인 JavaScript에서 상속 객체 간의 연결 관계를 내부적으로 알아보았다.  다음 챕터에서는 어떻게 객체 간의 상속을 구현할 수 있는지 코드로 살펴보겠다.

 

Inheritance Patterns

ES6가 도입되기 전 Pesudoclassical 구체적인 설명은 더보기란을 참고하라. 우리는 class 문법을 집중적으로 살펴보자.

더보기

우선 우리는 prototype 개념을 배운 사람들로서 내장 Array 함수의 특징을 가진 우리만의 Array를 만들 수 있다.

var myArray = function(){
  // 비어있지만, 내장 함수 prototype으로 받을거임.
}

myArray.prototype = Array.prototype;

var arr = new myArray();
arr.push(1); // 사용가능

하지만, 같은 prototype 주소를 공유하고 있으므로 내장 Array 함수 prototype이 변경될 위험이 있다는 것에 주의하라.

그래서 Object.create(prototype)를 통해 특정 porototype 객체를 똑 닮은 객체를 만들어 상속 받을 함수 원형 객체로 사용한다. ( 신기하게, 해당 시점에서 복사본으로 객체를 만들어서 이후 Super 함수 Prototype에 메소드가 추가되어도 Sub 함수 Prototype에 적용되지 않을 거라고 생각했는데, 적용이 된다! 헐!! 그리고 반대로 Sub에서 바뀌는 사항은 Super에게 적용 안됨. ) 

function fn1(){
 this.name = "fn1";
}

function fn2(){
 this.name = "fn2";
}

fn2.prototype = Object.create(fn1.prototype); // fn1 프로토타입 으로부터 상속받은 fn2 프로토타입
// fn2.prototype.constructor === fn1 위 실행 결과이므로 아래처럼 자신의 생성자 함수로 연결
fn2.prototype.constructor = fn2; 

fn1.prototype.return1 = function(){return 1} // Object.create 이후에 추가하여도
fn2.prototype.return1(); // 사용 가능

fn2.prototype.return2 = function(){return 2} // 자식한테 추가하면 부모 변경 안됨.
fn1.prototype.return2(); // 사용 불가

만약 상위 프로토타입의 constructor 함수를 하위 프로토타입의 constructor 함수에서 실행하고 싶다면, this만 적절히 binding 시켜주면된다. 

var Human = function(name){ // 상위 프로토타입 constructor
  this.default = "default";
  this.name = name;
  this.sleep = function(){
    console.log('zzz....'); 
  }
}



var Student = function(grade){ // 하위 프로토타입 constructor
  // 이곳에서 상위 프로토타입 constructor를 실행시키고 싶다면, this lexical scope binding 필요
  Human.call(this, name); 
}

Student.prototype = Object.create(Human.prototype);
Student.prototype.constructor = Student;
Student.prototype.learn = function() {
  console.log("학습중");
};

var steve = new Human('steve'); 
// sleep만 실행가능. learn은 하위 프로토타입에만 정의되어있으므로

var john = new Student('john'); // Student {default: "default", name: "", sleep: ƒ}
// slepp, learn 둘다 잘 사용 됨.

이렇듯 프로토타입 기반의 OOP 구현은 꽤나 복잡하다. 이제는 ES6 class keyword를 재미나게 배워보고 그 편리성을 느껴보자.

 

우선 Pesudoclassical 방법으로 구현한 것.

function Human(name){ // 상위 객체 constructor
  this.name = name;
}
// method는 따로
Human.prototype.sleep = function(){console.log("zzz...");}

function Student(name){ // 하위 객체 constructor
  Human.call(this, name); // 상위 객체 constructor 실행 후 
  this.nation = "Korea";
}

// Human.call(this.name) 코드 포함해서 아래 2줄이 상속을 위한 필수 코드이다.
Student.prototype = Object.create(Human.prototype);
Student.prototype.constructor = Student;

Student.prototype.learn = function(){
  Human.prototype.sleep();
  console.log("아니에요, 저 안 잤어요");
} // method는 따로 (하위 객체에만 추가됨)
var 선미 = new Student('선미');

console.log(선미.name, 선미.nation); // 선미 Korea
선미.sleep(); // zzz...
선미.learn(); // zzz... 아니에요, 저 안 잤어요

 

그리고 class 방법으로 구현한 것이 아래와 같다. 위에서 상속을 위해 설정해줘야할 많은 것들이 간략화되었다.

class Human{
  constructor(name){
    this.name = name;
  }
  // prototype method도 안 에서 생성 가능.
  sleep(){
    console.log("zzz...");
  }
}

class Student extends Human { // 상속 기본 문법
  constructor(name){
    super(name); // 상위 consturctor 실행 및 & method 가져오기
    this.nation = "Korea"; // 사전에 super 를 실행해야 this 키워드 사용 가능
  }
  learn(){
    super.sleep();
    console.log("아니에요, 저 안 잤어요");
  }
}

var 선미 = new Student('선미');

console.log(선미.name, 선미.nation); // 선미 Korea
선미.sleep(); // zzz...
선미.learn(); // zzz... 아니에요, 저 안 잤어요

 

 

이상으로 자바스크립트 객체 지향 프로그래밍 에 대한 설명을 마치겠다.

반응형