Design Pattern | Factory Method Pattern

Giống như một nhà máy trong thế giới thực, mục đích của các nhà máy này trong lập trình hướng đối tượng là tạo ra các đối tượng. Sử dụng nhà máy, làm cho phần mềm của bạn dễ bảo trì và thay đổi hơn vì việc tạo đối tượng xảy ra trong các nhà máy. Các phương thức sử dụng nhà máy này có thể sử dụng vào hành vi khác.

Hãy tưởng tượng rằng phần mềm của bạn triển khai một cửa hàng trực tuyến và bạn muốn tạo ra các đối tượng để bán trong cửa hàng đó. Ví dụ một cửa hàng bán dao chẳng hạn. Có rất nhiều loại dao, nhưng hãy bắt đầu với dao bít tết (steak knives) và dao đầu bếp (chef knives). Bạn có một superclass dao, với các lớp phụ SteakKnife và ChefsKnife. Bạn viết một phương thức orderKnife:

Đầu tiên, sẽ tạo nên đối tượng mua này, sau đó tạo nên những lô hàng. Và các chế phẩm trên con dao là: mài, đánh bóng, gói. Phương pháp của bạn giống như thế này: Phương thức đầu tiên khai báo một biến knife, sẽ tham chiếu đến đối tượng dao được tạo, các điều kiện xác định lớp con dao nào (sub class) thực sự được tạo. Hành động thực sự khởi tạo một lớp để tạo một đối tượng của một loại cụ thể gọi là Concrete Instantiation ( khởi tạo cụ thể ). Trong Java, bạn thường thực hiện Concrete Instantiation với toán tử mới. Bạn sẽ thấy việc khởi tạo đối tượng cụ thể sẽ là mục đích chính cho nhà máy của bạn. Bước tiếp theo là gọi một số phưong thức phổ biến cho các loại đối tượng dao khác nhau như mài, đánh bóng, và đóng gói. Những phương pháp này lại không quan tâm con dao được tạo ra trong quy trình như thế nào ( tạo lớp knive), tất cả những gì họ quan tâm là muốn con dao hoạt động.

Hãy tưởng tượng tiếp là cửa hàng của bạn sẽ thêm nhiều loại dao hơn khi doanh số của nó được cải thiện. Các lớp con mới được thêm vào khi cần thiết. Dao bánh mì (Bread knive), dao gọt (Paring knive) .v.v Trong trường hợp này, danh sách các điều kiện phát triển và tăng lên khi các loại dao mới được thêm vào. Lưu ý một điều là những gì chúng ta làm với các con dao sau khi nó tạo ra sẽ không thay đổi. Tất cẩ những con dao này vẫn cần được mài, đánh bóng, và đóng gói. Sự việc bắt đầu khá phức tạp.

Điều gì sẽ xảy ra thay vì làm dao trong cửa hàng, bạn sẽ làm chúng ở nơi khác ? Giống như trong thế giới thực, các vật thể thường được sản xuất trong nhà máy. Chúng ta có thể tạo ra một đối tượng nhà máy có vai trò là tạo các đối tượng sản phẩm thuộc các loại cụ thể. Vì vậy, việc mài, dũa và đánh bóng sẽ giữ nguyên vị trí của phương thức orderKnife, nhưng bạn sẽ giao trách nhiệm tạo ra đối tượng sản phẩm cho một đối tượng khác, một nhà máy sản xuất dao. Bạn sẽ chuyển đoạn code để quyết định loại dao nào sẽ được tạo ra và mã để quyết định loại dao nào sẽ được khởi tạo vào lớp nhà máy này.



Lớp knifeFactory được hiển thị ở đây với phương thức createKnife. Một số code khác sẽ khởi tạo đối tượng nhà sản xuất dao. Nhưng sau khi thực hiện xong, bạn có thể dùng nó để tạo phương thức dao rồi tạo các đối tượng daoo thuộc các loại cụ thể. Đây gần như là cùng một đoạn code giống như để tạo một đối tượng dao trong ứng dụng trước. Nó như vừa được chuyển sang một phương thức của một lớp mới gọi là knifeFactory. Nói chung, factory object  là một instance của một lớp như vậy, có một phương thức để tạo các đối tượng sản phẩm.

Bây giờ, knifeFactory có thể được sử dụng bởi class knifeStore, nói cách khác, knifeStore hiện là khách hàng của của knifeFactory, nhìn vào ví dụ này:


Trong ví dụ này, đối tượng knifeFactory, sẽ được truyền vào hàm tạo cho lớp knifeStore, phương thức orderKnife rất giống với trước đây. Tuy nhiên, thay vì tự thực hiện khởi tạo cụ thể, nó ủy thác nhiệm vụ này cho đối tượng nhà máy. Bạn đã đạt được những gì ? Trước hết, knifeStore và phwog thức orderKnife của nó  có thể không phải là khách duy nhất của knifeFactory của bạn. Các khách hàng khác có thể sử dụng knifeFactory để tạo dao cho các mục đích khác. Có thể có một phương pháp cho các đơn đặt hàng lớn hoặc thử nghiệm bình đẳng. Vì tất cả các việc tạo dao thực tết xảy ra trong knifeFactory, bạn chỉ cần thêm các loại dao mới vào knifeFactory mà không cần sữa đổi code client.

Nếu có nhiều khách muốn khởi tạo cùng một nhóm các class, thì bằng cách sử dụng đối tượng nhà máy, bạn đã cắt bỏ code thừa và làm cho phần mềm dễ sửa đổi hơn. Thay vì săn lùng nhiều đoạn mã khởi tạo tương tự, bạn chỉ cần thêm hoặc xóa một lớp con được khởi tạo, bạn chỉ cần thay đổi nhà máy của mình, thay vì mọi khách hàng.

Nhìn vào phương thức orderKnife. Theo như nó liên quan, miễn là nó nhận được một đối tượng dao từ nhà máy, nó có thể đáp ứng trách nhiệm của mình. Điều này cho phép nhà phát triển thực hiện các thay đổi đối với khởi tạo cụ thể (concrete instantiaton) mà không cần chạm vào phương thức máy khách. Phương thức máy khách (client) không cần đặt tên cho một lớp dao cụ thể mà bây giờ xử lý với việc khái quát hóa dao. Điều này được gọi là mã hóa cho một giao diện, không phải là một triển khai.

Hãy nghĩ về điều này giống như một cuộc sống ngoài đời thực. Thông thường, khi một cửa hàng đang bán một thứ gì đó như một con dao trong ví dụ của chúng ta, chính cửa hàng đó không tạo ra con dao đó. thay vào đó, cửa hàng lấy dao của họ từ  nhà máy sản xuất dao, nhà máy sản xuất dao này làm dao cho cửa hàng, nhưng cũng có thể còn có các cửa hàng khác. Nếu nhà máy thay đổi cách sản xuất dao, thì các cửa hàng không quan tâm đến miễn là họ nhận được dao đúng loại. Vì vậy, chúng ta có thể sử dụng một đối tượng nhà máy để tạo các đối tượng sản phẩm. Mặc dù nó là một kỹ thuật hữu ích, nhưng đối tượng nhà máy không thực sự là một trong bốn nhóm thiết kế (gang of four design pattern). Chúng ta hãy tìm hiểu mô hình Factory method pattern.

Factory Method Pattern tiếp cận việc tạo ra câc loại đối tượng cụ thể theo một cách khác. Thay vì sử dụng một đối tượng riêng biệt, một đối tượng nhà máy để tạo ra các đối tượng, thì The factory method sử dụng một phương thức riêng biệt trong cùng một lớp để tạo các đối tượng. Bạn sẽ thấy sức mạnh của The factory method từ cách nó tạo ra một sản phẩm dao cụ thể. Để tạo các đối tượng sản phẩm chuyên biệt, đối tượng nhà máy sẽ tiếp cận subclass của the factory class. Ví dụ: một lớp con của nhà máy sản xuất dao được gọi là Budget Knife Factory có thể tạo ra các sản phẩm dao đầu bếp  quality và dao bít tết quality. Một nhà máy sản xuất dao chất lượng có thể tạo ra các đối tượng sản phẩm dao đầu bếp chất lượng và dao bít tết chất lượng.

Thay vào đó, một phương pháp tiếp cận Method Factory, có một con dao Budget Knife của cửa hàng dao.

Bỏ dở, giờ viết tiếp ..

The budget knife store chứa một method, the factory method chịu trách nhiệm tạo ra budget chefs và budget steak knife. Mục đích thiết kế của method này là xác định một giao diện để tạo các đối tượng, nhưng để các lớp con quyết định xem lớp nào sẽ được khởi tạo, điều này làm như thế nào ?

Trước tiên, hãy nhìn vào superclass knifeStore, trước hết, knifeStrore phải là lớp abstract, điều này có nghĩa là chúng ta không thể khởi tạo một cửa hàng dao ( can not  instantite).
Thay vào đó, bạn sẽ cần phải có các lớp con (subclass) của knifeStore, chẳng hạn như là class budget knife store hay là quality knife store. Các lớp con (subclass) này sẽ kế thừa cùng một phương thức orderKnife. Cũng giống như trước đây, phương thức orderKnife sử dụng một nhà máy. Nhưng bây giờ, nó lại là một phương thức nhà máy (method factory). Factory method trong ví dụ này là createdKnife. Nó được khai báo trong superclass, nhưng nó là abstract (trừu tượng) và trống rỗng (empty).  Chúng ta để trống method factory và đặt nó là abstract bởi vì chúng ta muốn nó được xác định bởi các lớp con (sub-class). Bất cứ khi nào  một lớp con knifeStrore được định nghĩa, nó phải định nghĩa phương thức createKnife này. Giờ chúng ta hãy xem một lớp con (subclass) của knifeStore sẽ trông như thế này:

Bây giờ, bạn sẽ instantiate budget KnifeStore. The budget knifeStore có method riêng để tạo ra đối tượng knife. Bạn không thể định nghĩa một subclass knifeStore mới mà không cung cấp phương thức này. Bởi vì budgetKnifeStore là một subclass của class knifeStore nên nó kế thừa phương thức orderKnife. Bạn có thể chạy phương thức orderKnife từ bất kỳ lớp con nào của knifeStore. Tất cả các lớp con của knifeStore: BudgetKnifeStore,QualityKnifeStore, FamilyKnifeStore .v.v đều kế thừa phương thức orderKnife. Tuy nhiên, mỗi lớp con lại xác định một phương thức tạo knife riêng của nó. Chúng ta hãy xem sơ đồ UML cho một knifeStore:


Sơ đồ lớp UML cung cấp cho bạn một số thông tin quan trọng. Trước hết, cả knife và knifeStore được in nghiêng để biểu thị các lớp trừu tượng. Bạn không thể khởi tạo các lớp này trực tiếp. Bạn phải xác định subclass của chúng, biểu đồ UML chỉ thể hiện subclass knife, nhưng có thể có nhiều hơn: BudgetChefKnife, BugetParingKnife, .v.v.  Chúng ta cũng có thể có nhiều hơn một knifeStore, có thể là qualityKnifeStore, familyKnifeStore, sẽ tạo ra các loại knife khác. Phương thức createKnife() cũng trừu tượng trong knifeStore(), điều này có ý nghĩa là bất kỳ lớp con nào của knifeStore() đều phải định nghĩa, xác định phương thức này. Vì vậy, BudgetKnifeStore phải có phương thức createKnife() xác định riêng của nó. Đây chính là cốt lõi (core) thiết kế của Factory Method Pattern.


The Factory Method Pattern luôn tuân theo một cấu trúc chung như sơ đồ UML trên. Chúng ta có một lớp Creator abstract, lớp này chứa các phương thức chỉ hoạt động dựa trên khát quát. Nói cách khác, miễn là chúng ta cung cấp cho các phương thức này các sản phẩm mà chúng muốn hoạt động, chúng sẽ rất vui. Trong ví dụ của chúng ta, product là dao (knife), creator cũng phải khai báo phương thức factoryMethod() một cách trừu tượng. Chúng ta gọi nó là Concrete Creator bởi vì nó chịu trách nhiệm cho việc Concrete Instantiatie, hành động thức tế của việc tạo ra các đối tượng, Concrete Creator cũng thừa kề các phương thức từ Abstract Creator. Mỗi khi nhà phát triển thêm một lớp con của Concrete Creator vào thiết kế, họ phải xác định một factoryMethod() (phương thức xuất xưởng) để tạo ra sản phẩm phù hợp, Đây chính là cách mà lớp con quyết định cách tạo đối tượng.

Và superclass product, là knife trong ví dụ của chúng ta, khái quát hóa các sản phẩm các loại. Theo như the creator class, các phưong thức của nó chỉ hoạt động dựa trên sản phẩm chung, không bao giờ là sản phẩm cụ thể. Loại sản phẩm được tạo ra quyết định bởi người được tạo ra, trong ví dụ của chúng ta, BudgetKnifeStore đã tạo ra BudgetChefKnife và BudgetSteakKnife, trong khi QualityKnifeStore có thể tạo ra QualityChefKnife hay QualitySteakKnife.

Trong bài viết này, chúng ta đã trình bày mô hình thiết kế Factory Method Pattern và Factory Object. Một nhà máy nói chung là một phương thức (method) hoặc đối tượng (object) cho phép bạn có thể tạo ra đối tượng khác, hãy nhớ rằng hành động tạo đối tượng, thông thường với từ khóa mới trong Java được gọi là Concrete Instantiate. Factory cho phép code client hoạt động dựa trên các khái quát. Điều này được gọi là mã hóa cho một interface (giao diện), không phải là một implement (triển khai). Miễn là code client nhận được đối tương mà nó mong đợi, một knife trong ví dụ của chúng ta, nó có thể đáp ứng trách nhiệm của nó mà không phải lo lắng về các chi tiết tạo đối tượng. Một Factory Object có thể là một công cụ hữu ích, nó là một đối tượng tạo ra đối tượng khác. Nó có thể hữu ích nếu nhiều phần của phần mềm muốn cùng tạo ra đối tượng. Bạn thậm chí có thể tạo ra các nhà máy chuyên dụng bằng cách có các lớp con của lớp nhà máy, các Factory Method Pattern có thể luôn có ột chút khác nhau. Thay vì sử dụng một đối tượng khác để tạo đối tượng, chúng ta tách việc tạo đối tượng thành một phương thức khác, là Factory Method. Bây giờ thay vì làm việc với object Factory, chúng ta chuyên, hoặc phân lớp sử dụng các phương thức nhà máy. Mỗi lớp con phải xác định phương thức nhà máy riêng của mình. Chúng ta nói rằng chúng ta đang để các lớp con quyết định cách đối tượng được tạo ra. Bằng cách tách biệt tạo đối tượng thực tế khỏi các hành vi sử dụng các nhà máy, code trở nên clean (trong sáng hơn) để đọc và dễ dàng hơn để bảo trì và thay đổi. Code client được đơn giản hóa và các chi tiết tạo đối tượng được chuyển vào các nhà máy. Code trở nên mở rộng hơn và khai thác sức mạnh của các nguyên tắc thiết kế hướng đối tượng.
Cảm ơn.



Nhận xét

Bài đăng phổ biến từ blog này

Hiểu về Norm Regularization

Những thuật toán nền tảng trong lĩnh vực Trí tuệ nhân tạo (Artificial Intelligence I)