So sánh Factory Function, Constructor và Class

So sánh Factory Function, Constructor và Class

Trong bài viết này, chúng ta sẽ cùng tìm hiểu về Factory Function và chỉ ra điểm khác biệt của Factory Function, Constructor và Class.

Để tạo một object với các method và data dựng sẵn, chúng ta có các phương pháp để làm trong JS:

// class
class ClassCar {
    drive() {
        console.log('GOOO');
    }
}

const car1 = new ClassCar();
console.log(car1.drive());

// constructor function
function ConstructorCar() {}

ConstructorCar.prototype.drive = function() {
    console.log('GOOO');
}

const car2 = new ConstructorCar();
console.log(car2.drive());

// factory
const proto = {
    drive() {
        console.log('GOOO');
    }
};

const factoryCar = () => Object.create(proto);

const car3 = factoryCar();
console.log(car3.drive());Code language: JavaScript (javascript)

Về tính năng, thì cả 3 là như nhau, và có thể dùng thay thế cho nhau được.

Có thể bạn chưa biết: Trong JS, bất cứ function nào trả về một object, mà không phải là constructor function hay class, thì được gọi là factory function

Vài so sánh giữa Factory Function và Constructor

Constructor bắt buộc phải khởi tạo bằng keyword new. Factory thì không.

Vậy thì keyword new của Constructor và Class nó làm gì?

  1. Khởi tạo một object mới và bind giá trị cho từ khóa this
  2. Bind instance.__proto__ vào Constructor.prototype
  3. Bind instance.__proto__.constructor vào Constructor
  4. Ngầm trả về this (refer vào giá trị instance)

Về mặt lợi ích khi sử dụng Constructor và Class

  • Dễ tiếp cận với những người có xuất phát điểm từ những ngôn ngữ lập trình có hỗ trợ class
  • this luôn prefer đến một object mới
  • Nhiều người thích cách viết myFoo = new Foo()

Nhược điểm của Constructor và Class

  • Bắt buộc phải dùng từ khóa new để khởi tạo
  • Tất cả những thằng sử dụng đều dùng chung một constructor, rất khó nếu muốn thay đổi hiện thực bên trong constructor từ bên ngoài.
  • Không đáp ứng dụng nguyên tắc open/closed: API chỉ cho phép extend, nhưng không cho phép modify
  • Kết thừa class và các vấn đề mà nó sinh ra là câu chuyện không mới khi các bạn viết object oriented (có thể tra cứu google bằng các từ khóa sau: the fragile base class problem, the gorilla banana problem, the duplication by necessity problem)

Lợi ích việc sử dụng Factory

  • Linh động hơn class và constructor function
  • Bạn sẽ không bao giờ đụng vô từ khóa extend vốn là một con đường đã gây ra đau khổ bấy năm nay.
  • Không còn cần dùng từ khóa new, không còn loằn ngoằn rối rắm với từ khóa this
  • Nhiều người thích đọc code dạng này myFoo = createFoo()

Nhược điểm của Factory

  • Không thể check instanceof, do không có liên kết giữa instance và Factory.prototype
  • this không còn refer vào object mới tạo (this cũng có ưu nhược điểm của nó chứ không phải toàn nhược điểm)
  • Có thể chậm hơn một chút. Thật ra cũng không cần quá bận tâm việc này, vì chưa ai chứng minh được nó ảnh hưởng đến tốc độ, hiệu năng của ứng dụng, lý thuyết là chậm hơn xíu xiu nhưng máy tính giờ nhanh lắm rồi.

Có nên dùng Factory Function?

Có rất nhiều quan điểm đưa ra để khuyên bạn đừng dùng contructor trong JS, bài viết Constructors Are Bad For JavaScript có liệt kê khá khá lý do bạn có thể tham khảo.

Một ví dụ tương đối đầy đủ về factory function

const Player = (name, level) => {
  let health = level * 2;
  const getLevel = () => level;
  const getName  = () => name;
  const die = () => {
    // uh oh
  };
  const damage = x => {
    health -= x;
    if (health <= 0) {
      die();
    }
  };
  const attack = enemy => {
    if (level < enemy.getLevel()) {
      damage(1);
      console.log(`${enemy.getName()} has damaged ${name}`);
    }
    if (level >= enemy.getLevel()) {
      enemy.damage(1);
      console.log(`${name} has damaged ${enemy.getName()}`);
    }
  };
  return {attack, damage, getLevel, getName}
};

const jimmie = Player('jim', 10);
const badGuy = Player('jeff', 5);
jimmie.attack(badGuy);Code language: JavaScript (javascript)

Để kế thừa trong factory function, các bạn có thể làm như sau

const Person = (name) => {
  const sayName = () => console.log(`my name is ${name}`)
  return {sayName}
}

const Nerd = (name) => {
  // tạo Person, sau đó trả về hàm sayName
  const {sayName} = Person(name)
  const doSomethingNerdy = () => console.log('nerd stuff')
  return {sayName, doSomethingNerdy}
}

const jeff = Nerd('jeff')

jeff.sayName() //my name is jeff
jeff.doSomethingNerdy() // nerd stuffCode language: JavaScript (javascript)

Với cách trên, chỉ định rất cụ thể hàm nào sẽ được trả về, còn nếu muốn trả tất cả những gì của Person, đơn giản là merge object

const Nerd = (name) => {
  const prototype = Person(name)
  const doSomethingNerdy = () => console.log('nerd stuff')
  return Object.assign({}, prototype, {doSomethingNerdy})
}Code language: JavaScript (javascript)

Nghe có vẻ hơi trái tai, mặc dù JS đã có hỗ trợ class, nhưng các bạn đừng nên dùng nó.

Các bạn có thể tham khảo các bài viết hay về JavaScript tại đây.


Hãy tham gia nhóm Học lập trình để thảo luận thêm về các vấn đề cùng quan tâm.

Leave a Reply

Your email address will not be published. Required fields are marked *