OOP - 객체 지향적 프로그래밍 정리

Object Oriented Programming

  • 객체를 기반으로 모델링하여 어려운 실세계의 일을 해결한다는 프로그래밍 패러다임
  • 데이터가 객체 내에 캡슐화
  • 하나의 정형화된 틀(class)을 만들어 그를 기반으로 여러 인스턴스(객체)를 만들어 사용한다.

Procedural Langauges vs. Object-Oriented Languages

  • 절차 지향적 언어는 모든 것이 한 프로그램 내에 있고 순차적으로 진행된다. 이 절차를 정하는 것이 키포인트
  • 절차 지향 프로그래밍에서는 위에서 아래로 실행 순서가 정해지기 때문에 실행 속도는 빠르지만, 프로그램 전체가 유기적으로 연결되어 있으므로 한 기능을 수정하면 모든 기능을 수정해야 한다. 따라서 유지보수 및 디버깅이 어렵다.
  • Procedural Langauges의 종류: C, ALGOL, COBOL, FORTAN, Java, Pascal 등
  • 객체 지향 프로그래밍에서는 프로그램을 구성하는 모든 요소들이 객체(object)로 이루어져있다. 따라서 계층 구조를 만들어 쉽게 재사용할 수 있다. 반면 처리 속도나 설계에 상대적으로 더 많은 시간이 소모된다.
  • 객체 지향 언어가 아닌 언어에서도 객체 지향처럼 구현할 수도 있다.(JavaScript)
  • Object-Oriented Languages의 종류: C++, JAVA, C#, Python, Ruby 등


OOP의 4가지 핵심 개념

  • Encapsulation: 데이터나 함수를 감싸 객체 패키지 안에 보관한다. // 속성, 기능을 모아둔다. === 캡슐화한다.
  • Inheritance: 부모의 특징을 물려받는 것. // 자식 클래스가 부모 클래스를 상속하게 되면 자식 또한 부모의 속성이나 기능을 갖게된다.

    JavaScript에서는 .__ proto __ 에 부모의 속성을 가지고 있으며, 자식에서 새로운 속성, 기능을 추가하거나 변경할 수 있다.

  • Abstraction: 복잡한 것들을 보다 단순한 모델로 변환하는 작업 // 사용자가 내부 구조를 몰라도 쉽게 사용할 수 있도록 하는 것.

    class 내부 구조는 복잡할지라도 사용자는 호출 또는 생성 키워드를 통해 쉽게 활용할 수 있다.

  • Polymorphism: 여러 객체 타입에 같은 기능을 상황에 맞게 정의할 수 있는 능력 // 다른 형식으로 하나의 작업을 수행

    공통되는 부모를 갖는 여러 자식들이 각각 공통 부모의 속성 및 기능을 사용할 수 있다. 각각의 자식들 내부에서 확장하여 추가하거나 변경할 수 있다.


Class

  • JavaScript는 프로토타입 기반 언어로서, 클래스 개념이 존재하지 않기 때문에 프로토타입으로 클래스의 기능을 구현한다.
  • class란 같은 특성을 공유하면서 데이터 값이 다른 여러 객체(objects)들을 만들어내는 하나의 틀 // 연관있는 데이터들을 묶어놓는 컨테이너
  • 같은 이름의 클래스는 한 번만 선언 가능하며, 메모리에 저장되는 데이터 없이 틀만 있는 것이다.
  • Object(instance): class를 이용해 실제로 데이터를 넣은 것. 클래스에 정의된 데이터와 함수를 갖는다.

Instantiation patterns

  • JavaScript에서 instance(클래스 기반 object)를 만드는 작업
  • object 생성 + object의 method(함수), property(변수) 생성

1. Functional Instantiation

함수를 이용해 클래스를 만들고 instance를 찍어내는 방식

함수 내에서 빈 객체를 생성하고, 프로퍼티와 메소드를 추가한 후 객체를 리턴한다.

  let User = function(name, age) {
    let obj = {};
    obj.name = name;
    obj.age = age;
    
    obj.sayHello = funtion(name) {
      console.log("Hello, " + obj.name);
    }
    
    obj.info = function(name, age) {
      console.log(obj.name + " is " + obj.age);
    }
    
    return obj;
  }
  
  let user1 = User('Jace', 25) 
  user1.sayHello() // 결과값: "Hello, Jace"
  user1.info() // 결과값: "Jace is 25"


장점: 객체 내에 모두 포함되어 있기 때문에 이해하기 쉬운 코드이다. 변수(properties)들은 closure scope에 포함되어 외부 접근이 불가하다.

단점: 메소드들이 모두 객체 내에 private하게 있기 때문에 instance를 생성할 때마다 메모리에 모든 메소드와 프로퍼티들을 복제해야 한다.(메모리 비효율적)


2. Functional Shared Instantiation

객체 간 메소드를 공유함으로써 모든 메소드와 프로퍼티를 복제해야하는 Functional instantiation의 단점을 보완한다.

함수 내에서 빈 객체를 생성하고 변수를 선언한다. 메소드는 다른 객체에서 정의하고, 다른 객체들이 포인터(extend)를 이용해 같은 메소드에 접근할 수 있도록 한다.

  let User = function(name, age) {
    let obj = {};
    obj.name = name;
    obj.age = age;
    
    extend(obj, objMethods);
    return obj;
  }
  
  // obj와 objMethods를 합치는 함수
  let extend = function(obj, methods) {
    for (let key in methods) {
      obj[key] = methods[key]
    }
  }
  
  let objMethods = {
    sayHello: function(name) {
      console.log("Hello, " + this.name);
    },
    
    info: function(name, age) {
      console.log(this.name + " is " + this.age);
    }
  }
  
  let user1 = User('Jace', 25) 
  user1.sayHello() // 결과값: "Hello, Jace"
  user1.info() // 결과값: "Jace is 25"


장점: 객체 내 메소드들의 주소만을 참조하기 때문에 메소드가 중복되지 않아 Functional instantiation보다 메모리 개선에 유리하다.

단점: 확장된 메소드 객체와 연결되는 각 객체의 포인터는 객체가 생성될 때 함께 생성된다. 메소드를 수정 후 새 객체 생성 시 원래 객체와 새 객체가 서로 다른 메소드를 가리킨다.


3. Prototypal Instantiation

prototype chain을 이용하여 객체 생성. 특정 객체를 prototype으로 하는 객체를 생성한다.

Ojbect.create() 메소드를 이용하여 객체의 프로토타입에 메소드를 묶는다.

모든 메소드를 분리된 객체로 만들고 Object.create()로 메소드를 포함하는 객체를 프로토타입으로 하는 새 객체를 생성한다.

  let User = function(name, age) {
    let obj = Object.create(objMethods);
    obj.name = name;
    obj.age = age;
    
    return obj;
  }
  
  let objMethods = {
    sayHello: function(name) {
      console.log("Hello, " + this.name);
    },
    
    info: function(name, age) {
      console.log(this.name + " is " + this.age);
    }
  }
  
  let user1 = User('Jace', 25) 
  user1.sayHello() // 결과값: "Hello, Jace"
  user1.info() // 결과값: "Jace is 25"


장점: 메소드가 해당 객체 내에서 리턴되는 것이 아니라, 객체의 프로토타입에 연결된다. 메모리에서의 메소드 복제 없이 모든 객체가 모든 메소드를 사용할 수 있다.

단점: 메소드를 사용하기 위해서는 메소드 객체를 프로토타입으로 하는 객체를 만들어 새로 생성한 객체에 할당해준 후 생성자 함수에서 객체를 리턴해야 한다.


4. Pseudoclassical Instantiation

prototypal instantiation처럼 prototype chain을 이용해 객체를 생성하면서 보다 단순화된 방법을 사용할 수 있다.

new operator를 사용하여 메소드를 포함하는 객체를 프로토타입으로 갖는 새 객체를 생성한다.

새 변수를 정의하거나 Object.create()를 할당하는 대신 새 객체에 “this”를 할당한다.

this 키워드를 사용하여 함수와 프로퍼티(속성)을 정의한다. 메소드는 프로토타입에 정의된다.

  let User = function(name, age) {
    this.name = name;
    this.age = age;
  }
  
  User.prototype.sayHello = function(name) {
      console.log("Hello, " + this.name);
  }
    
  User.prototype.info = function(name, age) {
      console.log(this.name + " is " + this.age);
  }
  
  let user1 = new User('Jace', 25) 
  user1.sayHello() // 결과값: "Hello, Jace"
  user1.info() // 결과값: "Jace is 25"


장점: JavaScript에 내장된 기능을 활용함으로써 객체 지향 특성을 가장 잘 살린 방법으로 효율적이며 가장 자주 사용되는 방법이다.

단점: 다른 방법들에 비해서 복잡한 디자인


Inheritance Patterns

  • 상속: 상위 객체의 특징을 하위 객체에게 넘겨주는 것. 자식 객체는 부모 객체의 프로퍼티 또는 메소드를 상속받아 사용할 수 있고, 부모의 기능을 베이스로 하려 새로운 기능을 추가할 수 있다.
  • 자바스크립트에서 각각의 객체는 자신의 프로토타입이 되는 다른 객체와 연결된다.

Prototype chain

  • prototype: 기반이 되는 원형 객체(original form)
  • 자바스크립트에서 모든 함수는 prototype이라는 특수한 속성을 가지고 있다. 이 prototype에 속성이나 메소드를 추가할 수 있다.
  • .__ proto __ 를 통해 객체의 prototype에 접근할 수 있다.
  • 어떤 객체의 prototype 속성은 자신의 prototype이 되는 다른 객체를 가리키고, 그 객체의 프로토타입 또한 프로토타입을 가지며 이것이 반복되면서 상위로 연결된다. 가장 상위에 null을 프로토타입으로 가지는 Object에서 prototype chain이 종결된다.
  • 객체의 어떤 속성에 접근할 때, 그 객체 자체의 속성부터 그 객체의 프로토타입, 그 프로토타입의 프로토타입 등 프로토타입 체인의 최상위에 이를 때까지 상위로 속성을 탐색한다.
 let Human = function(name) {
   this.name = name;
 }

 let Simon = new Human('Simon');
 Simon.__proto__ === Human.prototype
 // true


  • 자신이나 자신의 프로토타입에 정의되지 않은 객체 메소드를 쓸 수 있다. 프로토타입 체인의 최상위 객체인 Object에 정의된 메소드이기 때문이다.
 // prototype chain
 Simon.toString();
 // "[object Object]"

 Simon.__proto__.__proto__ === Object.prototype
 // true


  • Prototype은 할당이 가능하다.
 let MyArray = function() {
 }
 
 // 프로토타입에 Array의 프로토타입을 할당하면 Array처럼 작동하며, 배열 메소드를 이용할 수 있게된다.
 MyArray.prototype = Array.prototype
 
 let myArr = new MyArray();
 myArr.push(1) // MyArray [1]
 myArr.length // 1


Object.create()를 이용한 상속

  • Object.create(): 프로토타입 객체를 인자로 전달받아 그 객체를 바탕으로 새 프로토타입 객체를 만든다.
  • 다른 객체 프로토타입을 바로 할당하면 그 참조 자체가 바뀌게되기 때문에 그 프로토타입과 동일한 기능을을 하는 복사본, 즉 새로운 프로토타입 객체를 생성한다.
  • this: 함수가 실행될 때 참조하는 객체. new 키워드로 인스턴스를 생성하면 해당 인스턴스가 this가 된다.
  • 상속 후 new 키워드로 인스턴스 생성 시 context 자체가 프로토타입으로 전달되지 않기 때문에, 별도로 constructor에 this를 전달해주는 작업이 필요하다. // call이나 apply를 사용한다.
 let Human = function(name) {
   this.name = name;
 }

 Human.prototype.sayHello = function() {
   console.log('Hi, ' + this.name);
 }
 
 let Simon = new Human('Simon');
 
 let User = function(name) {
   Human.call(this, name); // Human.apply(this, arguments)
 }
 
 User.prototype = Object.create(Human.prototype);
 User.prototype.constructor = User;
 
 User.prototype.sayBye = function() {
   console.log('Bye, ' + this.name);
 }
 
 let Alec = new User('Alec');
 Alec.sayHello(); // Hi, Alec
 Alec.sayBye(); // Bye, Alec


Class keyword: super, extends

  • ES6(ECMAScript 2015)에 새로 도입된 키워드로 class를 구현한다.
  • class: prototype 기반의 상속으로 새로운 class를 선언
  • constructor(): class 내에서 객체를 생성하고 상태를 정의하는 메소드. 필요한 데이터를 전달하는 역할로서 클래스 내에 한 개만 존재할 수 있다.
  • Class를 정의하는 방법: class 선언, class 표현식
 // class 선언: class 키워드, 클래스 이름(보통 첫글자는 대문자로)
 // 함수 선언의 경우 hoisting이 일어나지만 class 선언은 hoisting이 일어나지 않는다.
 class Rectangle {
   constructor(height, width) {
     this.height = height;
     this.width = width;
   }
 }
 // class 표현식: class 키워드, 클래스 이름은 선택사항
 // 이름이 없는 class 표현식
 let Rectangle = class {
   constructor(height, width) {
     this.height = height;
     this.width = width;
   }
 };
 console.log(Rectangle.name); // 결과값: "Rectangle"

 // 이름이 있는 class 표현식
 let Rectangle = class Rectangle2 {
   constructor(height, width) {
     this.height = height;
     this.width = width;
   }
 };
 console.log(Rectangle.name); // 결과값: "Rectangle2"


  • extends: 생성된 class를 다른 class의 자식 클래스(하위클래스)로 만드는 키워드
  • super: 부모 객체의 함수를 호출하는 키워드. this 키워드 전에 사용되어야 한다.
 class Rectangle {
   constructor(height, width) {
     this.height = height;
     this.width = width;
   }
   getArea() {
     return this.width * this.height;
   }
   printArea() {
    console.log('사각형의 넓이는 ' + this.getArea())
   }
 }
 class Square extends Rectangle {
   constructor(height, width, angle) {
     super(height, width);
     this.angle = angle
   }
 }

 let square1 = new Square(20, 10, 90)
 square1.printArea() // '사각형의 넓이는 200'

Categories:

JavaScript

Load Comments