2012-07-07 12 views
6

Tôi đang đọc cuốn sách java hiệu quả của Bloch [1] và đã xem qua ví dụ sau của SPI:cung cấp dịch vụ giao diện mà không cung cấp

//Service interface 
public interface Service { 
    //Service specific methods here 
} 

//Service provider interface 
public interface Provider { 
    Service newService(); 
} 

//Class for service registration and access 
public class Services { 
    private Services(){} 

    private static final Map<String, Provider> providers = 
    new ConcurrentHashMap<String, Provider>(); 
    public static final String DEFAULT_PROVIDER_NAME = "<def>"; 

    //Registration 
    public static void registerDefaultProvider(Provider p) { 
    registerProvider(DEFAULT_PROVIDER_NAME, p); 
    } 
    public static void registerProvider(String name, Provider p) { 
    providers.put(name, p); 
    } 

    //Access 
    public static Service newInstance() { 
    return newInstance(DEFAULT_PROVIDER_NAME); 
    } 
    public static Service newInstance(String name) { 
    // you get the point..lookup in the map the provider by name 
    // and return provider.newService(); 
    } 

câu hỏi của tôi này: tại sao là giao diện nhà cung cấp cần thiết? Chúng tôi không thể dễ dàng đăng ký Dịch vụ - ví dụ: duy trì bản đồ triển khai Dịch vụ và sau đó trả về bản sao khi tra cứu? Tại sao thêm lớp trừu tượng?

Có lẽ ví dụ này quá chung chung - bất kỳ ví dụ "tốt hơn" nào để minh họa cho điểm cũng sẽ tuyệt vời.


[1]Second edition, Chương 2. Các ấn bản đầu tiên ví dụ không đề cập đến các giao diện cung cấp dịch vụ.

+0

Tôi cũng muốn biết mục đích và tiện ích của nhà cung cấp. – danidacar

Trả lời

1

Nếu bạn có thể cần nhiều dịch vụ cho từng loại, bạn không thể sử dụng lại các Dịch vụ cũ. (Ngoài ra, kiểm tra và những thứ tương tự có thể muốn tạo các dịch vụ mới cho mỗi bài kiểm tra, chứ không phải là tái sử dụng dịch vụ mà có thể đã bị sửa đổi hoặc cập nhật bởi các xét nghiệm trước đó.)

0

Vì vậy, có vẻ như bạn có thể có nhiều Provider s cho cùng Service và dựa trên tên Nhà cung cấp cụ thể, bạn có thể nhận được các trường hợp khác nhau của cùng một Dịch vụ. Vì vậy, tôi sẽ nói mỗi nhà cung cấp là loại giống như nhà máy tạo ra dịch vụ một cách thích hợp.

Ví dụ giả sử class PaymentService implements Service và yêu cầu Gateway. Bạn có PayPal và cổng Chase xử lý các bộ xử lý thanh toán đó. Bây giờ bạn tạo một PayPalProvider và ChaseProvider, mỗi cái đều biết cách tạo đúng cá thể PaymentService với cổng bên phải.

Nhưng tôi đồng ý, có vẻ như bị giả mạo.

1

Tôi nghĩ câu trả lời được đề cập trong Effective Java cùng với ví dụ.

Một tùy chọn thành phần thứ tư của một khuôn khổ cung cấp dịch vụ là một giao diện nhà cung cấp dịch vụ , mà các nhà cung cấp thực hiện để tạo ra trường hợp thực hiện dịch vụ của họ. Trong trường hợp không có dịch vụ giao diện nhà cung cấp, việc triển khai được đăng ký theo tên lớp và được phản ánh ngay lập tức (Mục 53).

Trong trường hợp của JDBC,
Connection đóng phần của giao diện dịch vụ,
DriverManager.registerDriver là việc đăng ký API cung cấp, DriverManager.getConnectiondịch vụ truy cập API
Driver là giao diện nhà cung cấp dịch vụ .

Vì vậy, như bạn đã lưu ý chính xác, không nhất thiết phải có giao diện Nhà cung cấp mà chỉ là một cách tiếp cận rõ ràng hơn.

+0

Nhưng mục đích của nhà cung cấp là gì và làm cách nào bạn có thể sử dụng "API đăng ký nhà cung cấp" nếu bạn không có nhà cung cấp. – danidacar

-1

cung cấp dịch vụ giao diện mà không có một nhà cung cấp

Chúng ta hãy xem làm thế nào nó sẽ trông như thế mà không có một nhà cung cấp.

//Service interface 
public interface Service { 
    //Service specific methods here 
} 

//Class for service registration and access 
public class Services { 
    private Services(){} 

    private static final Map<String, Service> services = 
    new ConcurrentHashMap<String, Service>(); 
    public static final String DEFAULT_SERVICE_NAME = "<def>"; 

    //Registration 
    public static void registerDefaultService(Provider p) { 
    registerService(DEFAULT_SERVICE_NAME, p); 
    } 
    public static void registerService(String name, Provider p) { 
    services.put(name, p); 
    } 

    //Access 
    public static Service getInstance() { 
    return newInstance(DEFAULT_SERVICE_NAME); 
    } 
    public static Service getInstance(String name) { 
    // you get the point..lookup in the map the service by name 
    // and return it; 
    } 

Như bạn thấy, nó có thể tạo ra một giao diện cung cấp dịch vụ mà không có một giao diện Provider. Người gọi số #getInstance(..) cuối cùng sẽ không nhận thấy sự khác biệt.

Sau đó, tại sao chúng tôi cần nhà cung cấp?

Giao diện Provider là một Abstract FactoryServices#newInstance(String) là một Factory Method. Cả hai mẫu thiết kế đều có lợi thế là họ tách dịch vụ triển khai dịch vụ khỏi đăng ký dịch vụ.

Single responsibility principle

Thay vì thực hiện các instantiation dịch vụ trong một xử lý sự kiện khởi động, trong đó đăng ký tất cả các dịch vụ, bạn tạo một nhà cung cấp cho mỗi dịch vụ. Điều này làm cho nó được kết hợp lỏng lẻo và dễ dàng hơn để tái cấu trúc, bởi vì Nhà cung cấp Dịch vụ và Dịch vụ có thể được đặt gần nhau, ví dụ vào một tệp JAR khác.

"Phương pháp của nhà máy là phổ biến trong các bộ công cụ và khung công tác, nơi mã thư viện cần tạo các đối tượng thuộc loại có thể được phân lớp bởi các ứng dụng sử dụng khung công tác". [1]

Lifetime quản lý:

Bạn có thể đã nhận ra trong các mã trên mà không cần các nhà cung cấp, mà chúng ta đang đăng ký dịch vụ trường thay vì một nhà cung cấp, trong đó có thể quyết định nhanh chóng một trường hợp dịch vụ mới.

Cách tiếp cận này có một số nhược điểm:

1. trường hợp dịch vụ phải được tạo ra trước khi cuộc gọi dịch vụ đầu tiên. Khởi tạo lười biếng là không thể. Điều này sẽ trì hoãn việc khởi động và liên kết các tài nguyên với các dịch vụ hiếm khi được sử dụng hoặc thậm chí không bao giờ.

1b. Bạn "không thể" đóng các dịch vụ sau khi sử dụng, bởi vì không có cách nào để phục hồi chúng. (Với một nhà cung cấp bạn có thể thiết kế giao diện dịch vụ trong một cách mà người gọi phải gọi #close(), mà thông báo cho nhà cung cấp và các nhà cung cấp quyết định giữ lại hoặc hoàn thiện các trường hợp dịch vụ.)

2. Tất cả những người gọi sẽ sử dụng cùng một cá thể dịch vụ, do đó bạn phải đảm bảo rằng nó an toàn cho luồng. Nhưng làm cho nó an toàn thread sẽ làm cho nó chậm. Ngược lại, một nhà cung cấp có thể chọn tạo một vài trường hợp dịch vụ để giảm thời gian lưu trữ.

Kết luận

Một giao diện nhà cung cấp là không cần thiết, nhưng nó đóng gói dịch vụ cụ thể Logic instantiation và tối ưu hóa phân bổ nguồn lực.

2

Tại sao giao diện Nhà cung cấp cần thiết? Chúng tôi không thể dễ dàng đăng ký Dịch vụ - ví dụ:duy trì bản đồ triển khai Dịch vụ và sau đó trả về bản sao khi tra cứu?

Như những người khác đã nêu, mục đích của Nhà cung cấp là có một AbstractFactory có thể tạo ra các trường hợp Service. Bạn không phải lúc nào cũng muốn giữ tham chiếu đến tất cả các triển khai Dịch vụ vì chúng có thể tồn tại trong thời gian ngắn và/hoặc có thể không sử dụng được sau khi chúng đã được thực thi.

Nhưng mục đích của nhà cung cấp là gì và làm thế nào bạn có thể sử dụng một "đăng ký API cung cấp" nếu bạn không có một nhà cung cấp

Một trong những lý do mạnh mẽ nhất để có một giao diện cung cấp rất bạn KHÔNG cần phải thực hiện tại thời gian biên dịch. Người dùng API của bạn có thể thêm triển khai của riêng họ sau này.

Hãy sử dụng JDBC là một ví dụ như Ajay được sử dụng trong câu trả lời khác nhưng chúng ta hãy cho nó một chút nữa:

Có rất nhiều loại khác nhau của cơ sở dữ liệu và các nhà cung cấp cơ sở dữ liệu người đều có những cách hơi khác nhau trong việc quản lý và thực hiện các cơ sở dữ liệu (và có lẽ cách truy vấn chúng). Những người sáng tạo Java không thể tạo các triển khai của tất cả các cách có thể khác nhau này vì nhiều lý do:

  • Khi Java được viết lần đầu, nhiều công ty hoặc hệ thống cơ sở dữ liệu này chưa tồn tại.
  • Không phải tất cả các nhà cung cấp cơ sở dữ liệu này đều là nguồn mở nên những người tạo Java không thể biết cách giao tiếp với họ ngay cả khi họ muốn.
  • Người dùng có thể muốn viết cơ sở dữ liệu tùy chỉnh riêng của họ

Vậy làm thế nào để bạn giải quyết này? Bằng cách sử dụng Service Provider.

  • Giao diện DriverProvider. Nó cung cấp các phương thức để tương tác với cơ sở dữ liệu của một nhà cung cấp cụ thể. Một trong những phương thức trong Driver là phương pháp nhà máy để tạo một cá thể Connection (là Service) vào cơ sở dữ liệu được cung cấp một url và các thuộc tính khác (như tên người dùng và mật khẩu, v.v.).

Mỗi nhà cung cấp cơ sở dữ liệu tự thực hiện Driver cách liên lạc với hệ thống cơ sở dữ liệu của riêng họ. Chúng không được bao gồm trong JDK; bạn phải vào trang web của công ty hoặc một số kho lưu trữ mã khác và tải chúng xuống dưới dạng một cái bình riêng biệt.

Để sử dụng các trình điều khiển này, bạn phải thêm bình vào đường dẫn lớp và sau đó sử dụng lớp JDK DriverManager để đăng ký trình điều khiển.

Lớp DriverManager có phương thức registerDriver(Driver) được sử dụng để đăng ký phiên bản Trình điều khiển trong Đăng ký dịch vụ để có thể sử dụng. Theo quy ước, hầu hết Driver triển khai đăng ký vào thời điểm lớp tải vì vậy tất cả bạn phải làm trong mã của bạn là viết

Class.forname("foo.bar.Driver"); 

Để đăng ký trình điều khiển cho nhà cung cấp "foo.bar"(giả sử bạn có jar với lớp học trong classpath của bạn.)

Khi Drivers Cơ sở dữ liệu đã được đăng ký, bạn có thể nhận được một trường hợp thực hiện dịch vụ được kết nối với cơ sở dữ liệu của bạn.

Ví dụ, nếu bạn đã có một cơ sở dữ liệu mysql trên máy tính địa phương của bạn có tên là "test" và bạn đã có một tài khoản người dùng với tên người dùng "monty" và password "greatsqldb", sau đó bạn có thể tạo một cài đặt dịch vụ như thế này:

Connection conn = 
    DriverManager.getConnection("jdbc:mysql://localhost/test?" + 
           "user=monty&password=greatsqldb"); 

lớp DriverManager thấy Chuỗi bạn đã truyền vào và tìm trình điều khiển đã đăng ký có thể hiểu điều đó có nghĩa là gì. sử dụng Mẫu Chain of Responsibility bằng cách xem qua tất cả Trình điều khiển đã đăng ký và gọi phương thức Driver.acceptsUrl(Stirng) của chúng tôi cho đến khi url được chấp nhận)

Lưu ý rằng không có mã cụ thể mysql trong JDK. Tất cả những gì bạn phải làm là đăng ký Trình điều khiển của một số nhà cung cấp và sau đó chuyển một Chuỗi được định dạng đúng cho Nhà cung cấp Dịch vụ. Nếu sau đó chúng ta quyết định sử dụng một nhà cung cấp cơ sở dữ liệu khác (như oracle hoặc sybase) thì chúng ta chỉ cần trao đổi các lọ và sửa đổi chuỗi kết nối của chúng ta. Mã trong DriverManager không thay đổi.

Tại sao chúng ta không tạo kết nối một lần và duy trì nó? Tại sao chúng ta cần Dịch vụ?

Chúng tôi có thể muốn kết nối/ngắt kết nối sau mỗi thao tác. Hoặc chúng tôi có thể muốn giữ kết nối lâu hơn. Việc Dịch vụ cho phép chúng tôi tạo các kết nối mới bất cứ khi nào chúng tôi muốn và không loại trừ chúng tôi khỏi việc tham chiếu đến nó để sử dụng lại sau này.

Đây là một khái niệm rất mạnh mẽ và được sử dụng bởi các khuôn khổ để cho phép nhiều hoán vị và phần mở rộng có thể mà không làm lộn xộn lõi mã.

EDIT

Làm việc với nhiều nhà cung cấp và các nhà cung cấp cung cấp nhiều Services:

Có gì ngăn cản bạn từ việc có nhiều nhà cung cấp là. Bạn có thể kết nối với nhiều cơ sở dữ liệu được tạo bằng cách sử dụng phần mềm nhà cung cấp cơ sở dữ liệu khác nhau cùng một lúc. Bạn cũng có thể kết nối với nhiều cơ sở dữ liệu được sản xuất bởi cùng một nhà cung cấp cùng một lúc.

Nhiều dịch vụ - Một số Nhà cung cấp thậm chí có thể cung cấp các triển khai Service khác nhau tùy thuộc vào url kết nối. Ví dụ, H2 có thể tạo cả hai cơ sở dữ liệu dựa trên hệ thống hoặc trong dựa trên memeory cơ sở dữ liệu. Cách để nói với H2 mà một trong những bạn muốn sử dụng là một định dạng url khác nhau. Tôi đã không nhìn vào mã H2, nhưng tôi giả định các tập tin dựa trên và trong bộ nhớ dựa là triển khai dịch vụ khác nhau.

Tại sao DriverManager không quản lý kết nối và Oracle có thể triển khai OracleConnectionWrapper? Không có nhà cung cấp!

Điều đó cũng sẽ yêu cầu bạn biết rằng bạn có kết nối Oracle. Đó là khớp nối rất chặt chẽ và tôi sẽ phải thay đổi rất nhiều mã nếu tôi thay đổi nhà cung cấp.

Service Registration chỉ cần một chuỗi. Hãy nhớ rằng nó sử dụng chain of Responsiblity để tìm Nhà cung cấp đã đăng ký đầu tiên biết cách xử lý url. ứng dụng có thể là nhà cung cấp trung lập và nhận được url kết nối và tên lớp Trình điều khiển từ một tệp thuộc tính. Bằng cách đó tôi không phải biên dịch lại mã của mình nếu tôi thay đổi nhà cung cấp.Tuy nhiên, nếu tôi hardcoded refernences để "OracleConnectionWrapper" và sau đó tôi thay đổi các nhà cung cấp, tôi sẽ phải viết lại các phần của mã của tôi và sau đó biên dịch lại.

Không có gì ngăn người khác hỗ trợ nhiều định dạng url của nhà cung cấp cơ sở dữ liệu nếu họ muốn. Vì vậy, tôi có thể làm cho một GenericDriver có thể xử lý mysql và oracle nếu tôi muốn.

+1

Tôi thích chi tiết của bạn nhưng vẫn có một số thứ không phải là Bloch đã trình bày chúng. DRIVER là một PROVIDER. Kết nối là SERVICE. Và mọi thứ bạn nói về nhà cung cấp vẫn hoạt động tương tự nếu bạn chỉ có một giao diện dịch vụ: không cần thực hiện cụ thể. – danidacar

+0

Bạn đúng là Người lái xe là Nhà cung cấp, không phải là Dịch vụ; Tôi sửa lỗi đánh máy của mình. Tôi không chắc chắn tôi hiểu những gì bạn đang nói mặc dù về việc thực hiện cụ thể. Bạn cần triển khai tại một số thời điểm để thực hiện bất kỳ loại công việc nào. – dkatzel

+1

Bạn nói "lý do mạnh mẽ nhất để có giao diện Nhà cung cấp là vì vậy bạn KHÔNG cần phải thực hiện tại thời gian biên dịch". Nhưng hãy tưởng tượng chúng tôi không có một nhà cung cấp và chúng tôi chỉ có dịch vụ. Khái niệm này vẫn áp dụng. – danidacar

0

Khi tổng hợp các câu trả lời khác (thành phần thứ tư là lý do văn bản), tôi nghĩ điều này là để hạn chế phụ thuộc biên dịch. Với SPI, bạn có tất cả những công cụ để loại trừ en tham khảo rõ ràng để thực hiện:

  • Các META-INF/dịch vụ/ thư mục chứa các tập tin nhắc đến các dịch vụ có sẵn nhà cung cấp triển khai
  • Các ServiceLoader lớp tiêu chuẩn cho phép độ phân giải của các tên triển khai có sẵn và theo cách xây dựng động [1].

SPI không được đề cập trong ấn bản đầu tiên. Nó có lẽ không phải là nơi thích hợp để đưa nó vào một mục về các nhà máy tĩnh. DriverManager được đề cập trong văn bản là một gợi ý, nhưng Bloch không đi sâu. Trong một cách, nền tảng thực hiện một loại ServiceLocator pattern để giảm phụ thuộc biên dịch, tùy thuộc vào môi trường. Với một SPI trong nhà máy trừu tượng của bạn, nó trở thành ServiceFactory của ServiceLocator với sự trợ giúp của ServiceLoader cho mô đun.

Có thể sử dụng ServiceLoader iterator để điền động dịch vụ bản đồ ví dụ.


[1] Trong một môi trường OSGi, đây là một subtle operation.