[JS] 객체 지향 프로그래밍(OOP)의 기초

객체 지향 프로그래밍

1. 개념

  • 하나의 모델이 되는 청사진(blueprint)을 만들고, 그 청사진을 바탕으로 한 객체를 만드는 프로그래밍 패턴
  • 절차 지향 프로그래밍과 달리 데이터와 기능을 한곳에 묶어서 처리
    *절차적 언어 : 순차적인 명령의 조합
  • 속성과 메소드가 하나의 '객체'라는 개념에 포함
    *자바스크립트 내장 타입인 object와는 달리 Class라는 이름 사용
  • 자바스크립트 : 객체 지향 언어는 아니지만 객체 지향 패턴으로 작성 가능

2. 특징

1) 캡슐화(Encapsulation)

  • 데이터(속성)와 기능(메소드)을 하나의 단위로 묶는 것
  • 코드가 복잡하지 않고 재사용 가능하도록 만들어줌

(1) 은닉(hiding)

  • 은닉화 : 구현은 숨기고 객체 외부에서 필요한 동작(메소드)만 노출시켜 내부 데이터나 내부 구현이 외부로 노출되지 않도록 함
  • 엄격한 클래스 : 속성의 직접적인 접근을 막고, 설정하는 함수(setter)와 불러오는 함수(getter)를 철저하게 구분하기도 함
const Cat = function(name) {
  this.name = name
}

const KKAKKA = new Cat('KKAKKA')
KKAKKA.name = 'GGAGGA'

console.log(KKAKKA)
// Cat {name: "GGAGGA"}
// KKAKKA의 이름이 GGAGGA로 바뀜
  • 클로저를 활용한 방법
const Cat = function(name) {
  const a = name
  this.getName = function() {
    console.log(a)
  }
}

const KKAKKA = new Cat('KKAKKA')
KKAKKA.name = 'GGAGGA'

console.log(KKAKKA)
// Cat {name: "GGAGGA", getName: ƒ}

KKAKKA.getName()
// KKAKKA
// 외부에선 값을 얻을 수만 있고 바꿀 순 없음
  • #키워드를 사용한 방법(only class) : #name
class Cat {
  #name
  constructor(name) {
    this.#name = name
  }
}

const KKAKKA = new Cat('KKAKKA')
KKAKKA.#name = 'GGAGGA'
//에러 발생 : Private field '#name' must be declared in an enclosing class

(2) 느슨한 결합(Loose Coupling)에 유리

  • 언제든 구현 수정 가능
  • 느슨한 결합 : 코드 실행 순서에 따라 절차적으로 코드를 작성하는 것이 아닌, 코드가 상징하는 실제 모습과 닮게 코드를 모아 결합

2) 추상화(Abstraction)

  • 내부 구현은 복잡하지만 실제로 노출되는 부분은 단순하게 만드는 것
  • 단순한 인터페이스 : 너무 많은 기능들이 노출되지 않아 예기치 못한 사용상 변화 방지

3) 상속(Inheritance)

  • 부모 클래스의 특징을 자식 클래스가 물려받는 것
    *기본 클래스(base class)의 특징을 파생 클래스(derive class)가 상속받는 것
  • 자식 클래스만의 특징 : 추가적으로 속성/메소드 추가 가능

4) 다형성(Polymorphism)

  • 다양한 형태를 가질 수 있는 특징 : 똑같은 메소드라도 다른 방식으로 구현 가능

클래스를 이용한 모듈화

1. 객체

  • 속성(property): 키-값 쌍을 의미
  • 메소드 호출 : 객체.메소드()
    *화살표 함수 사용 불가

2. 클래스

1) ES6

class Cat {
  // 생성자(constructor) 함수
  constructor(name, color, age) { // 속성
    this.name = name
    this.color = color
    this.age = age
  }
  sayHello() { // 메소드
    return `Hello, I'am ${this.name}`
  }
}

console.log(Cat.prototype)
// {constructor: ƒ, sayHello: ƒ}
// 	constructor: class Cat
// 	sayHello: ƒ sayHello()
// 	__proto__: Object

const KKAKKA = new Cat('KKAKKA', 'Black', 7)
console.log(KKAKKA)
// Cat {name: "KKAKKA", color: "Black", age: 7}
// 	age: 7
// 	color: "Black"
// 	name: "KKAKKA"
// 	__proto__:
// 	  constructor: class Cat
// 	  sayHello: ƒ sayHello()
// 	  __proto__: Object
for(key in KKAKKA) {
  console.log(key)
}
// name, color, age
// 메소드는 제외됨
  • 하나의 모델이 되는 청사진(blueprint)
  • 클래스명 : 대문자로 시작하는 일반명사
    *일반 함수 : 적절한 동사를 포힘, 소문자로 시작
  • 속성 : name, color, age
  • 생성자(constructor) 함수 : 인스턴스가 만들어질 때 실행되는 코드로 return 값을 만들지 않음
    *this : 인스턴스 객체를 의미
  • 메소드 : 객체에 딸린 함수를 의미, 생성자 함수와 함께 class 키워드 안쪽에 묶어서 정의

2) ES5

// 생성자(constructor) 함수
function Cat(name, color, age) { // 속성
  this.name = name
  this.color = color
  this.age = age
  this.eat = function() {
    console.log('eating...')
  }
}

Cat.prototype.legs = 4
Cat.prototype.sayHello = function() { // 메소드
  return `Hello, I'am ${this.name}`
}

// 위의 코드는 아래처럼도 가능하지만 아래의 경우 constructor 명시 필요
// Cat.prototype = {
//   constructor : Cat,
//   legs = 4,
//   sayHello = function() { // 메소드
//   	return `Hello, i'am ${this.name}`
//   }
// }

console.log(Cat.prototype)
// {legs: 4, sayHello: ƒ, constructor: ƒ}
// 	legs: 4
// 	sayHello: ƒ ()
// 	constructor: ƒ Cat(name, color, age)
// 	__proto__: Object

const KKAKKA = new Cat('KKAKKA', 'Black', 7)
console.log(KKAKKA) 
// Cat {name: "KKAKKA", color: "Black", age: 7, eat: ƒ}
// 	age: 7
// 	color: "Black"
// 	eat: ƒ ()
// 	name: "KKAKKA"
// 	__proto__:
// 	  legs: 4
// 	  sayHello: ƒ ()
// 	  constructor: ƒ Cat(name, color, age)
// 	  __proto__: Object
  • prototype 키워드를 사용해 프로토타입에 저장 가능

3. 인스턴스

let KKAKKA = new Cat('KKAKKA', 'Black', 7)

KKAKKA.color // Black
KKAKKA.age // 7
KKAKKA.sayHello() // Hello, i'am KKAKKA
  • 청사진(blueprint)을 바탕으로 만든 객체
  • 인스턴스 만들기 : new 키워드 사용
  • 생성자 함수가 실핼되며 변수에 클래스의 설계를 닮은 새로운 객체(인스턴스) 생성
  • 인스턴스 : 고유한 속성과 메소드를 갖게 됨

Prototype

const Cat = {
  legs: 4,
  sleep() {
    console.log("slepping...")
  }
}

const KKAKKA = {
  name : "KKAKKA",
  color : "Black"
}

KKAKKA.__proto__ = Cat

console.log(KKAKKA.legs) //4
// KKAKKA 객체엔 legs가 없지만 __proto__로 Cat을 상속받음
// KKAKKA.legs : KKAKKA 객체에 legs가 없으므로  __proto__에서 legs를 찾음
for (key in KKAKKA) {
  console.log(key)
}
// name, color, legs, sleep
// 상속받은 key까지 모두 나옴
// .hasOwnProperty를 이용하면 for in 문에서도 상속된 property는 나오지 않게 할 수 있음

Object.keys(KKAKKA) // ["KKAKKA", "Black"]
Object.values(KKAKKA) // ["KKAKKA", "Black"]
// 상속된 property는 나오지 않음
const KKAKKA2 = {
  name : "KKAKKA2",
  color : "gray"
}

KKAKKA2.__proto__ = KKAKKA
console.log(KKAKKA2.legs) //4
// 상속은 계속됨
// legs가 KKAKKA2에 없어 KKAKKA로 올라가 찾고, 
// KKAKKA에도 없으면 Cat으로 올라가 찾음 = Prototype Chain
const KKAKKA = {
  name : "KKAKKA",
  color : "Black",
  legs : 5
}
console.log(KKAKKA.legs) //5
// KKAKKA.legs : KKAKKA 객체에 legs있으므로 KKAKKA 객체에서 legs를 찾음
  • JavaScript : 프로토타입 기반 언어로, 모든 객체들이 메소드와 속성들을 상속 받기 위한 템플릿으로써 프로토타입 객체(prototype object)를 가짐
  • 프로토타입 객체도 또 다시 상위 프로토타입 객체로부터 메소드와 속성을 상속 받을 수도 있고 그 상위 프로토타입 객체도 마찬가지(= 프로토타입 체인)
  • 상속되는 속성과 메소드들은 각 객체가 아니라 객체의 생성자의 prototype이라는 속성에 정의되어 있는 것
  • 프로토타입 : 모델의 청사진을 만들 때 쓰는 원형 객체(original form)

1. 클래스와 프로토타입

Cat.prototype.constructor === Cat // true
Cat.prototype === KKAKKA.__proto__ // true
Cat.prototype.sayHello === KKAKKA.sayHello // true

  • Cat의 prototype의 생성자는 Cat
  • Cat의 prototype엔 sayHello가 존재
  • KKAKKA.sayHello는 Cat.prototype.sayHello를 '던더 proto'로 참조
  • new Cat을 통해 KKAKKA 인스턴스 생성

2. 프로토타입 체인

  • 프로토타입 체인(prototype chain) : 다른 객체에 정의된 메소드와 속성을 한 객체에서 사용할 수 있도록 해줌
  • 상속 : 자바스크립트에서 구현할 때 프로토타입 체인 사용
    *extends와 super 키워드를 이용
  • extends : 상속할 때 사용
  • super :
    - 자식 클래스에 constructor를 쓰고 싶을 때 사용
    - 자식 메소드에 부모의 메소드와 동일한 이름의 메소드가 있는 경우 부모의 메소드를 그대로 쓰면서 확장하고 싶을 때 사용
class Person {
  constructor(name) {
    this.name = name
  }
  sleep() { 
    console.log('sleeping...')
  }
}

const somin = new Person('somin')

class Student extends Person{
  constructor(name, age) {
    super(name)
    // super 키워드가 없으면 에러 발생
    // super를 통해 먼저 부모의 constructor를 실행해줘야 함
    // 이때 인자도 받아서 넘겨줘야 함
    this.age = age
  }
  sleep() { 
    super.sleep()
    // super 키워드가 없으면 자식 메소드로 덮어씌워져 'zzz...'만 나옴
    // super 키워드 사용 시 부모의 메소드 사용으로 'sleeping...'도 나옴
    console.log('zzz...')
  }
  learn() { 
    console.log('learning...')
  }
}

const park = new Student('park', 16) 

console.log(park)
// Student {name: "park", age: 16}
// 	age: 16
// 	name: "park"
// 	__proto__: Person
// 	  constructor: class Student
// 	  learn: ƒ learn()
// 	  sleep: ƒ sleep()
// 	  __proto__:
// 	    constructor: class Person
// 	    sleep: ƒ sleep()
// 	    __proto__: Object

park.name // 'Park'
park.learn() // 'learning...'
park.sleep() // 'sleeping...', 'zzz...'

park instanceof Student // true
park instanceof Person // true
park.__proto__ // Person

References

1. 상속과 프로토타입
2. 클래스

좋은 웹페이지 즐겨찾기