12

Tôi đã đọc thường xuyên rằng Service Locators in IOC are an anti-pattern.Tránh dịch vụ Định vị mẫu Antipattern với ứng dụng cũ không được thiết kế cho IOC

Năm ngoái, chúng tôi đã giới thiệu IOC (đặc biệt là Ninject) cho ứng dụng của chúng tôi tại nơi làm việc. Các ứng dụng là di sản, nó rất lớn và nó bị phân mảnh. Có rất nhiều cách một lớp, hoặc một chuỗi các lớp học có thể được tạo ra. Một số được tạo ra bởi các khuôn khổ web (đó là tùy chỉnh), một số được tạo ra bởi nHibernate. Rất nhiều chỉ nằm rải rác xung quanh ở những nơi kỳ lạ.

Làm cách nào để xử lý các trường hợp khác nhau và không đưa ra điều gì đó ít nhất ServiceLocatorish và không kết thúc với các hạt nhân khác nhau ở các vị trí khác nhau (phạm vi như singleton, HttpRequest và chuỗi là quan trọng).

Chỉnh sửa Tôi sẽ thêm chi tiết hơn một chút vào những gì đã dẫn chúng tôi đến mẫu SL hiện tại của chúng tôi.

Chúng tôi thực sự không muốn nhiều hạt nhân. Chúng tôi chỉ muốn một (và thực sự vì SL chúng tôi chỉ có một cái tĩnh). Đây là thiết lập của chúng tôi:

1) Chúng tôi có Mô-đun Ninject trong 7-8 dự án/hội đồng khác nhau. Khi ứng dụng của chúng tôi (một webapp) bắt đầu các mô-đun được thu thập thông qua quá trình quét lắp ráp và được nạp vào hạt nhân và được đặt trong Bộ định vị dịch vụ. Vì vậy, tất cả những gì là khá tốn kém.

2) Chúng tôi có khung giao diện người dùng tùy chỉnh xây dựng hạnh phúc. Hãy suy nghĩ về khoảng 120 hình thức tab mà mỗi xây dựng 1-10 trang tab như là một phần của vòng đời của họ. SL được sử dụng chiến lược ở 5-6 nơi bao gồm tất cả điều này hoặc là độ phân giải thuần túy hoặc chỉ thực hiện việc trích xuất bài đăng ngay lập tức.

3) Mọi thứ trong giao diện người dùng không được bao hàm bởi các cuộc gọi cấp cao nhất đó và nếu các lớp đó muốn sử dụng IOC, chúng cần đưa ra chiến lược riêng của chúng. Có nhiều khuôn khổ nhỏ khác nhau, mỗi thế giới nhỏ bé của riêng họ.

Vì vậy, cách lý tưởng để làm điều đó từ những gì tôi đã đọc là tiêm hạt nhân bất cứ khi nào bạn cần truy cập IOC ... thì đó là tất cả tốt và tốt; chúng tôi giữ việc sử dụng SL ở mức tối thiểu.

Nhưng tôi lấy hạt nhân này từ đâu? Tôi không muốn xây dựng và đăng ký một cái mới ở khắp mọi nơi. Có vẻ như tôi sẽ phải đưa nó ra khỏi bối cảnh tĩnh hoặc nhà máy để tôi có thể đảm bảo rằng hạt nhân tôi đang sử dụng đang giữ các đối tượng có phạm vi tương tự mà mọi người khác đang sử dụng và cũng để tránh chi phí đăng ký tất cả mô-đun. Tại thời điểm đó, bất cứ điều gì có vẻ như rất giống một Service Locator phải không?

Xin lưu ý rằng ứng dụng này là HUGE và được kết hợp chặt chẽ. Chúng tôi không có sự sang trọng chi tiêu một vài tháng trong một lần tái cấu trúc nó. SL dường như là một cách tốt để chúng tôi từ từ làm việc IOC ở nơi chúng tôi có thể khi chúng tôi có thời gian.

+2

Với mức độ chi tiết bạn đã đưa ra, tất cả những gì tôi có thể nói là đọc các câu trả lời hàng đầu của @Mark Seeman và sau đó quay lại và cung cấp một số chi tiết có liên quan về lý do tại sao bạn cảm thấy cần nhiều hạt nhân giai đoạn trong quá trình tái cấu trúc) mà bạn có nhiều Root Roots.Ngay bây giờ câu hỏi là quá mở cho bất cứ ai để trả lời và cảm thấy họ đang thêm một cái gì đó đáng giá mà chưa được bao gồm trong các bài viết nói trên –

+0

thêm một số chi tiết – ryber

Trả lời

12

Vì vậy, cách lý tưởng để làm việc đó từ những gì Tôi đã đọc được tiêm một hạt nhân bất cứ khi nào bạn cần truy cập IOC ... cũng đó là tất cả tốt và tốt; chúng tôi giữ việc sử dụng SL ở mức tối thiểu.

Không, việc đưa hạt nhân vào các lớp học kinh doanh của bạn không phải là cách tốt nhất để thực hiện. Cách tốt hơn là tạo nhà máy, ví dụ: IFooFactory { IFoo Create(); } hoặc Func<IFoo> và để điều này tạo ra phiên bản mới.

Việc triển khai giao diện này đi vào gốc ghép, lấy một cá thể của hạt nhân và thực hiện giải quyết bằng cách sử dụng hạt nhân. Điều này giúp cho các lớp của bạn không có một vùng chứa cụ thể và bạn có thể tái sử dụng chúng trong một dự án khác bằng cách sử dụng một vùng chứa khác. Trong trường hợp của Func bạn có thể sử dụng mô-đun sau: Does Ninject support Func (auto generated factory)? Ninject 2.4 sẽ có hỗ trợ gốc cho việc này.


Theo như việc tái cấu trúc, bạn khó có thể cho bạn biết cách tốt nhất để đi mà không biết mã nguồn của ứng dụng của bạn là gì. Tôi chỉ có thể cung cấp cho bạn một approch rằng có lẽ có thể làm việc.

Tôi giả sử bạn muốn cấu trúc lại toàn bộ ứng dụng thành DI thích hợp trong thời gian dài. Những gì tôi đã làm một lần cho một dự án khá lớn (30-40 năm-người) là về những điều sau đây:

Bắt đầu tại gốc ghép và làm việc xuống cây đối tượng và thay đổi một lớp DI. Một khi bạn đạt đến tất cả các lá bắt đầu refactor tất cả các dịch vụ mà không phụ thuộc vào các dịch vụ khác và làm việc với lá của họ bằng cách sử dụng cùng một cách tiếp cận. Sau đó, tiếp tục với các dịch vụ chỉ phụ thuộc vào các dịch vụ đã được tái cấu trúc và lặp lại cho đến khi tất cả các dịch vụ được tái cấu trúc. Tất cả các bước này có thể được thực hiện sau khi các bước khác để mã liên tục được cải thiện trong khi các tính năng mới vẫn có thể được thêm cùng một lúc. Trong thời gian trung bình ServiceLocation là chấp nhận được, miễn là trọng tâm là làm cho nó đúng càng sớm càng tốt.

Pseudo mã ví dụ:

Foo{ ServiceLocator.Get<Service1>(), new Bar() } 
Bar{ ServiceLocator.Get<IService1>(), ServiceLocator.Get<IService2>(), new Baz() } 
Baz{ ServiceLocator.Get<IService3>() } 
Service1 { ServiceLocator.Get<Service3>() } 
Service2 { ServiceLocator.Get<Service3>() } 
Service3 { new SomeClass()} 

Bước 1 - Thay đổi Root (Foo)

Foo{ ctor(IService1, IBar) } 
Bar{ ServiceLocator.Get<IService1>(), ServiceLocator.Get<IService2>(), new Baz() } 
Baz{ ServiceLocator.Get<IService3>() } 
Service1 { ServiceLocator.Get<IService2>() } 
Service2 { ServiceLocator.Get<IService3>() } 
Service3 { new SomeClass()} 

Bind<IBar>().To<Bar>(); 
Bind<IService1>().ToMethod(ctx => ServiceLocator.Get<IService1>()); 

Bước 2 - Thay đổi phụ thuộc của rễ

Foo{ ctor(IService1, IBar) } 
Bar{ ctor(IService1, IService2, IBaz) } 
Baz{ ServiceLocator.Get<IService3>() } 
Service1 { ServiceLocator.Get<Service2>() } 
Service2 { ServiceLocator.Get<Service3>() } 
Service3 { new SomeClass()} 

Bind<IBar>().To<Bar>(); 
Bind<IBaz>().To<Baz>(); 
Bind<IService1>().ToMethod(ctx => ServiceLocator.Get<IService1>()); 
Bind<IService2>().ToMethod(ctx => ServiceLocator.Get<IService2>()); 

Bước 3 - Thay đổi phụ thuộc của họ và tiếp tục cho đến khi bạn đang ở trên lá

Foo{ ctor(IService1, IBar) } 
Bar{ ctor(IService1, IService2, IBaz) } 
Baz{ ctor(IService3) } 
Service1 { ServiceLocator.Get<Service2>() } 
Service2 { ServiceLocator.Get<Service3>() } 
Service3 { new SomeClass() } 

Bind<IBar>().To<Bar>(); 
Bind<IBaz>().To<Baz>(); 
Bind<IService1>().ToMethod(ctx => ServiceLocator.Get<IService1>()); 
Bind<IService2>().ToMethod(ctx => ServiceLocator.Get<IService2>()); 
Bind<IService3>().ToMethod(ctx => ServiceLocator.Get<IService3>()); 

Bước 4 - Refactor các dịch vụ mà không phụ thuộc vào những người khác

Foo{ ctor(IService1, IBar) } 
Bar{ ctor(IService1, IService2, IBaz) } 
Baz{ ctor(IService3) } 
Service1 { ServiceLocator.Get<Service2>() } 
Service2 { ServiceLocator.Get<Service3>() } 
Service3 { ctor(ISomeClass) } 

Bind<IBar>().To<Bar>(); 
Bind<IBaz>().To<Baz>(); 
Bind<ISomeClass>().To<SomeClass>(); 
Bind<IService1>().ToMethod(ctx => ServiceLocator.Get<IService1>()); 
Bind<IService2>().ToMethod(ctx => ServiceLocator.Get<IService2>()); 
Bind<IService3>().To<Service3>().InSingletonScope(); 

Bước 5 - Refactor Tiếp theo những người phụ thuộc vào dịch vụ mà chỉ có dịch vụ refactored như phụ thuộc.

Foo{ ctor(IService1, IBar) } 
Bar{ ctor(IService1, IService2, IBaz) } 
Baz{ ctor(IService3) } 
Service1 { ServiceLocator.Get<Service2>() } 
Service2 { ctor(IService3) } 
Service3 { ctor(ISomeClass) } 

Bind<IBar>().To<Bar>(); 
Bind<IBaz>().To<Baz>(); 
Bind<ISomeClass>().To<SomeClass>(); 
Bind<IService1>().ToMethod(ctx => ServiceLocator.Get<IService1>()); 
Bind<IService2>().To<Service2>().InSingletonScope(); 
Bind<IService3>().To<Service3>().InSingletonScope(); 

Bước 6 - Lặp lại cho đến khi mọi dịch vụ được tái cấu trúc.

Foo{ ctor(IService1, IBar) } 
Bar{ ctor(IService1, IService2, IBaz) } 
Baz{ ctor(IService3) } 
Service1 { ctor(IService2) } 
Service2 { ctor(IService3) } 
Service3 { ctor(ISomeClass) } 

Bind<IBar>().To<Bar>(); 
Bind<IBaz>().To<Baz>(); 
Bind<ISomeClass>().To<SomeClass>(); 
Bind<IService1>().To<Service1>().InSingletonScope(); 
Bind<IService2>().To<Service2>().InSingletonScope(); 
Bind<IService3>().To<Service3>().InSingletonScope(); 

Có thể bạn muốn chuyển sang cấu hình vùng chứa dựa trên quy ước cùng với việc tái cấu trúc. Trong trường hợp này, tôi sẽ thêm một thuộc tính cho tất cả các lớp đã được cấu trúc lại để đánh dấu chúng và loại bỏ nó một lần nữa sau khi tất cả việc tái cấu trúc được thực hiện. Trong các quy ước, bạn có thể sử dụng thuộc tính này để lọc tất cả các lớp được cấu trúc lại.

+0

Cảm ơn, thực sự điều này không xa những gì chúng tôi đã xảy ra. Chúng tôi đã thay đổi định vị dịch vụ của chúng tôi cách đây một lúc để sử dụng một nhà máy (trong đó có một giao diện) để chứa hạt nhân, đã giúp trong một số kịch bản thử nghiệm bởi vì chúng tôi có thể trao đổi trong các mocks khác nhau. Nó sẽ chỉ là một chút làm việc trực tiếp với nhà máy đó chứ không phải là các dịch vụ định vị dịch vụ tĩnh. – ryber

Các vấn đề liên quan