Hàm xây dựng so với hàm Factory


150

Ai đó có thể làm rõ sự khác biệt giữa hàm xây dựng và hàm xuất xưởng trong Javascript.

Khi nào nên dùng cái này thay cho cái kia?

Câu trả lời:


149

Sự khác biệt cơ bản là hàm xây dựng được sử dụng với newtừ khóa (khiến JavaScript tự động tạo một đối tượng mới, đặt thistrong hàm cho đối tượng đó và trả về đối tượng):

var objFromConstructor = new ConstructorFunction();

Hàm nhà máy được gọi là hàm "thông thường":

var objFromFactory = factoryFunction();

Nhưng để nó được coi là một "nhà máy", nó sẽ cần trả về một thể hiện mới của một đối tượng nào đó: bạn sẽ không gọi nó là hàm "nhà máy" nếu nó vừa trả về một boolean hoặc một cái gì đó. Điều này không xảy ra tự động như với new, nhưng nó cho phép linh hoạt hơn đối với một số trường hợp.

Trong một ví dụ thực sự đơn giản, các hàm được tham chiếu ở trên có thể trông giống như thế này:

function ConstructorFunction() {
   this.someProp1 = "1";
   this.someProp2 = "2";
}
ConstructorFunction.prototype.someMethod = function() { /* whatever */ };

function factoryFunction() {
   var obj = {
      someProp1 : "1",
      someProp2 : "2",
      someMethod: function() { /* whatever */ }
   };
   // other code to manipulate obj in some way here
   return obj;
}

Tất nhiên bạn có thể làm cho các chức năng của nhà máy phức tạp hơn nhiều so với ví dụ đơn giản đó.

Một lợi thế cho các chức năng của nhà máy là khi đối tượng được trả về có thể có nhiều loại khác nhau tùy thuộc vào một số tham số.


14
"(EDIT: và đây có thể là một vấn đề vì không có chức năng mới vẫn sẽ chạy nhưng không như mong đợi)." Đây chỉ là một vấn đề nếu bạn cố gắng gọi một hàm xuất xưởng bằng "mới" hoặc bạn cố gắng sử dụng từ khóa "này" để gán cho thể hiện. Nếu không, bạn chỉ cần tạo một đối tượng mới, tùy ý và trả lại nó. Không có vấn đề gì, chỉ là một cách khác, linh hoạt hơn để làm mọi việc, với ít bản tóm tắt hơn và không rò rỉ chi tiết khởi tạo vào API.
Eric Elliott

6
Tôi muốn chỉ ra rằng các ví dụ cho cả hai trường hợp (hàm xây dựng so với hàm nhà máy) phải nhất quán. Ví dụ cho chức năng của nhà máy không bao gồm someMethodcác đối tượng được trả về bởi nhà máy và đó là nơi có một chút sương mù. Bên trong chức năng của nhà máy, nếu chỉ cần thực hiện var obj = { ... , someMethod: function() {}, ... }, điều đó sẽ dẫn đến mỗi đối tượng được trả về giữ một bản sao khác nhau someMethodmà chúng ta có thể không muốn. Đó là nơi sử dụng newprototypebên trong chức năng của nhà máy sẽ giúp ích.
Bharat Khatri

3
Như bạn đã đề cập rằng một số người cố gắng sử dụng các chức năng của nhà máy chỉ vì họ không có ý định để lại lỗi mà mọi người quên sử dụng newvới chức năng xây dựng; Tôi nghĩ đó là nơi mà người ta có thể cần phải xem cách thay thế các nhà xây dựng bằng ví dụ về chức năng của nhà máy và đó là nơi tôi nghĩ rằng tính nhất quán trong các ví dụ là bắt buộc. Dù sao, câu trả lời là đủ thông tin. Đây chỉ là một điểm tôi muốn nêu ra, không phải là tôi đang giảm chất lượng câu trả lời bằng mọi cách.
Bharat Khatri

4
Đối với tôi, ưu điểm lớn nhất của các chức năng của Factory là bạn có được đóng gói tốt hơn và ẩn dữ liệu có thể hữu ích trong một số ứng dụng. Nếu không có vấn đề gì trong việc làm cho mọi thuộc tính và phương thức cá nhân trở nên công khai và dễ dàng sửa đổi bởi người dùng thì tôi đoán chức năng Trình xây dựng là phù hợp hơn trừ khi bạn không thích từ khóa "mới" như một số người làm.
devius

1
@Federico - phương thức nhà máy không phải trả về một đối tượng đơn giản. Họ có thể sử dụng newnội bộ, hoặc sử dụng Object.create()để tạo một đối tượng với một nguyên mẫu cụ thể.
nnnnnn

109

Lợi ích của việc sử dụng các nhà xây dựng

  • Hầu hết các cuốn sách dạy bạn sử dụng các nhà xây dựng và new

  • this đề cập đến đối tượng mới

  • Một số người thích cách var myFoo = new Foo();đọc.

Hạn chế

  • Chi tiết về khởi tạo được rò rỉ vào API gọi (thông qua newyêu cầu), vì vậy tất cả các trình gọi được liên kết chặt chẽ với việc triển khai hàm tạo. Nếu bạn cần sự linh hoạt bổ sung của nhà máy, bạn sẽ phải cấu trúc lại tất cả người gọi (phải thừa nhận trường hợp ngoại lệ, thay vì quy tắc).

  • Quên newlà một lỗi phổ biến như vậy, bạn nên cân nhắc thêm việc kiểm tra bản tóm tắt để đảm bảo rằng hàm tạo được gọi chính xác ( if (!(this instanceof Foo)) { return new Foo() }). EDIT: Vì ES6 (ES2015) bạn không thể quên newvới hàm classtạo, hoặc hàm tạo sẽ gây ra lỗi.

  • Nếu bạn thực hiện instanceofkiểm tra, nó sẽ để lại sự mơ hồ về việc có newcần thiết hay không . Theo tôi, không nên như vậy. Bạn đã thực sự rút ngắn newyêu cầu, điều đó có nghĩa là bạn có thể xóa nhược điểm # 1. Nhưng sau đó, bạn vừa có một chức năng của nhà máy trừ tên , với bản tóm tắt bổ sung, chữ in hoa và thisbối cảnh kém linh hoạt hơn .

Các nhà xây dựng phá vỡ Nguyên tắc Mở / Đóng

Nhưng mối quan tâm chính của tôi là nó vi phạm nguyên tắc mở / đóng. Bạn bắt đầu xuất một hàm tạo, người dùng bắt đầu sử dụng hàm tạo, sau đó xuống đường bạn nhận ra rằng bạn cần sự linh hoạt của một nhà máy, thay vào đó (ví dụ, để chuyển việc triển khai sang sử dụng nhóm đối tượng hoặc để khởi tạo qua các bối cảnh thực thi hoặc để khởi tạo qua các bối cảnh thực thi hoặc để có tính linh hoạt kế thừa hơn bằng cách sử dụng OO nguyên mẫu).

Bạn đang bị mắc kẹt, mặc dù. Bạn không thể thực hiện thay đổi mà không phá vỡ tất cả mã gọi hàm tạo của bạn vớinew . Chẳng hạn, bạn không thể chuyển sang sử dụng nhóm đối tượng để tăng hiệu suất.

Ngoài ra, việc sử dụng các hàm tạo cho bạn một sự lừa đảo instanceofkhông hoạt động trong các bối cảnh thực thi và không hoạt động nếu nguyên mẫu hàm tạo của bạn bị tráo đổi. Nó cũng sẽ thất bại nếu bạn bắt đầu quay trở lạithis từ hàm tạo của mình và sau đó chuyển sang xuất một đối tượng tùy ý, điều bạn phải làm để kích hoạt hành vi giống như nhà máy trong hàm tạo của bạn.

Lợi ích của việc sử dụng nhà máy

  • Ít mã hơn - không yêu cầu nồi hơi.

  • Bạn có thể trả về bất kỳ đối tượng tùy ý nào và sử dụng bất kỳ nguyên mẫu tùy ý nào - giúp bạn linh hoạt hơn để tạo các loại đối tượng khác nhau thực hiện cùng một API. Ví dụ: trình phát phương tiện có thể tạo phiên bản của cả trình phát HTML5 và flash hoặc thư viện sự kiện có thể phát ra các sự kiện DOM hoặc sự kiện ổ cắm web. Các nhà máy cũng có thể khởi tạo các đối tượng trên các bối cảnh thực hiện, tận dụng các nhóm đối tượng và cho phép các mô hình kế thừa nguyên mẫu linh hoạt hơn.

  • Bạn sẽ không bao giờ có nhu cầu chuyển đổi từ nhà máy sang nhà xây dựng, vì vậy tái cấu trúc sẽ không bao giờ là vấn đề.

  • Không mơ hồ về việc sử dụng new. Đừng. (Nó sẽ làm cho thishành vi xấu, xem điểm tiếp theo).

  • thishoạt động như bình thường - vì vậy bạn có thể sử dụng nó để truy cập đối tượng cha mẹ (ví dụ, bên trong player.create(), thisđề cập đến player, giống như bất kỳ lời gọi phương thức nào khác. callapplycũng chỉ định lại this, như mong đợi. Nếu bạn lưu trữ nguyên mẫu trên đối tượng cha, điều đó có thể là một cách tuyệt vời để tự động trao đổi chức năng và cho phép đa hình rất linh hoạt cho việc khởi tạo đối tượng của bạn.

  • Không mơ hồ về việc có nên viết hoa hay không. Đừng. Các công cụ Lint sẽ phàn nàn, và sau đó bạn sẽ cố gắng sử dụng new, và sau đó bạn sẽ hoàn tác các lợi ích được mô tả ở trên.

  • Một số người thích cách var myFoo = foo();hoặc var myFoo = foo.create();đọc.

Hạn chế

  • newkhông hành xử như mong đợi (xem ở trên). Giải pháp: không sử dụng nó.

  • thiskhông đề cập đến đối tượng mới (thay vào đó, nếu hàm tạo được gọi bằng ký hiệu dấu chấm hoặc ký hiệu dấu ngoặc vuông, ví dụ foo.bar () - thisđề cập đến foo- giống như mọi phương thức JavaScript khác - xem lợi ích).


2
Theo nghĩa nào bạn có nghĩa là các nhà xây dựng làm cho người gọi kết hợp chặt chẽ với việc thực hiện của họ? Đối với các đối số của hàm tạo có liên quan, chúng sẽ cần được truyền ngay cả đến hàm xuất xưởng để nó sử dụng chúng và gọi hàm tạo thích hợp bên trong.
Bharat Khatri

4
Liên quan đến vi phạm Mở / Đóng: đây không phải là tất cả về tiêm phụ thuộc? Nếu A cần B, Wether A gọi B () hoặc A mới gọi BFactory.create (), cả hai đều giới thiệu khớp nối. Mặt khác, nếu bạn đưa cho A một thể hiện của B trong gốc thành phần, thì A không cần biết gì về cách B được khởi tạo. Tôi cảm thấy cả nhà xây dựng và nhà máy đều có công dụng của chúng; các nhà xây dựng là để khởi tạo đơn giản, các nhà máy để khởi tạo phức tạp hơn. Nhưng trong cả hai trường hợp, tiêm phụ thuộc của bạn là khôn ngoan.
Stefan Billiet

1
DI tốt cho trạng thái tiêm: cấu hình, đối tượng miền, v.v. Đó là quá mức cho mọi thứ khác.
Eric Elliott

1
Sự ồn ào là yêu cầu newvi phạm nguyên tắc mở / đóng. Xem Medium.com/javascript-scene/ trên để biết một cuộc thảo luận lớn hơn nhiều so với những nhận xét này cho phép.
Eric Elliott

3
Bởi vì bất kỳ hàm nào cũng có thể trả về một đối tượng mới trong JavaScript và khá nhiều trong số chúng làm như vậy mà không có newtừ khóa, tôi không tin rằng newtừ khóa thực sự cung cấp bất kỳ khả năng đọc bổ sung nào. IMO, có vẻ ngớ ngẩn khi nhảy qua các vòng để cho phép người gọi nhập nhiều hơn.
Eric Elliott

39

Một constructor trả về một thể hiện của lớp mà bạn gọi nó. Một chức năng nhà máy có thể trả lại bất cứ điều gì. Bạn sẽ sử dụng hàm xuất xưởng khi bạn cần trả về các giá trị tùy ý hoặc khi một lớp có quy trình thiết lập lớn.


5

Một ví dụ về hàm Con Contortor

function User(name) {
  this.name = name;
  this.isAdmin = false;
}

let user = new User("Jack");
  • newtạo một đối tượng được tạo nguyên mẫu User.prototypevà gọi Uservới đối tượng được tạo làm thisgiá trị của nó .

  • new coi một biểu thức đối số cho toán hạng của nó là tùy chọn:

         let user = new User;

    sẽ gây ra newcuộc gọi Usermà không có đối số.

  • newtrả về đối tượng mà nó đã tạo, trừ khi hàm tạo trả về một giá trị đối tượng , được trả về thay thế. Đây là một trường hợp cạnh mà phần lớn có thể bỏ qua.

Ưu và nhược điểm

Các đối tượng được tạo bởi các hàm xây dựng kế thừa các thuộc tính từ thuộc tính của hàm tạo prototypevà trả về true bằng instanceOftoán tử trên hàm xây dựng.

Các hành vi trên có thể thất bại nếu bạn thay đổi động giá trị của thuộc tính của hàm tạo prototypesau khi đã sử dụng hàm tạo. Làm như vậy là rất hiếm và không thể thay đổi nếu hàm tạo được tạo bằng classtừ khóa.

Các hàm xây dựng có thể được mở rộng bằng cách sử dụng extendstừ khóa.

Hàm xây dựng không thể trả về nulldưới dạng giá trị lỗi. Vì nó không phải là kiểu dữ liệu đối tượng, nên nó bị bỏ qua new.

Một ví dụ về chức năng của nhà máy

function User(name, age) {
  return {
    name,
    age,
  }
};

let user = User("Tom", 23);

Ở đây chức năng nhà máy được gọi là không có new. Hàm hoàn toàn chịu trách nhiệm cho việc sử dụng trực tiếp hoặc gián tiếp nếu các đối số của nó và loại đối tượng mà nó trả về. Trong ví dụ này, nó trả về một [Đối tượng] đơn giản với một số thuộc tính được đặt từ các đối số.

Ưu và nhược điểm

Dễ dàng che giấu sự phức tạp thực hiện của việc tạo đối tượng từ người gọi. Điều này đặc biệt hữu ích cho các chức năng mã gốc trong trình duyệt.

Hàm nhà máy không cần phải luôn trả về các đối tượng cùng loại và thậm chí có thể trả về nulldưới dạng chỉ báo lỗi.

Trong các trường hợp đơn giản, các chức năng của nhà máy có thể đơn giản về cấu trúc và ý nghĩa.

Các đối tượng được trả về thường không được thừa kế từ thuộc tính của hàm nhà máy prototypevà trả về falsetừ đó instanceOf factoryFunction.

Hàm nhà máy không thể được mở rộng một cách an toàn bằng extendstừ khóa vì các đối tượng mở rộng sẽ kế thừa từ thuộc tính chức năng của nhà máy prototypethay vì từ thuộc prototypetính của hàm tạo được sử dụng bởi hàm nhà máy.


1
Đây là một câu trả lời muộn được đăng để trả lời cho câu hỏi này trong cùng một chủ đề,
traktor53

không chỉ "null" mà "mới" cũng sẽ bỏ qua mọi kiểu dữ liệu sớm được trả về bởi hàm điều khiển.
Vishal

2

Các nhà máy "luôn luôn" tốt hơn. Khi sử dụng ngôn ngữ hướng đối tượng thì

  1. quyết định hợp đồng (phương thức và những gì họ sẽ làm)
  2. Tạo giao diện hiển thị các phương thức đó (trong javascript bạn không có giao diện, do đó bạn cần đưa ra một số cách kiểm tra việc thực hiện)
  3. Tạo một nhà máy trả về việc thực hiện từng giao diện cần thiết.

Việc triển khai (các đối tượng thực tế được tạo mới) không được hiển thị cho người dùng / người tiêu dùng của nhà máy. Điều này có nghĩa là nhà phát triển nhà máy có thể mở rộng và tạo các triển khai mới miễn là họ không phá vỡ hợp đồng ... và nó cho phép người tiêu dùng nhà máy chỉ được hưởng lợi từ API mới mà không phải thay đổi mã của họ ... nếu họ đã sử dụng mới và triển khai "mới" thì họ phải đi và thay đổi mọi dòng sử dụng "mới" để sử dụng triển khai "mới" ... với mã nhà máy của họ không thay đổi ...

Các nhà máy - tốt hơn tất cả mọi thứ khác - khung mùa xuân được xây dựng hoàn toàn xung quanh ý tưởng này.


Làm thế nào để nhà máy giải quyết vấn đề này phải thay đổi mọi dòng?
Tên mã Jack

0

Các nhà máy là một lớp trừu tượng, và giống như tất cả các trừu tượng mà chúng có a.cost về độ phức tạp. Khi gặp API dựa trên nhà máy, việc tìm ra nhà máy dành cho API cụ thể có thể là thách thức đối với người tiêu dùng API. Với các nhà xây dựng khám phá là tầm thường.

Khi quyết định giữa các nhà máy và nhà máy, bạn cần phải quyết định xem sự phức tạp có hợp lý bởi lợi ích hay không.

Đáng lưu ý rằng các nhà xây dựng Javascript có thể là các nhà máy tùy ý bằng cách trả lại một cái gì đó không phải cái này hoặc không xác định. Vì vậy, trong js, bạn có thể tận dụng tốt nhất cả hai thế giới - API có thể khám phá và nhóm đối tượng / bộ đệm.


5
Trong JavaScript, chi phí sử dụng các hàm tạo cao hơn chi phí sử dụng các nhà máy vì bất kỳ hàm nào trong JS đều có thể trả về một đối tượng mới. Các nhà xây dựng thêm độ phức tạp bằng cách: Yêu cầu new, Thay đổi hành vi this, Thay đổi giá trị trả về, Kết nối một tham chiếu nguyên mẫu, Kích hoạt instanceof(nằm và không nên được sử dụng cho mục đích này). Rõ ràng, tất cả những thứ đó là "tính năng". Trong thực tế, họ làm tổn thương chất lượng mã của bạn.
Eric Elliott

0

Đối với sự khác biệt, Eric Elliott làm rõ rất rõ,

Nhưng đối với câu hỏi thứ hai:

Khi nào nên dùng cái này thay cho cái kia?

Nếu bạn đến từ nền tảng hướng đối tượng, hàm Con constructor trông tự nhiên hơn đối với bạn. Bằng cách này, bạn không nên quên sử dụng newtừ khóa.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.