Inversion of Control Containers and the Dependency Injection pattern

http://martinfowler.com/articles/injection.html

Bộ Container Inversion of Control (bộ điều khiển đảo ngược) và mô hình Dependency Injection (tiêm sự phụ thuộc)

Trong cộng đồng Java có một cơn sốt về các container nhỏ gọn giúp lắp ráp các thành phần từ các dự án khác nhau vào một ứng dụng thống nhất. Đằng sau những container này là một mô hình khá phổ biến là làm thế nào họ thực hiện các kết nối giữa các hệ thống, một khái niệm mà họ gọi dưới cái tên rất chung chung đó là “Inversion of Control”. Trong bài viết này, tôi đi sâu vào mô hình này xem chúng hoạt động thế nào dưới cái tên cụ thể hơn là “Dependency Injection”, và ngược lại nó với sự thay thế của Service Locator. Sự lựa chọn giữa chúng ít quan trọng hơn so với các nguyên tắc tách cấu hình của chúng ra khỏi Use.

Nội dung

 

Các thành phần và dịch vụ

Một vài ví dụ đơn giản

Inversion of Control

Các hình thức Dependency Injection

Constructor Injection với PicoContainer

Setter Injection với Spring

Giao diện Injection

Sử dụng Service Locator

Sử dụng một giao diện tách biệt cho Locator

Một Service Locator năng động

Sử dụng cả Locator và Injection với Avalon

Quyết định tùy chọn để sử dụng

So sánh Service Locator Dependency Injection

Constructor so với Setter Injection

Sử dụng Code hay các file cấu hình

Tách Cấu hình tinh chỉnh từ Use

Một số vấn đề khác

Kết luận

 

Một trong những điều thú vị về thế giới doanh nghiệp Java là số tiền rất lớn phải chi trả cho các hoạt động trong xây dựng thay thế cho công nghệ chủ đạo J2EE, điều này xảy ra khá nhiều ở mã nguồn mở. Trong số đó, có rất nhiều phản hồi về sự phức tạp nặng nề trong thế giới J2EE chính thống, nhưng có khá nhiều vấn đề cũng được khám phá giải pháp thay thế và mở đầu cho những ý tưởng sáng tạo. Một trong những vấn đề phổ biến để giải quyết là làm thế nào để kết nối các yếu tố khác nhau lại: làm thế nào để bạn có làm cho kiến ​​trúc điều khiển trang web này phù hợp với giao diện cơ sở dữ liệu khi nó được xây dựng bởi các đội lập trình khác nhau và không nắm được ý của nhau. Một số framework đã mắc phải vấn đề này, và một số được phân nhánh để cung cấp khả năng tổng hợp để lắp ráp các thành phần từ các lớp khác nhau. Chúng thường được gọi là các Container nhỏ gọn, ví dụ bao gồm PicoContainer, và Spring.

 

Đằng sau những Container là một số nguyên tắc thiết kế thú vị, những thứ đi xa hơn cả những container cụ thể này và thực sự là nền tảng của Java. Ở đây, tôi muốn bắt đầu khám phá một số nguyên tắc này. Các ví dụ tôi sử dụng là của Java, nhưng cũng giống như hầu hết các văn bản của tôi, các nguyên tắc đều như nhau đối với các môi trường OO khác, đặc biệt là .NET.

Các thành phần và dịch vụ

 

Chủ đề hệ thống kết nối của các yếu tố kéo tôi gần như ngay lập tức vào các vấn đề thuật ngữ có nhiều rắc rối xung quanh các điều khoản dịch vụ và thành phần của chúng. Bạn sẽ dễ dàng tìm thấy các bài viết dài và trái ngược nhau về định nghĩa của những điều này. Đối với mục đích của tôi, có quá nhiều điều kiện hiện tại tôi đang sử dụng.

 

Tôi sử dụng thành phần để định nghĩa một khối phần mềm dự định sẽ được sử dụng mà không có sự thay đổi, bởi một ứng dụng vượt khỏi sự kiểm soát của các tác giả của các thành phần đó. Từ ‘không thay đổi’ có nghĩa là các ứng dụng màtôi sử dụng không hề thay đổi mã nguồn của các thành phần, mặc dù họ có thể thay đổi hoạt động của các thành phần bằng cách mở rộng nó bằng nhiều cách cho phép bởi những nhà lập trình các thành phần đó.

 

Một dịch vụ tương tự như một thành phần trong đó được sử dụng bởi các ứng dụng nước ngoài. Sự khác biệt chính là việc tôi mong đợi thành phần này được sử dụng tại nội vùng (hãy nghĩ đến file jar, lắp ráp, dll, hoặc các nguồn nhập). Một dịch vụ sẽ được sử dụng từ xa thông qua một số giao diện từ xa, 1 trong 2 đồng bộ hoặc không đồng bộ (ví dụ như dịch vụ web, hệ thống tin nhắn, RPC, hoặc ổ cắm.)

 

Tôi chủ yếu sử dụng dịch vụ trong bài viết này, nhưng nhiều của logic tương tự cũng có thể được áp dụng cho các thành phần địa nội vùng. Thật vậy, bạn thường xuyên cần một số loại framework thành phần nội vùng để dễ dàng truy cập vào một dịch vụ từ xa. Nhưng cứ viết “thành phần hoặc dịch vụ” thì đọc và viết thật là mệt mỏi, và các dịch vụ thì lại rất phổ biến tại thời điểm hiện tại.

 

Một vài ví dụ đơn giản

 

Để giúp tất cả các thứ gắn kết lại với nhau hơn tôi sẽ chạy một ví dụ để nói lên tất cả những điều này. Giống như trong tất cả các ví dụ của tôi, đó là một trong những ví dụ siêu đơn giản; đủ nhỏ đến nổi có thể không có thật, nhưng hy vọng đủ để bạn có thể hình dung những gì đang xảy ra mà không rơi vào vũng lầy của một ví dụ thực tế hơn.

 

Trong ví dụ này tôi đang viết một thành phần cung cấp một danh sách các phim dưới sự biên đạo của một đạo diễn cụ thể. chức năng tuyệt hữu ích này được thực hiện bằng một phương pháp duy nhất.

 

class MovieLister…

 public Movie[] moviesDirectedBy(String arg) {

     List allMovies = finder.findAll();

     for (Iterator it = allMovies.iterator(); it.hasNext();) {

         Movie movie = (Movie) it.next();

         if (!movie.getDirector().equals(arg)) it.remove();

     }

     return (Movie[]) allMovies.toArray(new Movie[allMovies.size()]);

 }

 

Việc thực hiện chức năng này là cực kì đơn giản, nó yêu cầu tìm một đối tượng cụ thể (thứ mà chúng ta sẽ nói tới tiếp sau đây) để trả về kết quả mỗi phim mà nó biết. Sau đó, nó chỉ cần thông qua danh sách này để trả về kết quả những phim của đạo diễn chỉ đạo nó. Đây đoạn cụ thể đến đỗi hết sức dễ dàng và tôi sẽ không phải sửa chữa gì, vì nó chỉ là màn mở đầu cho điểm sáng thực sự của bài viết này.

Điểm thực sự của bài viết này là đối tượng finder cụ thể này, hoặc đại loại như thế nào để chúng ta kết nối các đối tượng lister với một đối tượng finder cụ thể. Lý do điều này trở nên thú vị là bởi vì tôi muốn phương pháp tuyệt vời moviesDirectedBy của tôi hoàn toàn độc lập với cách mà tất cả các bộ phim đang được lưu trữ. Vì vậy, tất cả các phương pháp này không được đề cập đến một finder, và tất cả những finder biết là làm thế nào để đáp ứng với các câu lệnh findAll. Tôi có thể mang điều này bằng cách định danh giao diện cho công cụ finder.

 

public interface MovieFinder {

   List findAll();

}

 

Bây giờ tất cả những điều này đã tách rời nhau, nhưng tại một số điểm tôi có thể gắn kết với một lớp cụ thể để thực sự hiện thị cùng với những bộ phim. Trong trường hợp này tôi đặt code cho điều này trong constructor của lớp lister của tôi.

 

class MovieLister…

 private MovieFinder finder;

 public MovieLister() {

   finder = new ColonDelimitedMovieFinder(“movies1.txt”);

 }

 

Tên của lớp thực hiện xuất phát từ thực tế là tôi nhận được danh sách từ một tập tin văn bản phân cách bởi dấu chấm. Tôi sẽ cho bạn thêm các chi tiết, sau khi chỉ ra tất cả các điểm thì có một số bài thực hành.

 

Bây giờ nếu tôi sử dụng lớp này cho bản thân mình, thì chả có chuyện gì xảy ra. Nhưng điều gì sẽ xảy ra khi bạn bè của tôi đang bị choáng ngợp bởi các thiêt kế tuyệt vời của những chức năng này và muốn copy chương trình của tôi? Nếu họ cũng lưu trữ danh sách những bộ phim của họ trong một tập tin văn bản phân định bởi dấu chấm được gọi là “movies1.txt” thì mọi thứ đều tuyệt vời. Nếu họ lấy một tên khác cho tập tin phim của họ, nhưng sẽ dễ dàng để đặt tên các tập tin đó trong một tập tin properties. Nhưng nếu họ có một hình thức lưu trữ danh sách phim hoàn toàn khác nhau: Cơ sở dữ liệu SQL, tập tin XML, dịch vụ web, hoặc chỉ là một định dạng tập tin văn bản? Trong trường hợp này, chúng ta cần một lớp khác để lấy dữ liệu đó. Bây giờ, vì tôi đã xác định một giao diện MovieFinder, điều này sẽ không làm thay đổi câu lệnh moviesDirectedBy. Nhưng tôi vẫn cần phải có một số cách để có được một ví dụ của việc thực hiện công cụ finder vào đúng vị trí.

naive

Hình 1: Các sự phụ thuộc sử dụng một cách sáng tạo đơn giản trong lớp lister

 

Hình 1 cho thấy sự phụ thuộc đối với tình trạng này. Lớp MovieLister phụ thuộc vào cả hai giao diện MovieFinder và sau khi thực hiện. Chúng tôi sẽ thích nó nếu nó chỉ phụ thuộc vào giao diện, nhưng sau đó làm thế nào để chúng ta có một ví dụ để làm việc?  

 

Trong cuốn sách P của EAA của tôi, chúng tôi mô tả tình trạng này như một Plugin. Các lớp thực hiện cho công cụ finder không được liên kết vào chương trình tại thời gian biên dịch, vì tôi không biết bạn bè của tôi đang sử dụng chúng vào việc gì. Thay vào đó, chúng tôi muốn lister của tôi có thể làm việc với bất kỳ thực nghiệm nào, và để thực hiện điều đó phải được cắm ở tại một số điểm sau đó, ngoài tầm kiểm soát của tôi. Vấn đề là làm thế nào tôi có thể làm các liên kết đó để lớp lister của tôi là bỏ qua các lớp thực hiện, nhưng vẫn có thể tác động với ví dụ để chúng làm công việc của mình.

 

Mở rộng này thành một hệ thống thực sự, chúng ta có thể có hàng chục dịch vụ và các thành phần như vậy. Trong mỗi trường hợp, chúng ta có thể lấy ra việc chúng ta sử dụng các thành phần này bằng cách giao tiếp với chúng thông qua một giao diện (và sử dụng một bộ chuyển đổi nếu các thành phần không được thiết kế với một giao diện ngầm). Nhưng nếu chúng ta muốn triển khai hệ thống này theo những cách khác nhau, chúng ta cần phải sử dụng các plugin để xử lý sự tương tác của các dịch vụ này nên chúng ta có thể sử dụng các lệnh thực thi khác nhau trong các triển khai khác nhau.

 

Vì vậy, vấn đề cốt lõi là làm thế nào để chúng tôi lắp ráp các plugin vào một ứng dụng? Đây là một trong những vấn đề chính mà các container nhỏ gọn còn non trẻ này phải đối mặt, và phổ quát tất cả họ làm điều đó bằng cách sử Inversion of Control.

 

Inversion of Control

 

Khi các container này nói về việc chúng hữu ích như thế nào thì bởi vì chúng có thể thực hiện “Inversion of Control” tôi rất bối rối. Inversion of Control là một đặc điểm chung của các framework, nên nói rằng các container nhỏ gọn đặc biệt bởi vì chúng có thể sử dụng Inversion of Control giống như nói rằng xe của tôi là đặc biệt bởi vì nó có bánh xe.

 

Câu hỏi đặt ra là: “những gì khía cạnh điều khiển nào được chúng đảo ngược?” Khi tôi lần đầu tiên chạy thử Inversion of Control, nó nằm trong bộ điều khiển chính của giao diện người dùng. giao diện người dùng ban đầu được điều khiển bởi các chương trình ứng dụng. Bạn sẽ có một chuỗi các lệnh như “Nhập tên”, “nhập địa chỉ”; chương trình của bạn sẽ dẫn bạn đến các nhắc nhở và chọn ra một phản hồi cho mỗi nhắc nhở. Với đồ họa (hoặc thậm chí dựa vào màn hình) UIS, UI framwork sẽ chứa chương trình của bạn và vòng lặp chính thay vì cung cấp xử lý dữ kiện cho các khu vực khác nhau trên màn hình. Việc điều khiển chính của chương trình đã được đảo ngược, di chuyển ra xa bạn và đến các framework.

 

Đối với loại container mới này, đảo chiều ở đây là nói về cách chúng tra cứu một plugin thực nghiệm. Trong ví dụ đơn giản của tôi về lister, chúng tra cứu và thực hiện công cụ finder bằng cách trực tiếp tạo ra đối tượng cho nó. Điều này làm ngừng công cụ finder từ plugin. Cách tiếp cận mà các container sử dụng là để đảm bảo rằng bất kỳ người nào sử dụng một plugin với một số quy ước cho phép lắp ráp module riêng để tiêm sự hoàn thiện vào lister.

 

Kết quả là tôi nghĩ chúng ta cần một cái tên cụ thể hơn cho mô hình này. Inversion of Control là một thuật ngữ quá chung chung, và do đó mọi người thấy khó hiểu. Kết quả sau khi có rất nhiều cuộc thảo luận với những người ủng hộ IoC khác nhau chúng tôi quyết định gọi chúng là Dependency Injection.

 

Tôi sẽ bắt đầu bằng việc nói về các hình thức của dependency injection, nhưng tôi sẽ chỉ ra ngay bây giờ đó không phải là cách duy nhất để loại bỏ sự phụ thuộc từ lớp ứng dụng để thực hiện plugin. Các mô hình khác bạn có thể sử dụng để làm điều này là Service Locator, và tôi sẽ thảo luận về điều này sau khi tôi giải thích xong Dependency Injection.

 

Các hình thức Dependency Injection

 

Ý tưởng cơ bản của Dependency Injection là có một đối tượng riêng biệt, một chương trình lắp ráp, mà nằm trong một lĩnh vực của lớp lister với việc thực hiện phù hợp với giao diện công cụ finder, kết quả trong một biểu đồ phụ thuộc theo mũi tên ở hình 2
injector

Hình 2: Các sự phụ thuộc cho một Dependancy Injector

 

Dependency injection có ba cách chính. Những cái tên tôi đang sử dụng cho chúng là Constructor Injection, Setter injection, và Interface Injection. Nếu bạn đọc về các công cụ này trong các cuộc thảo luận hiện nay về Inversion of Control bạn sẽ nghe thấy chúng gọi là loại IoC 1 (Interface Injection), loại IoC 2 (setter injection) và loại IoC 3 (constructor injection). Tôi thấy tên bằng số khá khó nhớ, đó là lý do tại sao tôi đã sử dụng tên tôi có ở đây.

 

Constructor Injection với PicoContainer

 

Tôi sẽ bắt đầu với cho các bạn xem cách tiêm này được thực hiện bằng một container nhỏ gọn gọi là PicoContainer. Tôi bắt đầu ở đây chủ yếu là do một số đồng nghiệp của tôi tại ThoughtWork rất tích cực trong việc phát triển PicoContainer (vâng, đó là một loại lạm dụng quyền hành của công ty.)

 

PicoContainer sử dụng một constructor để quyết định làm thế nào để thực hiện tiêm một công cụ finder vào lớp lister. Để làm việc này, lớp lister phim cần phải khai báo một constructor trong đó bao gồm tất cả mọi thứ nó cần tiêm vào.

class MovieLister…

 public MovieLister(MovieFinder finder) {

     this.finder = finder;       

 }

 

Các công cụ finder chính nó cũng sẽ được quản lý bởi picocontainer, và như vậy tên tập tin của văn bản sẽ được tiêm vào nó bởi container này.

class ColonMovieFinder…

 public ColonMovieFinder(String filename) {

     this.filename = filename;

 }

 

Các picocontainer sau đó cần phải được chỉ định lớp thực hiện nào để kết hợp với mỗi giao diện, và những chuỗi nào sẽ được tiêm vào công cụ finder.

 

private MutablePicoContainer configureContainer() {

   MutablePicoContainer pico = new DefaultPicoContainer();

   Parameter[] finderParams =  {new ConstantParameter(“movies1.txt”)};

   pico.registerComponentImplementation(MovieFinder.class, ColonMovieFinder.class, finderParams);

   pico.registerComponentImplementation(MovieLister.class);

   return pico;

}

 

Mã cấu hình này thường được thiết lập trong một lớp khác. Trong ví dụ của chúng tôi, mỗi người sử dụng lister của tôi có thể viết mã cấu hình thích hợp ở một số lớp thiết lập của riêng mình. Tất nhiên việc giữ các loại thông tin cấu hình trong tập tin cấu hình riêng biệt này khá phổ biến. Bạn có thể viết một lớp học để đọc một tập tin cấu hình và thiết lập các container thích hợp. Mặc dù PicoContainer không chứa chức năng này trong chính bản thân nó, có một dự án liên quan chặt chẽ gọi là NanoContainer cung cấp những gói thích hợp để cho phép bạn có các file cấu hình XML. một nanocontainer như vậy sẽ phân tích cú pháp XML và sau đó cấu hình một picocontainer bên dưới. Triết lý của dự án này là để tách các định dạng tập tin cấu hình từ các cơ chế cơ bản.

 

Để sử dụng container, bạn viết code như thế này:

 

public void testWithPico() {

   MutablePicoContainer pico = configureContainer();

   MovieLister lister = (MovieLister) pico.getComponentInstance(MovieLister.class);

   Movie[] movies = lister.moviesDirectedBy(“Sergio Leone”);

   assertEquals(“Once Upon a Time in the West”, movies[0].getTitle());

}

 

Mặc dù trong ví dụ này, tôi đã sử dụng constructor injection, PicoContainer cũng hỗ trợ setter injection, mặc dù các nhà phát triển của nó thích thực hiện bằng constructor injection hơn.

 

Setter Injection và Spring

 

Spring framework là một framework có tầm rất rộng để phát triển Java doanh nghiệp. Nó bao gồm các lớp trừu tượng cho các giao dịch, các framework kiên cố, phát triển ứng dụng web và JDBC. Giống như PicoContainer nó hỗ trợ cả constructor và setter injection, nhưng các nhà phát triển có xu hướng thích sử dụng chúng cho setter injection – thứ mà tạo một sự lựa chọn thích hợp để lấy nó làm ví dụ.

 

Để có được lister phim của tôi chấp nhận tiêm, tôi xác định một phương pháp thiết lập cho dịch vụ đó

class MovieLister…

 private MovieFinder finder;

public void setFinder(MovieFinder finder) {

 this.finder = finder;

}

 

Tương tự như vậy tôi xác định một setter cho tên tập tin.

class ColonMovieFinder…

 public void setFilename(String filename) {

     this.filename = filename;

 }

 

Bước thứ ba là để thiết lập cấu hình cho các tập tin. Spring hỗ trợ cấu hình thông qua các tập tin XML và cũng thông qua code, nhưng XML là cách dự kiến để nó thực hiện điều này.

 

<beans>

   <bean id=”MovieLister” class=”spring.MovieLister”>

       <property name=”finder”>

           <ref local=”MovieFinder”/>

       </property>

   </bean>

   <bean id=”MovieFinder” class=”spring.ColonMovieFinder”>

       <property name=”filename”>

           <value>movies1.txt</value>

       </property>

   </bean>

</beans>

 

Chương trình thử nghiệm sau đó trông như thế này.

 

public void testWithSpring() throws Exception {

   ApplicationContext ctx = new FileSystemXmlApplicationContext(“spring.xml”);

   MovieLister lister = (MovieLister) ctx.getBean(“MovieLister”);

   Movie[] movies = lister.moviesDirectedBy(“Sergio Leone”);

   assertEquals(“Once Upon a Time in the West”, movies[0].getTitle());

}

 

Interface Injection

 

Kỹ thuật tiêm thứ ba là xác định và sử dụng giao diện cho việc tiêm. Avalon là một ví dụ về một framework có sử dụng kỹ thuật này. Tôi sẽ nói một chút về điều này sau, nhưng trong trường hợp này tôi sẽ sử dụng nó với một số mẫu code đơn giản.

 

Với kỹ thuật này, tôi bắt đầu nó bằng cách định nghĩa một giao diện mà tôi sẽ sử dụng để thực hiện việc tiêm. Đây là giao diện cho việc tiêm công cụ movie finder vào một đối tượng.

 

public interface InjectFinder {

   void injectFinder(MovieFinder finder);

}

 

Giao diện này sẽ được xác định bởi bất cứ ai cung cấp giao diện MovieFinder. Nó cần phải được thực hiện bởi bất kỳ lớp nào muốn sử dụng công cụ finder, chẳng hạn như lister.

class MovieLister implements InjectFinder

 public void injectFinder(MovieFinder finder) {

     this.finder = finder;

 }

 

Tôi sử dụng một cách tiếp cận tương tự như trên để tiêm tên tập tin vào việc thực hiện công cụ finder.

 

public interface InjectFinderFilename {

   void injectFilename (String filename);

}

class ColonMovieFinder implements MovieFinder, InjectFinderFilename…

 public void injectFilename(String filename) {

     this.filename = filename;

 }

 

Sau đó, như thường lệ, tôi cần một số code cấu hình để kết nối đến việc thực hiện. Để đơn giản, tôi sẽ làm điều đó trong code.

class Tester…

 private Container container;

 

  private void configureContainer() {

    container = new Container();

    registerComponents();

    registerInjectors();

    container.start();

 }

Cấu hình này có hai giai đoạn, đăng ký các thành phần thông qua các phím tra cứu khá tương tự như các ví dụ khác.

class Tester…

 private void registerComponents() {

   container.registerComponent(“MovieLister”, MovieLister.class);

   container.registerComponent(“MovieFinder”, ColonMovieFinder.class);

 }

 

Một bước tiến mới là đăng ký các injector để tiêm vào các thành phần phụ thuộc. Mỗi giao diện tiêm cần một số mã để tiêm các đối tượng phụ thuộc. Ở đây tôi làm điều này bằng cách đăng ký đối tượng injector với các container. Mỗi đối tượng injector thực hiện giao diện injector.

class Tester…

 private void registerInjectors() {

   container.registerInjector(InjectFinder.class, container.lookup(“MovieFinder”));

   container.registerInjector(InjectFinderFilename.class, new FinderFilenameInjector());

 }

public interface Injector {

 public void inject(Object target);

 

}

 

Khi sự phụ thuộc là một lớp được viết cho container này, nó có ý nghĩa đối với các thành phần để thực hiện các giao diện injector cho chính nó, như tôi đã làm ở đây với các công cụ movie finder. Đối với các lớp chung chung, chẳng hạn như chuỗi, tôi sử dụng một lớp bên trong mã cấu hình.

class ColonMovieFinder implements Injector…

 public void inject(Object target) {

   ((InjectFinder) target).injectFinder(this);        

 }

class Tester…

 public static class FinderFilenameInjector implements Injector {

   public void inject(Object target) {

     ((InjectFinderFilename)target).injectFilename(“movies1.txt”);      

   }

   }

 

Các thử nghiệm sau đó đã sử dụng container.

class IfaceTester…

 public void testIface() {

   configureContainer();

   MovieLister lister = (MovieLister)container.lookup(“MovieLister”);

   Movie[] movies = lister.moviesDirectedBy(“Sergio Leone”);

   assertEquals(“Once Upon a Time in the West”, movies[0].getTitle());

 }

 

Container sử dụng khai báo các giao diện tiêm tìm ra các phụ thuộc và các injector để tiêm sự phụ thuộc được chính xác. (Việc thực hiện container cụ thể tôi đã làm ở đây không quan trọng đối với kỹ thuật này, và tôi sẽ không cho bạn xem nó bởi vì bạn sẽ phì cười.)

 

Sử dụng Service Locator

 

Lợi ích chính của một Dependency Injector là nó loại bỏ sự phụ thuộc mà lớp MovieLister có trên việc thực hiện MovieFinder cụ thể. Điều này cho phép tôi cung cấp các lister cho bạn bè và cho họ gắn vào một thực hiện phù hợp với môi trường của mình. Tiêm không phải là cách duy nhất để phá vỡ sự phụ thuộc này, một là sử dụng Service Locator.

 

Ý tưởng cơ bản đằng sau service locator là để có một đối tượng mà biết làm thế nào để có thể giữ được tất cả các dịch vụ mà một ứng dụng có thể cần. Vì vậy, service locator cho các ứng dụng này sẽ có một phương thức trả về một Moviefinder một khi cần thiết. Tất nhiên điều này chỉ thay đổi một chút gánh nặng, chúng ta vẫn cần phải làm cho locator vào được lister, dẫn đến sự phụ thuộc của hình 3
locator

Hình 3: Các phụ thuộc cho một Service Locator

Trong trường hợp này tôi sẽ sử dụng các Service Locator như một Registry của 1 người đơn lẻ. Sau đó, lister có thể sử dụng để có được finder khi nó được khởi tạo.

class MovieLister…

 MovieFinder finder = ServiceLocator.movieFinder();

class ServiceLocator…

 public static MovieFinder movieFinder() {

     return soleInstance.movieFinder;

 }

 private static ServiceLocator soleInstance;

 private MovieFinder movieFinder;

 

Cũng như với các phương pháp tiêm, chúng ta phải cấu hình các service locator. Ở đây tôi đang làm nó trong code, nhưng nó không khó để sử dụng một cơ chế mà nó có thể đọc dữ liệu thích hợp từ một tập tin cấu hình có sẵn.

class Tester…

 private void configure() {

     ServiceLocator.load(new ServiceLocator(new ColonMovieFinder(“movies1.txt”)));

 }

class ServiceLocator…

 public static void load(ServiceLocator arg) {

     soleInstance = arg;

 }

 

 public ServiceLocator(MovieFinder movieFinder) {

     this.movieFinder = movieFinder;

 }

 

Còn đây là code thử nghiệm

class Tester…

 public void testSimple() {

     configure();

     MovieLister lister = new MovieLister();

     Movie[] movies = lister.moviesDirectedBy(“Sergio Leone”);

     assertEquals(“Once Upon a Time in the West”, movies[0].getTitle());

 }

 

Tôi thường nghe những lời phàn nàn rằng sử dụng các service locator là không nên, vì chúng không thể kiểm chứng bởi vì bạn không thể thay thế các thực hiện cho chúng. Chắc chắn bạn có thể thiết kế nó 1 cách dở tệ và vướn phải khá nhiều rắc rối loại này, nhưng bạn cần phải làm việc đó. Trong trường hợp này service locator đơn giản chỉ là chỗ lưu giữ dữ liệu. Tôi có thể dễ dàng tạo ra các locator với việc triển khai thử nghiệm các service của tôi.

 

Đối với một bộ locator phức tạp hơn, tôi có thể phân lớp service locator và vượt qua lớp phụ vào một biến của lớp registry. Tôi có thể thay đổi các phương thức tĩnh để gọi một phương thức trường hợp hơn là truy cập vào các biến trường hợp 1 cách trực tiếp. Tôi có thể cung cấp locator của chủ đề cụ thể bằng cách sử dụng kho lưu trữ chủ đề cụ thể. Tất cả điều này có thể được thực hiện mà không cần thay đổi chủ thể của service locator.

 

Một cách để nghĩ về điều này là service locator là một đăng ký không phải là một singleton. Một singleton cung cấp một cách đơn giản để thực hiện một đăng ký, nhưng mà quyết định thực hiện có thể dễ dàng thay đổi.

 

Sử dụng một giao diện tách biệt cho Locator

Một trong những vấn đề với cách tiếp cận đơn giản trên là MovieLister phụ thuộc hoàn toàn vào các lớp service locator, mặc dù nó chỉ sử dụng một dịch vụ. Chúng ta có thể giảm điều này bằng cách sử dụng một giao diện vai trò. Bằng cách đó, thay vì sử dụng toàn bộ các giao diện service locator, lister có thể khai báo chỉ những giao diện nó cần.

 

Trong tình huống này, các nhà cung cấp lister cũng sẽ cung cấp một giao diện locator mà nó cần được lưu giữ của các finder.

 

public interface MovieFinderLocator {

   public MovieFinder movieFinder();

 

Locator sau đó cần phải thực hiện các giao diện này để cung cấp quyền truy cập đến một finder.

 

MovieFinderLocator locator = ServiceLocator.locator();

MovieFinder finder = locator.movieFinder();

public static ServiceLocator locator() {

    return soleInstance;

}

public MovieFinder movieFinder() {

    return movieFinder;

}

private static ServiceLocator soleInstance;

private MovieFinder movieFinder;

 

Bạn sẽ nhận thấy rằng kể từ khi chúng ta muốn sử dụng một giao diện, chúng ta không thể truy cập các service thông qua các phương pháp tĩnh nữa. Chúng ta phải sử dụng lớp để có được một trường hợp của locator và sau đó sử dụng nó để lấy những gì chúng ta cần.

 

Một Service Locator năng động

 

Ví dụ trên là phương pháp tĩnh, trong đó các lớp service locator có các phương thức cho mỗi dịch vụ mà bạn cần. Đây không phải là cách duy nhất để làm việc đó, bạn cũng có thể tạo một Service Locator năng động, cho phép bạn lưu trữ bất kỳ dịch vụ mà bạn cần vào nó và tạo sự lựa chọn của bạn trong runtime.

 

Trong trường hợp này, định vị dịch vụ sử dụng map thay vì field cho mỗi service, cung cấp các phương pháp chung để có tải các service.

class ServiceLocator…

 private static ServiceLocator soleInstance;

 public static void load(ServiceLocator arg) {

     soleInstance = arg;

 }

 private Map services = new HashMap();

 public static Object getService(String key){

     return soleInstance.services.get(key);

 }

 public void loadService (String key, Object service) {

     services.put(key, service);

 }

 

Cấu hình liên quan đến việc tải một service với một phím thích hợp.

class Tester…

 private void configure() {

     ServiceLocator locator = new ServiceLocator();

     locator.loadService(“MovieFinder”, new ColonMovieFinder(“movies1.txt”));

     ServiceLocator.load(locator);

 }

 

Tôi sử dụng service bằng cách sử dụng chuỗi phím đi kèm.

class MovieLister…

 MovieFinder finder = (MovieFinder) ServiceLocator.getService(“MovieFinder”);

 

Nhìn chung tôi không thích cách tiếp cận này. Mặc dù nó rất linh hoạt nhưng lại không được rõ ràng. Cách duy nhất làm thế nào để đạt đến được một service tôi có thể tìm thấy thông qua các phím chữ. Tôi thích phương pháp rõ ràng bởi vì nó dễ dàng để tìm thấy nơi chúng ở mà chỉ cần nhìn vào giao diện định nghĩa.

 

Sử dụng cả Locator và Injection với Avalon

 

Dependency injection và Service Locator không nhất thiết phải cùng có khái niệm độc quyền. Một ví dụ của việc sử dụng cả hai cùng nhau là Avalon framework. Avalon sử dụng service locator, nhưng lại sử dụng injection cho các thành phần nơi để tìm được đến các locator.

 

Berin Loritsch gửi cho tôi phiên bản ví dụ đơn giản này của tôi chạy bằng Avalon.

 

public class MyMovieLister implements MovieLister, Serviceable {

   private MovieFinder finder;

 

   public void service( ServiceManager manager ) throws ServiceException {

       finder = (MovieFinder)manager.lookup(“finder”);

   }

 

Các phương pháp service là một ví dụ về giao diện injection, cho phép các container để tiêm một service manager vào MovieLister của tôi. Service manager là một ví dụ về service locator. Trong ví dụ này, lister không lưu trữ các quản lý trong một lĩnh vực, thay vào đó nó ngay lập tức sử dụng nó để tra cứu finder, tương tự như trên kho lưu trữ.

 

Quyết định tùy chọn để sử dụng

 

Vì vậy, đến nay tôi đã tập trung vào việc giải thích làm thế nào tôi nhìn thấy những mô hình và các biến thể của chúng. Bây giờ tôi có thể bắt đầu nói về những ưu và khuyết điểm của chúng để giúp tìm hiểu xem sử sụng cái nào và khi nào.

 

So sánh Service Locator Dependency Injection

 

Sự lựa chọn cơ bản là giữa Service Locator và Dependency Injection. Điểm đầu tiên là cả hai thực hiện cung cấp cách tách rời các thứ cơ bản những thứ đã mất trong ví dụ đơn giản – trong cả hai trường hợp, code ứng dụng độc lập với việc thực hiện cụ thể của giao diện dịch vụ. Sự khác biệt quan trọng giữa hai mô hình là làm thế nào việc thực hiện được cung cấp cho các lớp ứng dụng. Với service locator các lớp ứng dụng yêu cầu nó một cách rõ ràng bằng một tin nhắn để đến locator. Với injection không có yêu cầu rõ ràng, dịch vụ xuất hiện trong lớp ứng dụng – vì thế chúng sẽ thực hiên Inversion of control.

 

Inversion of control là một tính năng phổ biến của các framework, nhưng nó thứ mà khi sử dụng sẽ đi kèm với một cái giá phải trả. Nó có xu hướng khó hiểu và vướn phải các vấn đề khi bạn đang cố gắng để gỡ lỗi. Vì vậy, trên tất cả các mặt, tôi thường tránh việc sử dụng nó, trừ khi tôi cần nó. Đây không phải nói rằng sử dụng nó là không tốt, chỉ là tôi nghĩ rằng nó cần phải chứng minh cho bản thân nhiều hơn về việc chúng có thể thay thế một cách hiệu quả.

 

Sự khác biệt chính là với một Locator Service, mọi người dùng một dịch vụ có một phụ thuộc vào locator. Locator có thể ẩn sự phụ thuộc vào hệ thống xử lý khác, nhưng bạn cần phải xem locator. Vì vậy, quyết định sử dụng giữa locator và injector dựa vào vấn đề của sự phụ thuộc.

 

Sử dụng Dependancy injection có thể giúp nhìn thấy dễ dàng sự phụ thuộc của thành phần. Với Dependancy injection bạn chỉ có thể xem xét các cơ chế injection, chẳng hạn như các constructor, và xem sự phụ thuộc của chúng. Với các service locator, bạn phải tìm kiếm các mã nguồn cho các lần gọi đến locator. Các IDEs hiện đại với các tính năng tìm tài liệu tham khảo làm điều này dễ dàng hơn, nhưng nó vẫn không dễ dàng như việc trực tiếp xem các constructor hoặc thiết lập các phương pháp.

 

Điều này phụ thuộc rất nhiều vào bản chất của người sử dụng dịch vụ. Nếu bạn đang xây dựng một ứng dụng với các lớp khác nhau mà sử dụng một service, sau đó là Dependency từ các lớp ứng dụng đến locator không phải là vấn đề nan giải. Trong ví dụ của tôi đưa ra một Movie Lister cho bạn bè của tôi, sau đó sử dụng service locator và chúng hoạt động khá tốt. Tất cả những gì họ cần làm là cấu hình locator để kết nối chính xác việc triển khai các service phù hợp, hoặc thông qua một số code cấu hình hoặc thông qua một tập tin cấu hình. Trong phần  này tôi không thấy các injector đảo ngược cũng như cung cấp bất cứ điều gì hấp dẫn cả.

 

Sẽ có sự khác biệt nếu lister là một thành phần mà tôi cung cấp cho một ứng dụng mà người khác đang viết. Trong trường hợp này tôi không biết nhiều về các API của service locator mà khách hàng của tôi đang sử dụng. Mỗi khách hàng có thể có những service locator không tương thích của riêng mình. Tôi có thể làm được một số điều này bằng cách sử dụng các giao diện tách biệt. Mỗi khách hàng có thể viết một bộ chuyển đổi phù hợp với giao diện của tôi cho bộ locator của họ, nhưng trong mọi trường hợp tôi vẫn cần phải xem locator trước để tra cứu giao diện cụ thể. Và một khi các bộ chuyển đổi xuất hiện thì việc kết nối trực tiếp đến một bộ locator sẽ gặp rắc rối.

 

Kể từ khi có một injector bạn không cần phải có một sự phụ thuộc giữa một thành phần và injector, các thành phần không thể có được các service khác từ các injector một khi nó đã được cấu hình.

 

Một lý do phổ biến để mọi người để thích hình thức Dependency Injection là vì nó làm cho việc thử nghiệm dễ dàng hơn. Vấn đề ở đây là để làm thử nghiệm, bạn cần phải có một service thực sự để dễ dàng thay thế việc triển khai bởi các stub hoặc mock. Tuy nhiên thực sự là không có sự khác biệt ở đây giữa Dependency Injection và Service locator: cả hai đều tuân theo stubbing. Tôi nghi ngờ sự nhận định này đến từ các dự án mà mọi người không nỗ lực để đảm bảo rằng service locator của họ có thể dễ dàng bị thay thế. Đây là nơi việc thử nghiệm liên tục sẽ giúp ích, nếu bạn không thể dễ dàng stub service để thử nghiệm, điều này thể hiện tồn tại vấn đề nghiêm trọng trong thiết kế của bạn.

 

Tất nhiên vấn đề thử nghiệm càng trầm trọng bởi các thành phần môi trường rất dễ xâm nhậm, chẳng hạn như Java EJB framework. Quan điểm của tôi là các loại framework nên giảm thiểu tác động của nó vào các code ứng dụng, và đặc biệt là không nên để những việc đó làm chậm chu kỳ chỉnh sửa-thực hiện. Sử dụng plugin để thay thế các thành phần nặng kí hiện nay giúp quá trình này rất nhiều, rất quan trọng cho các hoạt động như: Test Driven Development.

 

Vì vậy, vấn đề lớn nhất là những người đang viết code mà dự kiến ​​sẽ được sử dụng trong các ứng dụng bên ngoài sự kiểm soát của tác giả. Trong những trường hợp thậm chí chỉ là một giả định tối thiểu về một service locator thôi cũng là một vấn đề.

 

Constructor so với Setter Injection

 

Đối với sự kết hợp các service, bạn phải luôn luôn phải có một số quy ước để kết nối các thứ cùng nhau. Ưu điểm chính của injection là nó đòi hỏi các quy ước rất đơn giản – ít nhất là nó hỗ trợ các contructor và setter injection. Bạn không cần phải làm bất cứ điều gì kỳ lạ trong các thành phần của bạn và nó khá đơn giản cho một injector để có được cấu hình của tất cả mọi thứ.

 

Interface injection thì khó hơn vì bạn phải viết rất nhiều giao diện để tất cả mọi thứ được sắp xếp gọn gàng. Đối với một tập hợp nhỏ của giao diện theo yêu cầu của các container, chẳng hạn như trong cách tiếp cận của Avalon, điều này không phải là quá tệ. Nhưng điều đó tốn rất nhiều công sức để lắp ráp các thành phần và thiết lập sự phụ thuộc, đó là lý do tại sao các công ty container nhỏ gọn hiện nay thường sử dụng setter và constructor injection.

 

Sự lựa chọn giữa setter và constructor injection khá là thú vị vì nó phản ánh một vấn đề tổng quát hơn về lập trình hướng đối tượng – bạn nên điền vào field bằng constructor hay setters.

 

Mặc định các đối tượng chạy thời gian dài theo tôi là càng nhiều càng tốt, để tạo các đối tượng có giá trị tại thời điểm thiết lập. Lời khuyên này trở lại với Kent Beck Smalltalk, mô hình thực hành tốt nhất: Phương pháp Constructor và các Thông số cho phương pháp Constructor. Các contructor với các thông số cung cấp cho bạn một tuyên bố rõ ràng về những gì nó có, nghĩa là để tạo ra một đối tượng hợp lệ tại một nơi cụ thể rõ ràng. Nếu có nhiều cách để làm điều đó, hãy tạo nhiều contructor có các kết hợp khác nhau.

 

Một ưu điểm khác với constructor khởi tạo là nó cho phép bạn để ẩn dễ dàng bất kỳ field nào bất biến bằng cách không cung cấp setter. Tôi nghĩ rằng đây là điều quan trọng – nếu có cái gì đó không nên thay đổi dẫn đến là thiếu sự giao tiếp của một setter, đều này rất tốt. Nếu bạn sử dụng setters để khởi tạo, sau đó điều này có thể trở nên rắc rối. (Thực tế trong những tình huống này tôi thích tránh những quy ước thiết lập thông thường, tôi muốn một phương pháp giống như initFoo, nhấn mạnh rằng đó là một cái gì đó bạn chỉ nên làm khi khởi tạo chương trình.)

 

Nhưng với mọi tình huống đều có sự ngoại lệ. Nếu bạn có rất nhiều thông số contructor mọi thứ có thể nhìn rất lộn xộn, đặc biệt là trong các ngôn ngữ không từ khóa. Nó là sự thật, một contructor lâu dài thường là biểu hiện dấu hiệu của một đối tượng quá bận rộn nên được tách ra, nhưng có những trường hợp khi đó là những gì bạn muốn.

 

Nếu bạn có nhiều cách để xây dựng một đối tượng có giá trị khả dụng, nó có thể gây khó khăn để thể hiện điều này thông qua contructor, kể từ khi các contructor chỉ có thể khác nhau về số lượng và kiểu của các tham số. Đây là lúc phương pháp Factory được đưa vào sử dụng, chúng có thể sử dụng kết hợp của các contructor riêng lẻ và setters để thực hiện công việc của chúng. Vấn đề với việc sử dụng phương pháp Factory cổ điển để lắp ráp linh kiện là chúng thường được xem như là phương pháp tĩnh, và bạn không thể thấy chúng trên giao diện. Bạn có thể tạo 1 lớp factory, nhưng sau đó nó chỉ trở thành một ví dụ về service. Một factory serivce thường là một chiến thuật tốt, nhưng bạn vẫn phải nhanh chóng làm các factory sử dụng một trong những kỹ thuật ở đây.

 

Các contructor cũng bị ảnh hưởng nếu bạn có các thông số đơn giản như các chuỗi. Với setter injection bạn có thể cung cấp cho mỗi setter một cái tên để chỉ ra những chuỗi là phải làm. Với contructor bạn chỉ cần dựa trên vị trí, thứ mà rất khó để  có thể nắm được.

 

Nếu bạn có nhiều contructor và sự thừa kế, mọi thứ có thể trở nên đặc biệt khó hiểu. Để khởi tạo tất cả mọi thứ, bạn phải cung cấp contructor chuyển tiếp đến từng superclass contructor, trong khi đó cũng có thể thêm lập luận riêng của bạn vào. Điều này có thể dẫn đến một vụ nổ lớn hơn của các contructor.

 

Mặc dù có những nhược điểm nhưng sở thích của tôi bắt đầu với constructor injection, nhưng hãy sẵn sàng để chuyển sang setter injection ngay khi những vấn đề tôi đã nêu ở trên trở thành sự thật.

 

Vấn đề này đã dẫn đến nhiều cuộc tranh luận giữa các nhóm khác nhau, của người cung cấp Dependency injection như là một phần của các framework của họ. Tuy nhiên có vẻ như hầu hết những người xây dựng những framework đã nhận ra rằng điều quan trọng là hỗ trợ cả hai cơ chế, thậm chí nếu có người chỉ thích sử dụng một trong số chúng.

 

Sử dụng Code hay các file cấu hình

 

Một vấn đề riêng biệt nhưng thường được đem lồng ghép vào nhau là liệu sử dụng file cấu hình hay code trên một API để kết nối đến service. Đối với hầu hết các ứng dụng có khả năng được triển khai ở nhiều nơi, một tập tin cấu hình riêng biệt thường thích hợp nhất. Hầu như tất cả sẽ là một tập tin dạng XML, và điều này là hợp lý. Tuy nhiên có những trường hợp sẽ dễ dàng hơn nếu sử dụng code chương trình để thực hiện việc lắp ráp. Trường hợp là nơi bạn có một ứng dụng đơn giản mà không có nhiều biến triển khai. Trong trường hợp này một chút mã có thể sẽ rõ ràng hơn so với một tập tin XML riêng biệt.

 

Một trường hợp tương phản là nơi việc lắp ráp diễn ra khá phức tạp, liên quan đến các bước điều kiện. Một khi bạn bắt đầu hiểu rõ hơn về ngôn ngữ lập trình sau đó XML bắt đầu bị hư hỏng và tốt hơn là sử dụng một ngôn ngữ thực sự mà có tất cả các cú pháp để viết một chương trình rõ ràng. Sau đó bạn viết một lớp builder thực hiện lắp ráp. Nếu bạn có phần builder riêng biệt, bạn có thể cung cấp một số lớp builder và sử dụng một tập tin cấu hình đơn giản để có sự chọn lựa giữa chúng.

 

Tôi thường nghĩ rằng mọi người đang quá háo hức để xác định các tập tin cấu hình. Thường thì một ngôn ngữ lập trình làm cho một cơ chế cấu hình đơn giản và mạnh mẽ. Ngôn ngữ hiện đại có thể dễ dàng biên dịch lắp ráp cở nhỏ mà có thể được sử dụng để lắp ráp bổ sung cho một hệ thống lớn hơn. Nếu bạn cảm thấy khó để biên dịch , thì phần ngôn ngữ scripting cũng có thể làm tốt công việc này.

 

Mọi người thường nói rằng các tập tin cấu hình không nên sử dụng ngôn ngữ lập trình bởi vì chúng cần phải được chỉnh sửa bởi những người không phải là lập trình viên. Nhưng thường là tbao nhiêu lần? Có phải mọi người thực sự mong đợi vào những người không phải lập trình viên để thay đổi mức cô lập giao tiếp của một ứng dụng từ phía máy chủ phức tạp chăng? file cấu hình phi ngôn ngữ làm việc cũng chỉ trong phạm vi đơn giản. Nếu chúng trở nên phức tạp thì đây là lúc để suy nghĩ về cách sử dụng ngôn ngữ lập trình thích hợp.

 

Một điều chúng ta đang thấy trong thế giới Java tại thời điểm này là sự không đồng điệu của các tập tin cấu hình, nơi mà mỗi thành phần có các tập tin cấu hình riêng của mình, thứ mà rất khác nhau đối với từng ngườ. Nếu bạn sử dụng một tá các thành phần này, bạn có thể dễ dàng dẫn đến tình trạng một file chục cấu hình khác nhau để giữ cho chúng được đồng bộ.

 

Lời khuyên của tôi ở đây là để luôn luôn cung cấp cách để làm tất cả các cấu hình dễ dàng với giao diện chương trình, và sau đó xử lý một tập tin cấu hình riêng biệt như là một tính năng tùy chọn. Bạn có thể dễ dàng tạo file handling cấu hình để sử dụng cho giao diện chương trình. Nếu bạn đang viết một thành phần sau đó bạn nên để người dùng của bạn sử dụng nó cho các giao diện chương trình của họ khi họ mong muốn, định dạng tập tin cấu hình của bạn, hoặc để tự họ viết định dạng tập tin cấu hình tùy chỉnh của riêng họ và gắn nó vào giao diện chương trình.

 

Tách cấu hình tinh chỉnh từ quá trình sử dụng

 

vấn đề quan trọng nhất là đảm bảo rằng cấu hình của dịch vụ được tách ra từ quá trình sử dụng. Quả thực đây là một nguyên tắc thiết kế cơ bản mà luôn đi chung với việc tách các giao diện từ việc thực hiện. Đó là điều chúng tôi thấy bên trong một chương trình hướng đối tượng khi điều kiện logic quyết định lớp nào để thực hiện nhanh chóng, và đánh giá điều kiện này trong tương lai được thực hiện qua hình đa thức chứ không phải qua các code điều kiện bị nhân đôi.

 

Nếu việc tách ra này là có ích trong một cơ sở code duy nhất, điều đó đặc biệt quan trọng khi bạn đang sử dụng các yếu tố ngoài nước như các thành phần và dịch vụ. Câu hỏi đầu tiên là bạn có muốn trì hoãn việc lựa chọn lớp thực hiện để triển khai cụ thể. Nếu vậy bạn cần phải sử dụng một số thực hiện của plugin. Khi bạn đang sử dụng plugin thì điều cần thiết là việc lắp ráp các plugin được thực hiện riêng rẽ với các phần còn lại của ứng dụng để bạn có thể thay thế các cấu hình khác nhau một cách dễ dàng để triển khai khác nhau. Làm thế nào bạn đạt được điều này chỉ là thứ phụ. cơ chế cấu hình này, hoặc có thể cấu hình service locator, hoặc sử dụng injection để cấu hình các đối tượng một cách trục tiếp trực tiếp.

 

Một số vấn đề khác

 

trong mục này, tôi tập trung vào các vấn đề cơ bản của dịch vụ cấu hình sử dụng Dependency injection và Service locator. Có những chủ đề khác nói về điều này cũng rất đáng quan tâm, nhưng tôi không có đủ thời gian đề đi sâu vào nó. Cụ thể thì có vấn đề về hoạt động vòng lập. Một số thành phần có những sự kiện vòng lập riêng biệt: ngừng và bắt đầu cho một thực hiện. Một số vấn đề khác là sự gia tăng về việc quan tâm sử dụng những ý tưởng định hướng đặc tính với những container này. Dù tôi không lưu ý đến mục này của tài liệu. Tôi không mong muốn viết thêm về điều này hay mở rộng mục này hay viết 1 cái khác

 

Bạn có thể tìm thấy rất nhiều những ý tưởng này bằng việc nhìn vào website nói về các Lightweight container. Lướt từ các trang picocontainerspring sẽ dẫn bạn đến rất nhiều những cuộc thảo luận của vấn đề này và bắt đầu một số vấn đề xa hơn

 

Kết luận

 

Các điểm quan trọng hiện hành của Lightweight container là đều có chung 1 một mô hình cơ bản phổ biến về cách chúng cùng nhau kết hợp dịch vụ – mô hình dependency injector. Dependency Injection là 1 sự điều chỉnh hiệu quả với Service Locator. Khi xây dựng những lớp ứng dụng, thì chúng tương đương nhau, nhưng tôi nghĩ Service Locator có phần nhỉnh hơn nhờ vào hành vi đơn giản của nó. Tuy nhiên nếu bạn xây dựng những lớp đa dụng thì Dependency Injection là cần thiết.

 

Nếu bạn sử dụng ” Dependency Injection ” thì có 1 số kiểu để chọn. Tôi gợi ý bạn nên đi theo Constructor injection nếu như bạn không gặp phải 1 số vấn đề với việc tiếp cận đó, nếu gặp phải vấn đề hãy chuyển qua việc sử dụng setter injection. Nếu bạn chọn xây dựng hoặc tạo 1 container, hãy tìm sự trợ giúp từ cả construtor và setter injection.

 

Sự lựa chọn giữa Service Locator và Dependency Injection là ít quan trọng hơn nguyên tác của việc phân chia cấu hình dịch vụ từ việc sử dụng dịch vụ trong ứng dụng.

 

_________________________________________________________

 

Lời cảm ơn

 

Tôi chân thành cảm ơn đến những người đã giúp đỡ tối trong bài viết này. Rod Johnson, Paul Hammant, Joe Walnes, Aslak Hellesøy, Jon Tirsén và Bill Caputo đã giúp đỡ tôi hiểu được những khái niệm này và góp ý kiến về những dữ thảo ban đầu của bài viết này. Berin Loritsch và Hamilton Verissimo de Oliveira đã cung cấp 1 số lời khuyên hữu ích vào việc làm thế nào làm cho Avalon được tích hợp. Dave W Smith kiên nhẫn hỏi tôi những câu hỏi về việc khởi tạo giao diện câu lệnh cấu hình việc tiêm, do đó đã khiến tôi thấy rằng nó thật ngu ngốc. Gerry Lowry đã gửi cho tôi rất nhiều bản sửa lỗi chính tả – đủ để tôi nói lời cảm ơn

 

Những sửa đổi đáng kể

 

23 tháng 1 năm 2004: Chỉnh sửa lại câu lệnh cấu hình của ví dụ về interface injection

16 tháng 1 năm 2004: Đã thêm 1 ví dụ nhỏ của cả Locator và injection với Avalon

14 tháng 1 năm 2004: công bố bản chính thức đầu tiên

 

© Martin Fowler

 

Advertisements