Factory method

Factory method, đầy đủ là Factory method pattern, là thiết kế mẫu hướng đối tượng trong việc thiết kế phần mềm cho máy tính, nhằm giải quyết vấn đề tạo một đối tượng mà không cần thiết chỉ ra một cách chính xác lớp nào sẽ được tạo. Factory method giải quyết vấn đề này bằng cách định nghĩa một phương thức cho việc tạo đối tượng, và các lớp con thừa kế có thể override để chỉ rõ đối tượng nào sẽ được tạo. Nói chung, "factory method" thường được áp dụng cho những phương thức mà nhiệm vụ chính của nó là tạo ra đối tượng.

 Định nghĩa 
Bản chất của mẫu thiết kế Factory là "Định nghĩa một giao diện (interface) cho việc tạo một đối tượng, nhưng để các lớp con quyết định lớp nào sẽ được tạo. "Factory method" giao việc khởi tạo một đối tượng cụ thể cho lớp con."

 Áp dụng 
 "Factory method" thường được dùng trong bộ phát triển (toolkit) hay framework, ở đó, đoạn mã của framework cần thiết phải tạo một đối tượng là những lớp con của ứng dụng sử dụng framework đó.

 Những lợi ích khác và biến thể 
Cho dù động cơ của mẫu thiết kế "Factory method" là cho phép lớp con được chọn lựa kiểu đối tượng nào sẽ được tạo, việc sử dụng factory method cũng có những lợi ích khác. Bởi thế, việc thường xuyên sử dụng "factory method" không chỉ cho việc tạo đối tượng một cách đa dạng mà còn lợi dụng những lợi ích khác. Những hàm như thế thì thường là tĩnh.

 Tóm lược 
Factory method gói gọn lại việc tạo đối tượng. Điều này hữu dụng nếu quá trình tạo phức tạp. Ví dụ như nó phụ thuộc vào những điều chỉnh trong tập tin cấu hình hay phụ thuộc vào thông tin của người dùng nhập vào.

Ví dụ một chương trình đọc tập tin ảnh và tạo ảnh thumbnail của nó.
Chương trình hỗ trợ nhiều định dạng ảnh khác nhau, và mỗi định dạng ảnh sẽ có một lớp hỗ trợ việc đọc tập tin.
public interface ImageReader {
     public DecodedImage getDecodedImage();
}

public class GifReader implements ImageReader {

     public GifReader(InputStream in) {
         // check that it's a gif, throw exception if it's not, then if it is
         // decode it.
     }

     public DecodedImage getDecodedImage() {
        return decodedImage;
     }
}

public class JpegReader implements ImageReader {
     //....
}
Mỗi khi chương trình đọc một ảnh, nó cần phải tạo một đối tượng phù hợp để đọc ảnh đó dựa vào những thông tin trong tập tin. Việc này có thể gói gọn trong factory method:
public class ImageReaderFactory {

    public static ImageReader getImageReader(InputStream is) {

        int imageType = figureOutImageType(is);

        switch(imageType){
            case ImageReaderFactory.GIF:
                return new GifReader(is);
            case ImageReaderFactory.JPEG:
                return new JpegReader(is);
            // etc.
        }
    }
}

Đoạn mã trong ví dụ bên trên sử dụng lệnh switch để chỉ định kết hợp một imageType với một đối tượng factory cụ thể. Ngoài ra, có thể thay thế câu lệnh switch bằng cách sử dụng một mảng.

 Giới hạn 
Có 3 giới hạn đối với việc sử dụng factory method. Một liên quan đến việc sửa đổi (tạm dịch từ refactoring) mã nguồn hiện tại; Hai vấn đề khác liên quan đến thừa kế.

 Giới hạn đầu tiên là việc sửa đổi (refactoring) một lớp đã có sẵn để sử dụng factory sẽ làm hỏng ứng dụng. Ví dụ, nếu lớp Complex là một lớp chuẩn, có thể tồn tại nhiều đoạn mã sử dụng lớp này như sau:

Complex c = new Complex(-1, 0);

Một khi chúng ta thấy cần thiết phải có hai factory khác nhau, chúng ta thay đổi lớp này. Tuy nhiên, vì hàm khởi tạo (constructor) bây giờ trở thành private, đoạn mã này sẽ không được dịch nữa.

 Giới hạn thứ hai là do mẫu thiết kế này dựa vào việc sử dụng hàm khởi tạo private, lớp này sẽ không thể được thừa kế và mở rộng. Các lớp con phải thừa kế hàm khởi tạo của lớp cha, nhưng việc này không thực hiện được vì hàm khởi tạo là private.
 Giới hạn thứ ba là, nếu chúng ta mở rộng một lớp (ví dụ: tạo một hàm khởi tạo protected -- điều này nguy hiểm nhưng chấp nhận được), lớp con phải cung cấp lại tất cả các hàm factory với tất cả thông số phải chính xác (bao gồm tên hàm và tham số). Ví dụ, nếu lớp StrangeComplex thừa kế lớp Complex, trừ khi StrangeComplex cung cấp tất cả các hàm factory, nếu không việc gọi

Những vấn đề trên có thể giải quyết bằng cách sử dụng ngôn ngữ lập trình (thay vì sử dụng mẫu thiết kế).

 Ví dụ 
 C# 
Pizza example:
public abstract class Pizza
{
    public abstract decimal GetPrice();

    public enum PizzaType
    {
        HamMushroom, Deluxe, Seafood
    }
    public static Pizza PizzaFactory(PizzaType pizzaType)
    {
        switch (pizzaType)
        {
            case PizzaType.HamMushroom:
                return new HamAndMushroomPizza();

            case PizzaType.Deluxe:
                return new DeluxePizza();

            case PizzaType.Seafood:
                return new SeafoodPizza();

        }

        throw new System.NotSupportedException("The pizza type " + pizzaType.ToString() + " is not recognized.");
    }
}
public class HamAndMushroomPizza: Pizza
{
    private decimal price = 8.5M;
    public override decimal GetPrice() { return price; }
}

public class DeluxePizza: Pizza
{
    private decimal price = 10.5M;
    public override decimal GetPrice() { return price; }
}

public class SeafoodPizza: Pizza
{
    private decimal price = 11.5M;
    public override decimal GetPrice() { return price; }
}

// Somewhere in the code
...
  Console.WriteLine(Pizza.PizzaFactory(Pizza.PizzaType.Seafood).GetPrice().ToString("C2")); // $11.50
...

 JavaScript 
Pizza example:
//Our pizzas
function HamAndMushroomPizza(){
  var price = 8.50;
  this.getPrice = function(){
    return price;
  }
}

function DeluxePizza(){
  var price = 10.50;
  this.getPrice = function(){
    return price;
  }
}

function SeafoodPizza(){
  var price = 11.50;
  this.getPrice = function(){
    return price;
  }
}

//Pizza Factory
function PizzaFactory(){
  this.createPizza = function(type){
     switch(type){
      case "Ham and Mushroom":
        return new HamAndMushroomPizza();
      case "DeluxePizza":
        return new DeluxePizza();
      case "Seafood Pizza":
        return new SeafoodPizza();
      default:
          return new DeluxePizza();
     }     
  }
}

//Sử dụng
var pizzaPrice = new PizzaFactory().createPizza("Ham and Mushroom").getPrice();
alert(pizzaPrice);

 Sử dụng 
 ADO.NET, IDbCommand.CreateParameter là ví dụ của việc dùng factory method để kết nối những lớp song song.
 Qt, QMainWindow::createPopupMenu is a factory method declared in a framework which can be overridden in application code.
 In Java (programming language) several factories are used in the javax.xml.parsers  package. e.g. javax.xml.parsers.DocumentBuilderFactory or javax.xml.parsers.SAXParserFactory.

 Xem thêm 
 Design Patterns, the highly-influential book
 Abstract Factory, a pattern often implemented using factory methods
 Builder pattern, another creational pattern
 Template method pattern, which may call factory methods
 Factory object

 Tham khảo 





 Các tham khảo khác 
 Factory method in UML and in LePUS3  (a Design Description Language)
 Description from the Portland Pattern Repository
 Jt  J2EE Pattern Oriented Framework

Thể loại:Mẫu thiết kế phần mềm
Thể loại:Phương thức (lập trình máy tính)