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

Chanki Hong·2022년 11월 28일
0

JavaScript

목록 보기
15/30
post-thumbnail

객체지향 프로그래밍(object-oriented programming)

  • 소프트웨어를 설계하고 구축하기 위한 컴퓨터 프로그래밍 패러다임(Paradigm) 중 하나로, 소프트웨어를 현실 세계처럼 생각하고 만드는 것.
  • 현실 세계의 개념을 모델링하고 코드를 구조화하는 방법으로, 코드의 재사용성과 유지보수성을 높임.

클래스(class; 객체 생성자 함수)

  • 객체지향 프로그래밍에서, 클래스는 데이터와 메서드(함수)를 포함하는 추상적인 설계 청사진(blueprint)을 의미함.
  • 클래스를 기반으로 실제 데이터를 담는 객체를 생성할 수 있음. 객체는 클래스의 인스턴스(instance)라고도 불림.
  • 즉, 객체는 현실 세계의 개념 또는 데이터를 나타내며, 클래스는 이러한 객체를 만들기 위한 템플릿이라고 할 수 있음.
  • 클래스(class)는 추상적이고 범용적임. (어떤 자동차)
  • 인스턴스(instance)는 구체적이고 한정적임. (특정 자동차)
  • 클래스는 계층적으로 분류. (슈퍼클래스; superclass, 서브클래스; subclass)
    • 운송수단이라는 클래스 아래 자동차, 비행기, 보트, 오토바이 클래스가 존재한다면,
    • 운송수단은 자동차의 슈퍼클래스이고 자동차는 운송수단의 서브클래스.
    • 또한 서브클래스가 서브클래스를 가질 수 있음.
  • 클래스의 식별자는 대문자로 시작해 구분해 줌.
  • instanceof 연산자를 이용해 인스턴스의 소속여부를 알 수 있음.
  • 클래스의 설계 의도대로 사용한다면, 데이터 프로퍼티는 항상 인스턴스에서 정의해야 함. (프로토타입 체인에서 정의 지양)
  • 하지만 강제하는 장치가 없으므로, 명확하게 확인하기 위해 hasOwnProperty 사용 권장.
  • hasOwnProperty 에 대한 내용.
function nirvana() {}
function queen() {}
const x = new nirvana(); // x는 nirvana의 인스턴스.

console.log(x instanceof nirvana); // true
console.log(x instanceof queen); // false

ES5

  • 클래스는 함수 형태.
  • new 키워드로 인스턴스 생성.
function Animal(type, name, sound) {
  this.type = type;
  this.name = name;
  this.sound = sound;
  this.say = function () {
    console.log(this.sound);
  };
}
const dog = new Animal('개', '멍멍이', '멍멍');
const cat = new Animal('고양이', '야옹이', '야옹');
dog.say(); // 멍멍
cat.say(); // 야옹

ES6

  • calss 키워드로 클래스 생성.
  • 단축 문법이며 기존의 클래스의 개념이 변한 것이 아님.
  • constructor 키워드로 생성자 함수를 구분.
  • new 키워드로 인스턴스 생성.
class Animal {
  constructor(type, name, sound) {
    this.type = type;
    this.name = name;
    this.sound = sound;
    this.say = function () {
      console.log(this.sound);
    };
  }
}

const dog = new Animal('개', '멍멍이', '멍멍');
const cat = new Animal('고양이', '야옹이', '야옹');

dog.say(); // 멍멍
cat.say(); // 야옹

클래스 상속

  • 상속은 클래스가 다른 클래스로부터 속성과 메서드를 상속받는 것을 의미.
  • 서브클래스를 만들 수 있고,
  • 인스턴스 또한 클래스의 모든 기능을 상속.
  • 클래스에 계층이 생기므로 적절한 위치에 메서드의 정의가 중요.

ES5

/* 객체 생성자 상속(call함수)을 이용한 경우. */
function Animal(type, name, sound) {
  this.type = type;
  this.name = name;
  this.sound = sound;
}
Animal.prototype.say = function () {
  console.log(this.sound);
};

function Dog(name, sound) {
  Animal.call(this, '개', name, sound); // 여기서 this는 함수 Dog을 의미.
}
function Cat(name, sound) {
  Animal.call(this, '고양이', name, sound); // 여기서 this는 함수 Cat을 의미.
}
Dog.prototype = Animal.prototype;
Cat.prototype = Animal.prototype;

const dog = new Dog('멍멍이', '멍멍');
const cat = new Cat('야옹이', '야옹');

ES6

  • extends 키워드를 이용해 상속.
  • constructor 를 선언하는 과정에서 super 키워드를 이용해서 슈퍼클래스의 생성자를 호출 및 기본값 설정.
// ex1.
class Animal {
  constructor(type, name, sound) {
    this.type = type;
    this.name = name;
    this.sound = sound;
  }
  say() {
    console.log(this.sound);
  }
}

class Dog extends Animal {
  constructor(name, sound) {
    super('개', name, sound); // super 키워드를 이용하여 슈퍼클래스의 생성자를 호출.
  }
}
class Cat extends Animal {
  constructor(name, sound) {
    super('고양이', name, sound); // super 키워드를 이용하여 슈퍼클래스의 생성자를 호출.
  }
}

const dog = new Dog('멍멍이', '멍멍');
const cat = new Cat('야옹이', '야옹');

dog.say();
cat.say();
// ex2.
class Food {
  constructor(name) {
    this.name = name;
    this.brands = [];
  }
  // brand를 받아 배열 brands에 넣어주는 메서드
  addBrand(brand) {
    this.brands.push(brand);
  }
  print() {
    console.log(`"${this.name}" 판매 음식점: ${this.brands.join(', ')}.`); // join을 이용해 배열 합침.
  }
}

const pizza = new Food('피자');
pizza.addBrand('파파존스');
pizza.addBrand('도미노피자');
console.log(pizza); // Food { name: '피자', brands: [ '파파존스', '도미노피자' ] }

const chicken = new Food('치킨');
chicken.addBrand('푸라닭');
chicken.addBrand('교촌치킨');
console.log(chicken); // Food { name: '치킨', brands: [ '푸라닭', '교촌치킨' ] }

pizza.print(); // "피자" 판매 음식점: 파파존스, 도미노피자.
chicken.print(); // "치킨" 판매 음식점: 푸라닭, 교촌치킨.
  • 클래스에 계층이 생기므로 적절한 위치에 메서드의 정의가 중요.
    • 슈퍼클래스로 운송수단이 있고, 서브클래스로 자동차와 보트가 있을 때
    • deployAirbags(에어백 작동) 클래스는 보트에 있을 필요가 없으니 자동차에 배치하고,
    • addPassenger(탑승자 추가) 클래스는 자동차와 보트에 존재가능 하기때문에 슈퍼클래스에 배치.
class Vehicle {
  constructor() {
    this.passengers = [];
    console.log('Vehicle created');
  }
  addPassenger(p) {
    this.passengers.push(p);
  }
}

class Car extends Vehicle {
  constructor() {
    super();
    console.log('Car created');
  }
  deployAirbags() {
    console.log('BWOOSH!');
  }
}

const v = new Vehicle();
v.addPassenger('Hong');
v.addPassenger('Jung');
console.log(v.passengers); // [ 'Hong', 'Jung' ]

const c = new Car();
c.addPassenger('Park');
c.addPassenger('Kim');
console.log(c.passengers); // [ 'Park', 'Kim' ]

c.deployAirbags(); // BWOOSH!
v.deployAirbags(); // TypeError: v.deployAirbags is not a function

메서드

인스턴스 메서드(프로토타입)

  • 클래스의 인스턴스에서 사용할 수 있는 인스턴스 메서드.
  • 모든 함수에는 prototype 이라는 특별한 프로퍼티가 존재.
  • 객체 내장 메서드와 프로퍼티는 주로 프로토타입 체인으로 상속.
  • 일반적인 함수에서는 사용할 일이 없지만, 클래스에서는 중요함.
  • 클래스로 만든 인스턴스에서 재사용(공유)하는 값 또는 함수를 설정하는 것.
  • 특히 new 키워드로 새 인스턴스를 만들었을 때 매우 중요함.
  • 새로운 인스턴스는 클래스의 prototype 프로퍼티를 인스턴스의 __proto__프로퍼티에 저장.
  • 인스턴스에서 메서드나 프로퍼티를 정의하면 프로토타입을 가리는 효과가 있음.
  • JS의 우선순위가 인스턴스 => 프로토타입이기 때문. (동적 디스패치; 동적 메서드 호출)

ES5

  • .prototype 키워드 사용.
function Animal(type, name, sound) {
  this.type = type;
  this.name = name;
  this.sound = sound;
}

Animal.prototype.say = function () {
  console.log(this.sound);
};

const dog = new Animal('개', '멍멍이', '멍멍');
const cat = new Animal('고양이', '야옹이', '야옹');
dog.say(); // 멍멍
cat.say(); // 야옹

ES6

  • 클래스 안(constructor 밖)에서 함수를 만들게 되면, 자동으로 프로토타입이 됨.
class Animal {
  constructor(type, name, sound) {
    this.type = type;
    this.name = name;
    this.sound = sound;
  }
  say() { // constructor 밖에서는 프로토타입으로 등록.
    console.log(this.sound);
  }
}

const dog = new Animal('개', '멍멍이', '멍멍');
const cat = new Animal('고양이', '야옹이', '야옹');

dog.say(); // 멍멍
cat.say(); // 야옹
/*
getter와 setter를 이용해서 프로퍼티를 보호.
보호하기 위한 프로퍼티는 _(언더바)를 붙여서,
외부에서 접근하면 안 되는 프로퍼티라는 것을 알림.
'가짜 접근 제한'
*/
class Car {
  constructor(make, model) {
    this.make = make;
    this.model = model;
    this._userGears = ['P', 'N', 'R', 'D']; // 사용가능한 기어 목록
    this._userGear = this._userGears[0];
  }
  get userGear() {
    return this._userGear;
  }
  set userGear(value) {
    if (this._userGears.indexOf(value) < 0) {
      // indexOf는 배열에서 찾는 원소의 인덱스값 반환.
      // 기어 목록에 한정한 값만 받기 위함.
      throw new Error(`Invalid gear: ${value}`);
    } // if의 조건은 사용가능한 기어 목록에 없을 때는 오류를 출력하기위함
  }
  // 기어를 바꾸는 프로토타입.
  shift(gear) {
    this._userGear = gear;
  }
}
const car1 = new Car('Tesla', 'Model S');
const car2 = new Car('Mazda', 'Mx5-rf');
car1.shift('D');
car2.shift('R');
console.log(car1.userGear); // D
console.log(car2.userGear); // R

클래스 메서드

  • 인스턴스에 적용되지 않는 정적 메서드.
  • this 가 인스턴스가 아니라 클래스에 묶임.
  • 정적 메서드에서 this 대신 클래스 이름을 사용하는 것이 상기하기 쉬움.
  • 클래스에 관련되지만 인스턴스와는 관련이 없는 범용적인 작업에 사용.
    • 예를 들어 자동차 차대번호(VIN)를 중복없이 부여하는 메서드.
    • 인스턴스 내에서는 다른 인스턴스와 중복여부를 확인해 부여하기 힘듬.

ES6

  • static 키워드 사용.
class Car {
  // VIN을 생성하는 메서드.
  static getNextVin() {
    return Car.nextVin++;
  }
  constructor(make, model) {
    this.make = make;
    this.model = model;
    this.vin = Car.getNextVin();
  }
  // 같은 제조사, 같은 모델인지 조회하는 메서드.
  static areSimilar(car1, car2) {
    return car1.make === car2.make && car1.model === car2.model;
  }
  // 같은 자동차인지 조회하는 메서드.
  static areSame(car1, car2) {
    return car1.vin === car2.vin;
  }
}
Car.nextVin = 0; // nextVin값 초기화.

const car1 = new Car('Tesla', 'X');
const car2 = new Car('Mazda', 'MX-5');
const car3 = new Car('Mazda', 'MX-5');

console.log(car1.vin); // 0
console.log(car2.vin); // 1
console.log(car3.vin); // 2

console.log(Car.areSimilar(car1, car2)); // false
console.log(Car.areSimilar(car2, car3)); // true
console.log(Car.areSame(car2, car3)); // false
console.log(Car.areSame(car2, car2)); // true

다형성(polymorphism)

  • 객체지향 언어에서 여러 슈퍼클래스의 멤버인 인스턴스를 가리킴.
  • 대부분의 객체지향 언어에서 다형성은 특별한 경우에 속하지만,
  • JS는 어디서든 객체를 쓸 수 있어, 어떤 면에서는 JS의 객체는 모두 다형성을 가짐.
  • 또한 JS의 모든 객체는 루트 클래스인 Object의 인스턴스고,
  • toString 같은 중요한 메서드를 상속하기 위해.
class Vehicle {
  constructor() {}
}
class Car extends Vehicle {
  constructor() {
    super();
  }
}
class Boat extends Vehicle {
  constructor() {
    super();
  }
}

const v = new Vehicle();
const c = new Car();
const b = new Boat();

// 모든 객체는 루트 클래스 Object의 인스턴스.
console.log(v instanceof Object); // true
console.log(c instanceof Object); // true
console.log(b instanceof Object); // true

console.log(v instanceof Vehicle); // true
console.log(c instanceof Vehicle); // true
console.log(b instanceof Vehicle); // true

console.log(v instanceof Car); // false
console.log(c instanceof Car); // true
console.log(b instanceof Car); // false

console.log(v instanceof Boat); // false
console.log(c instanceof Boat); // false
console.log(b instanceof Boat); // true

0개의 댓글