2.Abstract Factory

  • Mô-đun hóa là một vấn đề lớn trong lập trình ngày hôm nay. Lập trình viên trên toàn thế giới đang cố gắng tránh những ý tưởng về việc thêm mã vào các lớp đã được xây dựng để làm cho chúng đóng gói hỗ trợ thêm thông tin chung. Lấy ví dụ về trường hợp của một người quản lý thông tin, ở đây là quản lý các số điện thoại. Số điện thoại có một quy tắc cụ thể mà chúng được tạo ra tùy thuộc vào các vùng miền và quốc gia. Nếu tại một số thời điểm, ứng dụng nhận yêu cầu thay đổi để hỗ trợ thêm mẫu số điện thoại mới của một quốc gia mới, các mã của ứng dụng sẽ phải được thay đổi và lúc đó, mọi việc sẽ càng trở nên phức tạp hơn.
  • Để giải quyết vấn đề này, mẫu thiết kế Abstract Factory được sử dụng. Sử dụng mẫu thiết kế này, nó sản sinh ra các đối tượng tuân theo một mẫu chung và tại thời điểm runtime, factory này sẽ kết hợp với bất kỳ factory cụ thể nào để sản sinh ra các đối tượng đúng với mẫu của một quốc gia nhất định. Nói cách khác, Abstract Factory là super-factory để sản xuất ra các factory khác(Nhà máy của các nhà máy – Factory of factories).

Mục đích

Abstract Factory cung cấp giao diện để tạo ra một họ(family) các đối tượng có liên quan với nhau, mà không cần phải chỉ rõ các lớp một cách tường minh.

Thực thi

Về cơ bản, Abstract Factory hoạt động như mô hình dưới đây:

Các lớp tham gia vào mẫu Abstract Factory này là:

  • AbstractFactory – là một lớp abstract hay một interface cho các hoạt động tạo ra các sản phẩm abstract.
  • ConcreteFactory – implement các hoạt động để tạo ra các sản phẩm cụ thể.
  • AbstractProduct – là một lớp abstract hay một interface mô tả các hành xử chung của tài nguyên được sử dụng bởi ứng dụng.
  • Product – định nghĩa một sản phẩm được tạo ra cho phù hợp với ConcreteFactory; nó implement abstract hay interface AbstractProduct.
  • Client – sử dụng các interface khai báo bởi các lớp AbstractFactory and AbstractProduct

Lớp AbstractFactory là lớp xác định kiểu đối tượng cụ thể trong thực tế và tạo ra nó, nhưng nó trả về một abstract trỏ đến đối tượng cụ thể vừa tạo. Điều này sẽ xác định hành vi của client mà yêu cầu các factory để tạo ra một đối tượng của một loại trừu tượng nhất định và trả về abstract trỏ đến nó.

Ví dụ

Các mã sau đây cho thấy các địa chỉ quốc tế và số điện thoại có thể được hỗ trợ trong hệ thống quản lý thông tin cá nhân sử dụng mô hình Abstract Factory. Giao diện AddressFactory đại diện cho factory chính nó.

Example 1.1 AddressFactory.java

1.    public interface AddressFactory {

2.        public Address createAddress();

3.        public PhoneNumber createPhoneNumber();

4.    }

Lưu ý rằng AddressFactory định nghĩa hai phương thức factory, createAddress() và createPhoneNumber(). Các phương thức này sản xuất các sản phẩm trừu tượng Address và PhoneNumber, trong đó định nghĩa các phương thức mà các sản phẩm này hỗ trợ.

Example 1.2 Address.java

1. public abstract class Address {

2.     private String street;

3.     private String city;

4.     private String region;

5.     private String postalCode;

6.

7.     public static final String EOL_STRING =

8.            System.getProperty(“line.separator”);

9.     public static final String SPACE = ” “;

10.

11.    public String getStreet() {

return street;

}

12.    public String getCity() {

return city;

}

13.    public String getPostalCode() {

return postalCode;

}

14.    public String getRegion() {

return region;

}

15.    public abstract String getCountry();

16.

17.    public String getFullAddress() {

18.        return street + EOL_STRING +

19.             city + SPACE + postalCode + EOL_STRING;

20.    }

21.

22.    public void setStreet(String newStreet) {

street = newStreet;

}

23.    public void setCity(String newCity) {

city = newCity;

}

24.    public void setRegion(String newRegion) {

region = newRegion;

}

25.    public void setPostalCode(String newPostalCode) {

postalCode = newPostalCode;

}

26. }

Example 1.3 PhoneNumber.java

1. public abstract class PhoneNumber{

2.     private String phoneNumber;

3.     public abstract String getCountryCode();

4.

5.     public String getPhoneNumber() {

return phoneNumber;

}

6.

7.     public void setPhoneNumber(String newNumber) {

8.         try {

9.             Long.parseLong(newNumber);

10.            phoneNumber = newNumber;

11.        }

12.        catch (NumberFormatException exc) {

13.        }

14.    }

15. }

Trong ví dụ này, Address và PhoneNumber là các lớp ảo, nhưng nó cũng dễ dàng được định nghĩa như là interface nếu bạn ko cần định nghĩa code được sử dụng cho các sản phẩm cụ thể.

Để cung cấp chức năng cụ thể cho hệ thống, bạn cần phải tạo các lớp ConcreteFactory và ConcreteProduct. Trong trường hợp này, bạn định nghĩa một lớp implement AddressFactory, và tạo ra các lớp con của Address và PhoneNumber. Trong ví dụ này, cả 3 lớp này biểu diễn thông tin địa chỉ của U.S:

Example 1.4 USAddressFactory.java

1.    public class USAddressFactory implements AddressFactory{

2.        public Address createAddress(){

3.            return new USAddress();

4.        }

5.

6.        public PhoneNumber createPhoneNumber(){

7.            return new USPhoneNumber();

8.        }   16

9.    }

Example 1.5 USAddress.java

1.    public class USAddress extends Address{

2.        private static final String COUNTRY = “UNITED STATES”;

3.        private static final String COMMA = “,”;

4.

5.        public String getCountry(){ return COUNTRY; }

6.

7.        public String getFullAddress(){

8.            return getStreet() + EOL_STRING +

9.                getCity() + COMMA + SPACE + getRegion() +

10.               SPACE + getPostalCode() + EOL_STRING +

11.               COUNTRY + EOL_STRING;

12.       }

13.   }

Example 1.6 USPhoneNumber.java

1.    public class USPhoneNumber extends PhoneNumber{

2.        private static final String COUNTRY_CODE = “01”;

3.        private static final int NUMBER_LENGTH = 10;

4.

5.        public String getCountryCode(){ return COUNTRY_CODE; }

6.

7.        public void setPhoneNumber(String newNumber){

8.            if (newNumber.length() == NUMBER_LENGTH){

9.                super.setPhoneNumber(newNumber);

10.           }

11.       }

12.   }

Với framework chung từ AddressFactory, Address và PhoneNumber làm cho nó dễ dàng được mở rộng hệ thống khi có yêu cần bổ xung một quốc gia mới. Khi có yêu cầu bổ xung một quốc gia mới, bạn chỉ cần tạo ra một lớp ConcreteFactory và một lớp ConcreteProduct. Trong ví dụ này, giả sử bạn muốn thêm vào quản lý thông tin của quốc gia Pháp, bạn thực hiện như sau:

Example 1.7 FrenchAddressFactory.java

1.    public class FrenchAddressFactory implements AddressFactory{

2.        public Address createAddress(){

3.            return new FrenchAddress();

4.        }

5.

6.        public PhoneNumber createPhoneNumber(){

7.            return new FrenchPhoneNumber();

8.        }

9.    }

Example 1.8 FrenchAddress.java

1.    public class FrenchAddress extends Address{

2.        private static final String COUNTRY = “FRANCE”;

3.

4.        public String getCountry(){ return COUNTRY; }

5.

6.        public String getFullAddress(){

7.            return getStreet() + EOL_STRING +

8.                getPostalCode() + SPACE + getCity() +

9.                EOL_STRING + COUNTRY + EOL_STRING;

10.       }

11.   }

Example 1.9 FrenchPhoneNumber.java

1.    public class FrenchPhoneNumber extends PhoneNumber{

2.        private static final String COUNTRY_CODE = “33”;

3.        private static final int NUMBER_LENGTH = 9;

4.

5.        public String getCountryCode(){ return COUNTRY_CODE; }

6.

7.        public void setPhoneNumber(String newNumber){

8.            if (newNumber.length() == NUMBER_LENGTH){

9.                super.setPhoneNumber(newNumber);

10.           }

11.       }

12.   }

Lợi ích và hạn chế

  • Abstract Factory làm tăng tính linh hoạt chung của một ứng dụng. Sự linh hoạt này thể hiện trong thời gian thiết kế và thời gian chạy. Trong quá trình thiết kế, bạn không có để dự đoán những vấn đề sẽ phát sinh trong tương lai, và thay vào đó, bạn tạo một framework chung và sau đó phát triển thực thi một cách độc lập với phần còn lại của ứng dụng. Khi chạy, các ứng dụng có thể dễ dàng tích hợp các tính năng mới và tài nguyên.
  • Để nhận ra lợi ích của pattern này, bạn nên thận trọng trong việc định nghĩa một giao diện chung phù hợp cho abstract product. Nếu abstract product không đúng quy định thì sẽ sản sinh ra các concrete product không theo ý muốn.

Các biến thể  của Abstract Factory

  • Như đã đề cập ở trên, bạn có thể định nghĩa AbstractFactory và AbstractProduct hoặc là abstract class hay là một interface, tùy thuộc vào nhu cầu ứng dụng và sở thích của bạn.
  • Tùy thuộc vào cách mà factory được sử dụng, có một vài biến thể của pattern này cho phép tạo ra nhiều đối tượng ConcreteFactory, và kết quả là có thể sử dụng nhiều đồng thời nhiều họ ConcreteProduct.

Các pattern liên quan

  • Factory Method: sử dụng để implement AbstractFactory.
  • Singleton: sử dụng trong ConcreteFactory
  • DataAccess Object: sử đụng AbstractFactory để làm tăng tính linh động trong việc tạo ra các factory Database.

Bạn thấy bài viết này thế nào ?

Các bài viết liên quan:
Design Patterns trong Java – Phần 1
Design Patterns trong Java – Phần 2