2012-06-22 34 views
8

Tôi đang xây dựng một ứng dụng giống như Visual Studio trong WPF và tôi đang gặp một số vấn đề xác định tổ chức thiết kế kiến ​​trúc tốt nhất của các thành phần của tôi. Tôi có kế hoạch sử dụng Unity như container phụ thuộc của tôi tiêm và Visual Studio đơn vị kiểm tra khuôn khổ và có lẽ moq cho thư viện mocking.MVVM với thiết kế kiến ​​trúc Unity và Unit Testing

đầu tiên tôi sẽ mô tả cấu trúc của giải pháp của tôi, sau đó câu hỏi của tôi:

tôi có một dự án WPF có chứa:

  • My khởi chứa Unity (bootstrapper) khi khởi động ứng dụng (trong App.xaml.cs)
  • Tất cả lượt xem ứng dụng của tôi (XAML).

Một dự án khác được gọi là ViewModel này chứa:

  • Tất cả ViewModels ứng dụng của tôi. Tất cả ViewModels tôi kế thừa từ một ViewModelBase đó cho thấy một tài sản ILogger

Logic khởi của tôi là như sau:

  1. ứng dụng Startup
  2. Unity tạo container và đăng ký các loại: MainView và MainViewModel
  3. Giải quyết MainView của tôi và hiển thị nó.

var window = Container.Resolve<MainView>();

window.Show();

constructor MainView tôi nhận được một đối tượng MainViewModel trong constructor của nó:

public MainView(MainViewModel _mvm) 
  1. MainViewModel của tôi có một ViewModel Child cho mỗi tấm của nó:

    public ToolboxViewModel ToolboxVM{get; set;} 
    public SolutionExplorerViewModel SolutionExplorerVM { get; set; } 
    public PropertiesViewModel PropertiesVM { get; set; } 
    public MessagesViewModel MessagesVM { get; set; } 
    

Và tôi đang lập kế hoạch tạo phương thức InitializePanels() khởi tạo từng bảng.

Bây giờ ở đây câu hỏi của tôi: Làm thế nào có thể MainViewModel.InitializePanels() của tôi khởi tạo tất cả các bảng đó? cho các tùy chọn sau:

Lựa chọn 1: Khởi tạo ViewModels bằng tay:

ToolboxVM = new ToolboxViewModel(); 
//Same for the rest of VM... 

Nhược điểm:

  • tôi không sử dụng container Unity nên phụ thuộc của tôi (ví dụILogger) không được tự động giải quyết

Phương án 2: Sử dụng setter tiêm bằng cách chú thích tài sản của tôi:

[Dependency] 
public ToolboxViewModel ToolboxVM{get; set;} 
//... Same for rest of Panel VM's 

Nhược điểm:

  • Tôi đã đọc rằng Unity Setter phụ thuộc nên tránh vì chúng tạo ra sự phụ thuộc với Unity trong trường hợp này
  • Tôi cũng đã đọc rằng bạn nên tránh sử dụng Unity for Unit Tests, vì vậy làm thế nào để làm cho sự phụ thuộc này rõ ràng trong các Bài kiểm tra Đơn vị của tôi? Có nhiều thuộc tính phụ thuộc có thể là một cơn ác mộng để cấu hình.

Lựa chọn 3: Sử dụng Unity Constructor tiêm để vượt qua tất cả ViewModels Bảng điều chỉnh của tôi để các nhà xây dựng MainViewModel vì vậy họ sẽ được tự động giải quyết bằng Unity container:

public MainViewModel(ToolboxViewModel _tbvm, SolutionExploerViewModel _sevm,....) 

Ưu điểm:

  • Sự phụ thuộc sẽ hiển nhiên và rõ ràng tại thời điểm tạo ra, điều này có thể giúp xây dựng các đơn vị ViewModel của tôi.

Nhược điểm:

  • Có rất nhiều thông số nhà xây dựng có thể nhận xấu xí khá nhanh chóng

Lựa chọn 4: Đăng ký tất cả các loại máy ảo của tôi tại tích tụ chứa. Sau đó đi qua các ví dụ UnityContainer qua constructor injection để MainViewModel tôi:

public MainViewModel(IUnityContainer _container) 

Bằng cách đó tôi có thể làm một cái gì đó như:

 Toolbox = _container.Resolve<ToolboxViewModel>(); 
     SolutionExplorer = _container.Resolve<SolutionExplorerViewModel>(); 
     Properties = _container.Resolve<PropertiesViewModel>(); 
     Messages = _container.Resolve<MessagesViewModel>(); 

Nhược điểm:

  • Nếu tôi quyết định không sử dụng Unity cho UnitTests của tôi, như nhiều người đề nghị, sau đó tôi sẽ không thể giải quyết và khởi tạo Panel ViewModels của tôi.

Cho rằng lời giải thích dài dòng, cách tiếp cận tốt nhất để tôi có thể tận dụng lợi thế của Thùng chứa phụ thuộc và kết thúc với giải pháp Đơn vị có thể kiểm tra là gì ??

Cảm ơn trước,

+0

Bạn hoàn toàn đúng. Tôi nên mã chống lại giao diện và không thực hiện cụ thể, tuy nhiên, tôi thường bắt đầu mã hóa các lớp cụ thể và sau đó trích xuất các giao diện của họ với Resharper, chưa đạt đến điểm đó, nhưng tôi sẽ sớm! –

+0

Giải pháp nhanh chóng và bẩn thỉu của tôi cho điều này là để khởi tạo các mô hình xem của tôi trong app.xaml dưới dạng tài nguyên, sau đó kết hợp khi cần thiết. '' làm cho việc kiểm tra đơn vị dễ cài đặt. – Will

Trả lời

5

Điều đầu tiên trước tiên ... Như bạn đã nhận thấy, thiết lập hiện tại của bạn có thể gặp sự cố khi thử nghiệm đơn vị (khởi tạo VM phức tạp). Tuy nhiên, chỉ cần theo dõi DI principle, phụ thuộc vào trừu tượng, không phải trên concretions, khiến vấn đề này biến mất ngay lập tức. Nếu các mô hình khung nhìn của bạn sẽ triển khai các giao diện và các phụ thuộc sẽ được thực hiện thông qua các giao diện, bất kỳ khởi tạo phức tạp nào trở nên không liên quan như trong thử nghiệm, bạn sẽ chỉ sử dụng các mocks.

Tiếp theo, sự cố với thuộc tính được chú thích là bạn tạo khớp nối cao giữa mô hình xem và Unity (đây là lý do tại sao rất có thể là sai). Lý tưởng nhất, đăng ký nên được xử lý tại điểm duy nhất, cấp cao nhất (đó là bootstrapper trong trường hợp của bạn), do đó, container không bị ràng buộc trong bất kỳ cách nào để đối tượng nó cung cấp. lựa chọn của bạn # 3 và # 4 là giải pháp phổ biến nhất cho vấn đề này, với vài lưu ý:

  • để # 3: phụ thuộc quá nhiều nhà xây dựng thường được giảm nhẹ bằng cách nhóm các chức năng phổ biến ở facade classes (tuy nhiên 4 là không mà sau đó tất cả là). Thông thường, mã được thiết kế đúng cách không có vấn đề này. Lưu ý rằng tùy thuộc vào những gì MainViewModel của bạn có thể tất cả những gì bạn cần là phụ thuộc vào danh sách các kiểu xem trẻ em, chứ không phải là các mô hình cụ thể.
  • đến # 4: bạn không nên sử dụng vùng chứa IoC trong các thử nghiệm đơn vị. Bạn đơn giản tạo MainViewModel (qua ctor) theo cách thủ công và chèn mocks bằng tay.

Tôi muốn giải quyết thêm một điểm nữa. Hãy xem xét những gì sẽ xảy ra khi dự án của bạn phát triển. Đóng gói tất cả các mô hình xem vào một dự án có thể không phải là ý tưởng hay. Mỗi mô hình xem sẽ có các phụ thuộc của riêng mình (thường không liên quan đến người khác) và tất cả những thứ này sẽ phải ngồi cùng nhau. Điều này có thể nhanh chóng trở nên khó khăn để duy trì. Thay vào đó, hãy suy nghĩ xem bạn có thể trích xuất một số chức năng phổ biến hay không (ví dụ: nhắn tin, công cụ) và đưa chúng vào các nhóm dự án riêng biệt (một lần nữa, được chia thành các dự án M-VM-V).

Ngoài ra, việc trao đổi lượt xem dễ dàng hơn khi bạn có chức năng nhóm liên quan. Nếu cấu trúc dự án trông như thế này:

> MyApp.Users 
> MyApp.Users.ViewModels 
> MyApp.Users.Views 
> ... 

Thử nhìn khác nhau cho người dùng chỉnh sửa cửa sổ là một vấn đề biên dịch lại và trao đổi hội duy nhất (User.Views). Với tất cả trong một túi cách tiếp cận, bạn sẽ phải xây dựng lại phần lớn hơn của ứng dụng, thậm chí tho đa số của nó đã không thay đổi ở tất cả.

Edit: ghi nhớ rằng thay đổi cấu trúc của dự án (thậm chí một nhỏ) hiện có, thường là một quá trình rấttốn kém với kết quả kinh doanh nhỏ/none. Bạn có thể không được phép hoặc chỉ đơn giản là không thể đủ khả năng làm như vậy.Cách sử dụng dựa trên cấu trúc (DAL, BLL, BO, v.v.) không hoạt động, nó chỉ nặng hơn theo thời gian. Bạn cũng có thể sử dụng chế độ hỗn hợp, với chức năng chính được nhóm theo cách sử dụng của chúng và chỉ cần thêm các chức năng mới bằng cách sử dụng phương pháp mô đun.

+1

Người đàn ông, tôi đã đọc câu trả lời của bạn như 10 lần, mỗi khi nó có ý nghĩa hơn. Nó là một cái mở mắt đáng kinh ngạc. Về việc xây dựng các dự án riêng biệt cho mỗi 'Module' có ý nghĩa nhưng tôi đã có nhiều dự án như DAL (Lớp truy cập dữ liệu), Common, BLL (Business Logic Layer), Đối tượng kinh doanh, Dự án thử nghiệm, v.v ... –

+1

@AdolfoPerez: nếu đó là trường hợp, bạn phải xem xét thời gian và công sức đưa vào * thay đổi * cơ sở hạ tầng hiện có. Nếu bạn không thể đủ khả năng đó, chỉ đơn giản là không làm điều đó - * "túi" * cấu trúc cũng làm việc, chỉ là họ có thể nhận được chút khó khăn hơn để duy trì với thời gian. Tôi đã thêm điều đó vào câu trả lời của tôi để không có hiểu lầm. –

+0

Thực ra, tôi đang ở giai đoạn đầu của thiết kế/phát triển nên nó sẽ không có tác động tiêu cực để di chuyển mọi thứ xung quanh, tôi chỉ cần cảm thấy thoải mái khi muốn đi. Cám ơn bạn lần nữa vì đã thấu hiểu. –

2

Trước hết, bạn có thể muốn sử dụng giao diện chứ không phải là lớp bê tông, do đó bạn sẽ có thể vượt qua bạn các đối tượng giả khi kiểm tra đơn vị, tức là IToolboxViewModel thay vì ToolboxViewModel, v.v.

Điều đó đang được nói, tôi sẽ khuyên bạn nên chọn tùy chọn thứ ba - công cụ xây dựng.Điều này có ý nghĩa nhất, vì nếu không bạn có thể gọi var mainVM = new MainViewModel() và kết thúc bằng mô hình chế độ xem không hoạt động. Bằng cách đó, bạn cũng làm cho nó rất dễ hiểu những phụ thuộc của mô hình khung nhìn của bạn, làm cho nó dễ dàng hơn khi viết các bài kiểm tra đơn vị.

Tôi sẽ xem this link, vì nó có liên quan đến câu hỏi của bạn.

+0

Cảm ơn Lester, vâng tôi nghĩ rằng tùy chọn 3 có ý nghĩa hơn nữa, đặc biệt là khi nhóm các thông số của hàm tạo thông qua các lớp mặt tiền như @jimmy_keen đề xuất. Cảm ơn bạn đã giúp đỡ! Sẽ chờ đợi để nghe những gì người khác đề nghị –

1

Tôi đồng ý với điểm của Lester nhưng muốn thêm một vài tùy chọn và ý kiến ​​khác.

Trường hợp bạn đang truyền ViewModel đến Chế độ xem thông qua hàm tạo, điều này hơi độc đáo vì khả năng ràng buộc của WPF cho phép bạn tách hoàn toàn ViewModel khỏi Chế độ xem bằng cách liên kết với đối tượng DataContext. Trong thiết kế mà bạn đã phác thảo, Chế độ xem được kết hợp với triển khai cụ thể và giới hạn sử dụng lại.

Trong khi mặt tiền dịch vụ sẽ đơn giản hóa tùy chọn 3, nó không phải là không phổ biến (như bạn đã phác thảo) cho Mô hình Chế độ xem cấp cao nhất có nhiều trách nhiệm. Một mẫu khác mà bạn có thể xem xét là một bộ điều khiển hoặc mẫu nhà máy lắp ráp mô hình chế độ xem. Các nhà máy có thể được hỗ trợ bởi các container để làm việc nhưng container được abstracted đi từ người gọi. Một mục tiêu chính trong việc xây dựng các ứng dụng hướng container là giới hạn số lượng các lớp hiểu được cách thức hệ thống được lắp ráp.

Một mối quan ngại khác là số lượng trách nhiệm và mối quan hệ đối tượng thuộc về chế độ xem cấp cao nhất. Nếu bạn nhìn vào Prism (một ứng cử viên tốt với WPF + Unity), nó giới thiệu khái niệm "các vùng" được điền bởi các mô-đun. Một khu vực có thể đại diện cho một thanh công cụ được điền bởi các mô-đun mutliple. Theo thiết kế như vậy, viewmodel cấp cao nhất có ít trách nhiệm hơn (và phụ thuộc!) Và mỗi mô-đun chứa các thành phần DI có thể kiểm tra được. Sự thay đổi lớn trong suy nghĩ từ ví dụ bạn đã cung cấp.

Về tùy chọn 4, nơi vùng chứa được truyền vào thông qua hàm tạo là đảo ngược phụ thuộc kỹ thuật nhưng nó ở dạng vị trí dịch vụ thay vì tiêm phụ thuộc. Đã đi xuống con đường này trước khi tôi có thể nói với bạn rằng đó là một con dốc rất trơn (giống như một vách đá): Phụ thuộc được ẩn bên trong lớp và mã của bạn trở thành một trang web "đúng lúc" điên rồ - hoàn toàn không thể đoán trước, hoàn toàn không thể kiểm chứng.

+0

Cảm ơn phản hồi của bạn @bryanbcook. Trong thực tế, PRISM là một trong những lựa chọn cơ sở hạ tầng của tôi, tôi đã đi qua một số ví dụ về các vùng và mô-đun nhưng tôi nghĩ nó liên quan đến một đường cong học tập đắt hơn nhiều để hiểu sâu về khung PRISM. –

+0

Lăng kính chỉ là một ví dụ mà trách nhiệm có thể được chia nhỏ hơn nữa. – bryanbcook

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