Front-end/JavaScript

JS OOP 1편 - factory code

파리외 개발자 2023. 1. 24. 22:14

Object Oriented Programming

객체 지향 프로그램은 패러다임이며

코드를 객체 지향의 방식으로 만들겠다는 방법론이다.

JS는 크게 FP, OOP로 나눌 수 있으며 이 중 하나를 선택해서 써야 하는 것이 아닌

상황에 따라 둘 모두를 적절하게 사용할 수 있는 것이 바람직하다고

개인적으로 생각한다.

그러니깐 둘 다 잘해야 된다는 말이다.

Original code

const elf1 = {
  name: "Orwell",
  weapon: "bow",
  attack() {
    return "attack with" + elf1.weapon;
  },
};

const elf2 = {
  name: "Sally",
  weapon: "bow",
  attack() {
    return "attack with" + elf2.weapon;
  },
};

elf1.attack();
elf2.attack();

위처럼 하나의 엘프 개체를 생성하기 위해서 

매번 객체를 생성해준다면 중복되는 코드를 계속해서 작성해줘야 할뿐더러

메모리 공간도 불필요하게 차지하게 될 것이다.


factory function

//factory functions
function createElf(name, weapon) {
  return {
    name,
    weapon,
    attack() {
      return "attack with" + weapon;
    },
  };
}

const peter = createElf("Peter", "stones");
peter.attack();
const sam = createElf("Sam", "fire");
sam.attack();

함수를 사용해 인자에 따라 객체를 생성해 준다면

매번 새로운 객체를 만들어 줄 필요 없이 인자만 투입해 주면 된다.

하지만 역시나 attack함수는 모든 객체에 있어서 공통되는 부분인데

매 객체가 생성될 때마다 같이 생성될 필요는 없어 보인다.


Object.create( )

const elfFunctions = {
  attack() {
    return "attack with" + this.weapon;
  },
};

function createElf(name, weapon) {
  return {
    name,
    weapon,
  };
}

const peter = createElf("Peter", "stones");
peter.attack = elfFunctions.attack;
peter.attack();
const sam = createElf("Sam", "fire");
sam.attack = elfFunctions.attack;
sam.attack();

중복되는 함수를 객체의 메서드로 따로 생성하고

객체가 생성될 때 속성으로 추가를 해준다.

공통되는 함수를 매번 생성하지 않아도 되어 메모리 공간을 아낄 수 있지만

여전히 매 객체에 속성으로 추가해 주는 것은 번거롭다.

//Object.create
const elfFunctions = {
  attack() {
    return "attack with" + this.weapon;
  },
};

function createElf(name, weapon) {
  let newElf = Object.create(elfFunctions);
  newElf.name = name;
  newElf.weapon = weapon;
  return newElf;
}

const peter = createElf("Peter", "stones");
peter.attack();
const sam = createElf("Sam", "fire");
sam.attack();

엘프 객체를 생성할 때 생성되는 객체인 newElf를

Object.create를 통해 공통함수가 메서드로 존재하는 elfFunctions객체를 상속받도록 해준다.

그 후 전달받은 인자를 속성으로 추가하고 리턴해주면 

생성되는 모든 엘프 객체는 elfFunctions을 상속하기에 attack메서드를 사용할 수 있다.


Constructor functions

Object.create방식은 비교적 최근의 방식이며 

JS가 prototype을 기반으로 OOP와 상속을 구현하는 것을 직관적으로 표현한 방식이라고 생각된다.

constructor를 사용한 OOP는 내부는 prototype을 기반으로 동작하는 방식은 동일하지만

기존의 클래스 방식의 OOP구현방식과 표현이 비슷하다.

//Constructor Functions
function Elf(name, weapon) {
  this.name = name; //this pointed toto or alisa when use new keyword
  this.weapon = weapon;
}

Elf.prototype.attack = function () {
  return "attack with" + this.weapon;
};

const toto = new Elf("Toto", "knifes");
toto.attack();
const alisa = new Elf("Alisa", "fist");
alisa.attack();

Elf는 그 자체로 생성자 함수이며 함수 내의 this는 

new 연산자를 사용했을 때 생성되는 객체(toto, alisa)를 가리키게 된다.

또한 모든 함수는 프로토타입 객체를 가지며 해당 객체에 attack메서드를 속성으로 추가해 준다면

앞으로 Elf함수로 생성되는 모든 객체는 해당 객체의 attack메서드를 상속받아 사용할 수 있다.

Elf함수의 프로토타입 객체는 toto객체의 프로토타입이다.

const Elf3 = new Function(
  "name",
  "weapon",
  `this.name = name;
  this.weapon = weapon;`
);

const sarah = new Elf3("Sarah", "fireworks");

생성자 함수 방식으로 객체를 생성하는 것은 사실상 특별한 것이 아닌

null과 undefined를 제외한 모든 변수타입의 이름으로 된 생성자 함수가 있는 것과

프로토타입 객체를 생각해 보면 된다. 

일반적으로 함수를 생성할 때는 선언식, 표현식 등을 사용하지만

함수를 생성하는 생성자를 통해 함수를 생성할 수 있는 것처럼

생성자 함수방식으로 객체를 생성할 때도 비슷한 방식으로 동작한다고 볼 수 있다.

Elf.prototype.build = function () {
  function building() {
    return this.name + "'s house";
  }
  return building();
};

toto.build();

만약 생성자 함수의 프로토타입 객체에 메서드를 추가할 때는 this가 가리키는 곳을 유의해서 작성해야 한다.

위 코드에서 this는 dynamic 하게 동작하기 때문에 전역인 window를 가리키게 되어서

name값을 찾지 못하게 된다.

Elf.prototype.build = function () {
  const self = this;
  function building() {
    return self.name + "'s house";
  }
  return building();
};

toto.build();

외부의 함수는 객체에 종속된 메서드이기 때문에 this는 생성되는 객체를 가리키며

이 값을 self에 저장하고 내부 함수에 전달해 준다면

name은 생성된 객체의 이름을 정상적으로 출력해 준다.

Elf.prototype.build = function () {
  function building() {
    return this.name + "'s house";
  }
  return building.bind(this);
};

toto.build();

혹은 bind 등으로 this(여기선 객체 Elf를 가리키는)를

지정해 주거나 lexical scope를 가지는 arrow function으로 작성한다면

정상적으로 this는 생성되는 객체를 가리키게 된다.