2010-04-04 42 views
8

Tôi và hai đồng nghiệp khác đang cố gắng hiểu cách thiết kế chương trình tốt nhất. Ví dụ, tôi có một giao diện ISoda và nhiều lớp mà thực hiện giao diện như Coke, Pepsi, DrPepper, vv ....Sử dụng chính xác phụ thuộc tiêm

Đồng nghiệp của tôi đang nói rằng nó là tốt nhất để đặt các mặt hàng vào một cơ sở dữ liệu như một chìa khóa/giá trị đôi. Ví dụ:

Key  | Name 
-------------------------------------- 
Coke  | my.namespace.Coke, MyAssembly 
Pepsi  | my.namespace.Pepsi, MyAssembly 
DrPepper | my.namespace.DrPepper, MyAssembly 

... sau đó có tệp cấu hình XML ánh xạ đầu vào tới khóa chính xác, truy vấn cơ sở dữ liệu cho khóa, sau đó tạo đối tượng.

Tôi không có bất kỳ lý do cụ thể nào, nhưng tôi chỉ cảm thấy rằng đây là một thiết kế tồi, nhưng tôi không biết phải nói gì hoặc cách tranh luận chính xác chống lại nó.

Đồng nghiệp thứ hai của tôi cho thấy rằng chúng tôi quản lý vi mô từng lớp học này. Vì vậy, về cơ bản đầu vào sẽ đi qua một tuyên bố chuyển đổi, một cái gì đó tương tự như thế này:

ISoda soda; 

switch (input) 
{ 
    case "Coke": 
     soda = new Coke(); 
     break;  
    case "Pepsi": 
     soda = new Pepsi(); 
     break; 
    case "DrPepper": 
     soda = new DrPepper(); 
     break; 
} 

Điều này có vẻ tốt hơn một chút đối với tôi, nhưng tôi vẫn nghĩ rằng có một cách tốt hơn để làm điều đó. Tôi đã đọc lên trên container IoC vài ngày qua và nó có vẻ như là một giải pháp tốt. Tuy nhiên, tôi vẫn còn rất mới để tiêm phụ thuộc và container IoC, vì vậy tôi không biết làm thế nào để một cách chính xác tranh luận cho nó. Hoặc có lẽ tôi là người sai và có một cách tốt hơn để làm điều đó? Nếu vậy, ai đó có thể đề xuất một phương pháp tốt hơn?

Tôi có thể đưa loại đối số nào vào bảng để thuyết phục đồng nghiệp thử phương pháp khác? Ưu/khuyết điểm là gì? Tại sao chúng ta nên làm điều đó một cách?

Thật không may, đồng nghiệp của tôi rất có khả năng thay đổi vì vậy tôi đang cố gắng tìm ra cách tôi có thể thuyết phục họ.

Edit:

Có vẻ như câu hỏi của tôi là một chút khó hiểu. Những gì chúng tôi đang cố gắng tìm ra là làm thế nào để thiết kế tốt nhất một ứng dụng sử dụng nhiều giao diện và chứa nhiều loại cụ thể để triển khai các giao diện đó.

Có, tôi biết rằng việc lưu trữ nội dung này trong cơ sở dữ liệu là một ý tưởng tồi, nhưng chúng tôi chỉ đơn giản là không biết cách nào tốt hơn. Đây là lý do tại sao tôi yêu cầu cách chúng tôi có làm điều đó tốt hơn không.

public void DrinkSoda(string input) 
{ 
    ISoda soda = GetSoda(input); 
    soda.Drink(); 
} 

Làm cách nào để chúng tôi triển khai chính xác GetSoda? Chúng ta nên suy nghĩ lại toàn bộ thiết kế? Nếu đó là một ý tưởng tồi để vượt qua các chuỗi ma thuật như thế này, thì chúng ta nên làm như thế nào?

Đầu vào của người dùng như "Chế độ ăn kiêng", "Coke", "Coke Lime" hoặc bất kỳ thứ gì sẽ khởi tạo một loại Coke, do đó nhiều từ khóa ánh xạ tới một loại duy nhất.

Rất nhiều người đã nói rằng mã được đăng ở trên không tốt. Tôi biết nó xấu. Tôi đang tìm kiếm lý do để trình bày cho các đồng nghiệp của tôi và tranh luận tại sao nó xấu và tại sao chúng ta cần phải thay đổi.

Tôi xin lỗi nếu giải thích này vẫn khó hiểu. Thật khó cho tôi để mô tả tình hình bởi vì tôi không thực sự hiểu làm thế nào để đặt nó trong lời nói.

+3

Thật khó để hiểu vấn đề thực tế bạn đang cố giải quyết là gì. – mquander

+1

Khối mã thứ hai của bạn trông giống như thứ bạn sẽ thấy khi sử dụng mẫu nhà máy. Bạn có hỏi cách xác định loại ISode được chuyển cho bạn không? – NitroxDM

+2

Wow - lưu trữ tên kiểu trong cơ sở dữ liệu, sử dụng tệp XML để ánh xạ từ tới bản ghi cơ sở dữ liệu và sau đó sử dụng sự phản chiếu để khởi tạo chúng - bây giờ ** ** là ** enterprisey! ** – Aaronaught

Trả lời

4

Giới thiệu tốt nhất về tiêm phụ thuộc mà tôi đã gặp phải thực sự là quick series of articles that form a walkthrough of the concepts trên trang web wiki Ninject.

Bạn chưa thực sự đưa ra nhiều thông tin cơ bản về những gì bạn đang cố gắng hoàn thành, nhưng có vẻ như tôi đang cố gắng thiết kế kiến ​​trúc "plug-in". Nếu đây là trường hợp - cảm giác của bạn là chính xác - cả hai thiết kế đó đều trong một từ, khủng khiếp. Việc đầu tiên sẽ tương đối chậm (cơ sở dữ liệu phản ánh?), Có phụ thuộc lớn không cần thiết trên một lớp cơ sở dữ liệu, và sẽ không quy mô với chương trình của bạn, vì mỗi lớp mới sẽ phải được nhập vào cơ sở dữ liệu. Có gì sai khi sử dụng sự phản chiếu?

Cách tiếp cận thứ hai là không thể đánh giá được. Mỗi khi bạn giới thiệu một lớp mới, mã cũ phải được cập nhật, và một cái gì đó phải "biết" về mọi đối tượng mà bạn sẽ cắm vào - làm cho các bên thứ ba không thể phát triển các plugin. Các khớp nối bạn thêm, ngay cả với một cái gì đó giống như một mẫu Locator, có nghĩa là mã của bạn không phải là linh hoạt như nó có thể được.

Đây có thể là cơ hội tốt để sử dụng tính năng tiêm phụ thuộc. Đọc các bài báo tôi đã liên kết. Ý tưởng chính là khung tiêm phụ thuộc sẽ sử dụng tệp cấu hình để tải động các lớp mà bạn chỉ định. Điều này làm cho việc trao đổi các thành phần của chương trình của bạn rất đơn giản và dễ hiểu.

3

Tôi sẽ trung thực và tôi biết những người khác không đồng ý, nhưng tôi thấy rằng việc trừu tượng hóa lớp học thành XML hoặc cơ sở dữ liệu là một ý tưởng khủng khiếp. Tôi là một fan hâm mộ làm cho mã tinh thể rõ ràng ngay cả khi nó hy sinh một số tính linh hoạt. Nhược điểm tôi thấy là:

  • Thật khó để theo dõi mã và tìm đối tượng được tạo.
  • Những người khác làm việc trong dự án được yêu cầu hiểu định dạng độc quyền của bạn để khởi tạo các đối tượng.
  • Trên các dự án nhỏ, bạn sẽ dành nhiều thời gian hơn để triển khai việc tiêm phụ thuộc mà bạn sẽ thực sự chuyển đổi quanh các lớp học.
  • Bạn sẽ không thể biết những gì đang được tạo cho đến khi thời gian chạy. Đó là một cơn ác mộng gỡ lỗi và trình gỡ lỗi của bạn cũng sẽ không thích bạn.

Tôi thực sự hơi mệt khi nghe về mốt chương trình cụ thể này. Nó cảm thấy và trông xấu xí, và nó giải quyết một vấn đề mà vài dự án cần phải giải quyết. Nghiêm túc, bao lâu bạn sẽ được thêm và loại bỏ nước ngọt từ mã đó. Trừ khi nó là thực tế mỗi khi bạn biên dịch thì có rất ít lợi thế với rất nhiều complxexity thêm.

Chỉ cần làm rõ, tôi nghĩ rằng đảo ngược kiểm soát có thể là một ý tưởng thiết kế tốt. Tôi chỉ không thích có mã được nhúng bên ngoài chỉ cho instantiation, trong XML hay cách khác.

Giải pháp hộp chuyển đổi là tốt, vì đó là một cách để triển khai mẫu nhà máy. Hầu hết các lập trình viên sẽ không khó để hiểu nó. Nó cũng được tổ chức rõ ràng hơn là tạo các lớp con xung quanh mã của bạn.

+0

Đây là một trong những lý do tại sao tôi thích Ninject như một thùng chứa DI. Việc đăng ký được thực hiện trong một mô-đun mã sử dụng giao diện thông thạo - không phải là tệp cấu hình hoặc cơ sở dữ liệu XML. Tương tự với Autofac. – Aaronaught

+3

@aaronaught, tôi nghĩ rằng bất kỳ vùng chứa nào ngoại trừ Spring.NET, incl. Unity có thể thực hiện đăng ký mã, hầu hết trong số đó (trừ Unity và Spring) có thể thực hiện đăng ký dựa trên quy ước, vì vậy tôi không nghĩ đó là một vấn đề lớn - đó là tiêu chuẩn thực tế. –

3

Mỗi thư viện DI có cú pháp riêng cho điều này.Ninject trông giống như sau:

public class DrinkModule : NinjectModule 
{ 
    public override void Load() 
    { 
     Bind<IDrinkable>() 
      .To<Coke>() 
      .Only(When.ContextVariable("Name").EqualTo("Coke")); 
     Bind<IDrinkable>() 
      .To<Pepsi>() 
      .Only(When.ContextVariable("Name").EqualTo("Pepsi")); 
     // And so on 
    } 
} 

Tất nhiên, nó được khá tẻ nhạt để duy trì ánh xạ, vì vậy nếu tôi có loại hệ thống này sau đó tôi thường kết thúc đặt cùng một đăng ký phản ánh dựa trên tương tự như this one:

public class DrinkReflectionModule : NinjectModule 
{ 
    private IEnumerable<Assembly> assemblies; 

    public DrinkReflectionModule() : this(null) { } 

    public DrinkReflectionModule(IEnumerable<Assembly> assemblies) 
    { 
     this.assemblies = assemblies ?? 
      AppDomain.CurrentDomain.GetAssemblies(); 
    } 

    public override void Load() 
    { 
     foreach (Assembly assembly in assemblies) 
      RegisterDrinkables(assembly); 
    } 

    private void RegisterDrinkables(Assembly assembly) 
    { 
     foreach (Type t in assembly.GetExportedTypes()) 
     { 
      if (!t.IsPublic || t.IsAbstract || t.IsInterface || t.IsValueType) 
       continue; 
      if (typeof(IDrinkable).IsAssignableFrom(t)) 
       RegisterDrinkable(t); 
     } 
    } 

    private void RegisterDrinkable(Type t) 
    { 
     Bind<IDrinkable>().To(t) 
      .Only(When.ContextVariable("Name").EqualTo(t.Name)); 
    } 
} 

Mã nhẹ hơn nhưng bạn không bao giờ phải cập nhật mã, nó sẽ tự đăng ký.

Oh, và bạn sử dụng nó như thế này:

var pop = kernel.Get<IDrinkable>(With.Parameters.ContextVariable("Name", "Coke")); 

Rõ ràng đây là cụ thể cho một thư viện duy nhất DI (Ninject), nhưng tất cả đều ủng hộ quan điểm của các biến hoặc các thông số, và có mô hình tương đương cho tất cả chúng (Autofac, Windsor, Unity, vv)

Vì vậy, câu trả lời ngắn cho câu hỏi của bạn, về cơ bản, có, DI có thể thực hiện điều này và đây là một nơi khá tốt để có loại logic này , mặc dù cả hai ví dụ mã trong câu hỏi ban đầu của bạn thực sự không liên quan gì đến DI (thứ hai chỉ là một phương pháp nhà máy, và đầu tiên là ... tốt, enterprisey).

+0

Có, tôi nhận thấy rằng mã đã đăng không liên quan gì đến DI. Tôi yêu cầu những lý do chính đáng tại sao chúng ta nên hoặc không nên chuyển sang DI để xử lý các tình huống như thế này. Tôi hoàn toàn đồng ý rằng mã được đăng là xấu, và tôi muốn các đối số tốt để trình bày cho đồng nghiệp của tôi. – Rune

+0

@Rune: Tôi đã đọc bản chỉnh sửa của bạn và tôi nghĩ câu trả lời của tôi về bản chất là giống nhau; nếu bạn đang tìm kiếm một cách tốt hơn để làm điều này thì câu trả lời của tôi sẽ là * sử dụng thư viện DI *. Đối số chính có lợi là (a) bảo trì và (b) nó * dễ dàng *. Và có thể (c) ít có khả năng bị lỗi hơn là điều bạn tự viết. Nếu không có thứ gì quan trọng với bạn thì tôi sẽ chỉ sử dụng phương pháp nhà máy. – Aaronaught

7

Như một quy luật chung, nó hướng đối tượng hơn để truyền xung quanh các đối tượng đóng gói các khái niệm hơn là truyền xung quanh các chuỗi. Điều đó nói rằng, có rất nhiều trường hợp (đặc biệt là khi nói đến giao diện người dùng) khi bạn cần dịch dữ liệu thưa thớt (chẳng hạn như chuỗi) sang các đối tượng miền thích hợp.

Ví dụ, bạn cần dịch đầu vào của người dùng dưới dạng một chuỗi thành một cá thể ISoda.

Cách tiêu chuẩn, lỏng lẻo để làm điều này là sử dụng Abstract Factory. Định nghĩa nó như thế này:

public interface ISodaFactory 
{ 
    ISoda Create(string input); 
} 

tiêu dùng của bạn bây giờ có thể mất một sự phụ thuộc vào ISodaFactory và sử dụng nó, như thế này:

public class SodaConsumer 
{ 
    private readonly ISodaFactory sodaFactory; 

    public SodaConsumer(ISodaFactory sodaFactory) 
    { 
     if (sodaFactory == null) 
     { 
      throw new ArgumentNullException(sodaFactory); 
     } 

     this.sodaFactory = sodaFactory; 
    } 

    public void DrinkSoda(string input) 
    { 
     ISoda soda = GetSoda(input); 
     soda.Drink(); 
    } 

    private ISoda GetSoda(input) 
    { 
     return this.sodaFactory.Create(input); 
    } 
} 

Thông báo như thế nào sự kết hợp của các khoản Guard và readonly từ khóa đảm bảo SodaConsumer các bất biến của lớp, cho phép bạn sử dụng sự phụ thuộc mà không lo lắng rằng nó có thể là null.

+0

Có lẽ tôi chỉ hiểu lầm câu hỏi, nhưng tôi nghĩ anh ta đã hỏi làm thế nào để thực hiện phương thức 'ISodaFactory.Create' - đó là nơi mà tất cả những câu lệnh' switch' hoặc mã phản chiếu đã biến mất. – Aaronaught

4

Đây là mô hình tôi thường sử dụng nếu tôi cần phải nhanh chóng lớp đúng dựa trên một tên hoặc dữ liệu tuần tự khác:

public interface ISodaCreator 
{ 
    string Name { get; } 
    ISoda CreateSoda(); 
} 

public class SodaFactory 
{ 
    private readonly IEnumerable<ISodaCreator> sodaCreators; 

    public SodaFactory(IEnumerable<ISodaCreator> sodaCreators) 
    { 
     this.sodaCreators = sodaCreators; 
    } 

    public ISoda CreateSodaFromName(string name) 
    { 
     var creator = this.sodaCreators.FirstOrDefault(x => x.Name == name); 
     // TODO: throw "unknown soda" exception if creator null here 

     return creator.CreateSoda(); 
    } 
} 

Lưu ý rằng bạn vẫn sẽ cần phải thực hiện rất nhiều ISodaCreator triển khai, và bằng cách nào đó thu thập tất cả các triển khai để chuyển đến hàm tạo SodaFactory. Để giảm việc tham gia vào đó, bạn có thể tạo ra một tác giả generic nước ngọt duy nhất dựa trên phản ánh:

public ReflectionSodaCreator : ISodaCreator 
{ 
    private readonly ConstructorInfo constructor; 

    public string Name { get; private set; } 

    public ReflectionSodaCreator(string name, ConstructorInfo constructor) 
    { 
     this.Name = name; 
     this.constructor = constructor; 
    } 

    public ISoda CreateSoda() 
    { 
     return (ISoda)this.constructor.Invoke(new object[0]) 
    } 
} 

và sau đó chỉ cần quét lắp ráp thực hiện (hoặc một số bộ sưu tập khác của hội) để tìm tất cả các loại soda và xây dựng một SodaFactory như thế này:

IEnumerable<ISodaCreator> sodaCreators = 
    from type in Assembly.GetExecutingAssembly().GetTypes() 
    where type.GetInterface(typeof(ISoda).FullName) != null 
    from constructor in type.GetConstructors() 
    where constructor.GetParameters().Length == 0 
    select new ReflectionSodaCreator(type.Name, constructor); 
sodaCreators = new List<ISodaCreator>(sodaCreators); // evaluate only once 
var sodaFactory = new SodaFactory(sodaCreators); 

Lưu ý rằng câu trả lời của tôi bổ sung rằng Mark SEEMANN: anh đang nói cho bạn để cho khách hàng sử dụng một bản tóm tắt ISodaFactory, vì vậy mà họ không cần phải quan tâm đến cách công trình nhà máy soda.Câu trả lời của tôi là một ví dụ thực hiện của một nhà máy soda như vậy.

3

Tôi nghĩ rằng vấn đề không phải là ngay cả một nhà máy hoặc bất cứ điều gì; đó là cấu trúc lớp học của bạn.

DrPepper hoạt động khác với Pepsi không? Và Coke? Đối với tôi, có vẻ như bạn đang sử dụng giao diện không đúng cách.

Tùy thuộc vào tình huống, tôi sẽ tạo một lớp Soda với thuộc tính BrandName hoặc tương tự. BrandName sẽ được đặt thành DrPepper hoặc Coke, hoặc qua số enum hoặc string (trước đây có vẻ như là giải pháp tốt hơn cho trường hợp của bạn).

Giải quyết vấn đề về nhà máy thật dễ dàng. Trong thực tế, bạn không cần một nhà máy nào cả. Chỉ cần sử dụng hàm tạo của lớp Soda.

+0

Có, họ sẽ hành xử khác nhau. Tôi chỉ cố gắng làm ví dụ của tôi là chung nhất có thể. Nhưng trong thế giới thực, phương thức giao diện "Uống" sẽ được thực hiện khác nhau cho mỗi loại soda. – Rune

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