2009-04-23 48 views
26

Tôi đang thử nghiệm với MVVM lần đầu tiên và thực sự thích tách trách nhiệm. Tất nhiên mọi mẫu thiết kế chỉ giải quyết được nhiều vấn đề - không phải tất cả. Vì vậy, tôi đang cố gắng tìm ra nơi để lưu trữ trạng thái ứng dụng và nơi lưu trữ các lệnh rộng ứng dụng.Nơi lưu trữ cài đặt/trạng thái ứng dụng trong ứng dụng MVVM

Cho phép ứng dụng của tôi kết nối với một URL cụ thể. Tôi có một ConnectionWindow và một ConnectionViewModel hỗ trợ thu thập thông tin này từ người dùng và gọi các lệnh để kết nối đến địa chỉ. Lần sau khi ứng dụng bắt đầu, tôi muốn kết nối lại với cùng địa chỉ này mà không cần nhắc người dùng.

Giải pháp của tôi cho đến nay là tạo một ApplicationViewModel cung cấp lệnh để kết nối với một địa chỉ cụ thể và lưu địa chỉ đó vào một số lưu trữ liên tục (nơi lưu thực sự không liên quan đến câu hỏi này). Dưới đây là một mô hình lớp viết tắt.

Việc áp dụng quan điểm mô hình:

public class ApplicationViewModel : INotifyPropertyChanged 
{ 
    public Uri Address{ get; set; } 
    public void ConnectTo(Uri address) 
    { 
     // Connect to the address 
     // Save the addres in persistent storage for later re-use 
     Address = address; 
    } 

    ... 
} 

Kết nối xem mô hình:

public class ConnectionViewModel : INotifyPropertyChanged 
{ 
    private ApplicationViewModel _appModel; 
    public ConnectionViewModel(ApplicationViewModel model) 
    { 
     _appModel = model; 
    } 

    public ICommand ConnectCmd 
    { 
     get 
     { 
      if(_connectCmd == null) 
      { 
       _connectCmd = new LambdaCommand(
        p => _appModel.ConnectTo(Address), 
        p => Address != null 
        ); 
      } 
      return _connectCmd; 
     } 
    }  

    public Uri Address{ get; set; } 

    ... 
} 

Vậy câu hỏi là thế này: Là một ApplicationViewModel đúng cách để xử lý này? Bạn có thể lưu trữ trạng thái ứng dụng khác như thế nào?

CHỈNH SỬA: Tôi cũng muốn biết điều này ảnh hưởng như thế nào đến khả năng kiểm tra. Một trong những lý do chính để sử dụng MVVM là khả năng kiểm tra các mô hình mà không cần ứng dụng máy chủ lưu trữ. Cụ thể là tôi quan tâm đến thông tin chi tiết về cách cài đặt ứng dụng tập trung ảnh hưởng đến khả năng thử nghiệm và khả năng giả lập các mô hình phụ thuộc.

Trả lời

10

Nếu bạn không sử dụng M-V-VM, giải pháp rất đơn giản: bạn đặt dữ liệu và chức năng này vào loại có nguồn gốc Ứng dụng của bạn. Application.Current sau đó cung cấp cho bạn truy cập vào nó. Vấn đề ở đây, như bạn đã biết, đó là Application.Current gây ra vấn đề khi đơn vị kiểm tra ViewModel. Đó là những gì cần phải được sửa chữa. Bước đầu tiên là tách riêng chúng ta khỏi một cá thể ứng dụng cụ thể. Làm điều này bằng cách định nghĩa một giao diện và thực hiện nó trên kiểu ứng dụng cụ thể của bạn.

public interface IApplication 
{ 
    Uri Address{ get; set; } 
    void ConnectTo(Uri address); 
} 

public class App : Application, IApplication 
{ 
    // code removed for brevity 
} 

Bây giờ bước tiếp theo là xóa cuộc gọi đến ứng dụng. Hiện tại trong ViewModel bằng cách sử dụng Đảo ngược kiểm soát hoặc Dịch vụ định vị.

public class ConnectionViewModel : INotifyPropertyChanged 
{ 
    public ConnectionViewModel(IApplication application) 
    { 
    //... 
    } 

    //... 
} 

Hiện tại, tất cả chức năng "toàn cầu" được cung cấp qua giao diện dịch vụ giả lập, IApplication. Bạn vẫn còn với cách để xây dựng ViewModel với thể hiện dịch vụ chính xác, nhưng có vẻ như bạn đã xử lý nó? Nếu bạn đang tìm kiếm một giải pháp ở đó, Onyx (từ chối trách nhiệm, tôi là tác giả) có thể cung cấp giải pháp ở đó. Ứng dụng của bạn sẽ đăng ký sự kiện View.Created và thêm chính nó làm dịch vụ và khung công tác sẽ xử lý phần còn lại.

+0

Tôi đã thực sự rót mã Onyx trong vài ngày qua để thu thập một số thông tin chi tiết về WPF. Nó chắc chắn đã đặt ra rất nhiều suy nghĩ của tôi và tôi đã học được một chút. –

+0

Cảm ơn. Ngay cả khi bạn không sử dụng Onyx, tôi hy vọng những ý tưởng này rất hữu ích. Onyx là chắc chắn không cần thiết ở đây, mặc dù các giải pháp giao diện dịch vụ tôi nghĩ rằng thực sự là những gì bạn đang tìm kiếm. – wekempf

2

Có, bạn đang đi đúng hướng. Khi bạn có hai điều khiển trong hệ thống của mình cần truyền dữ liệu, bạn muốn làm điều đó theo cách được tách ra càng tốt. Có nhiều hướng khác nhau để làm điều đó.

Trong lăng kính 2, chúng có khu vực giống như một "bus dữ liệu". Một điều khiển có thể tạo ra dữ liệu với một khóa được thêm vào bus và bất kỳ điều khiển nào muốn dữ liệu đó có thể đăng ký gọi lại khi dữ liệu đó thay đổi.

Cá nhân, tôi đã triển khai một cái gì đó mà tôi gọi là "ApplicationState". Nó có cùng mục đích. Nó thực hiện INotifyPropertyChanged, và bất cứ ai trong hệ thống có thể ghi vào các thuộc tính cụ thể hoặc đăng ký cho các sự kiện thay đổi. Nó ít chung chung hơn so với giải pháp Prism, nhưng nó hoạt động. Đây là khá nhiều những gì bạn tạo ra.

Nhưng bây giờ, bạn có vấn đề về cách vượt qua trạng thái ứng dụng. Cách học cũ để làm điều này là làm cho nó thành Singleton. Tôi không phải là một fan hâm mộ lớn này. Thay vào đó, tôi có một giao diện được định nghĩa là:

public interface IApplicationStateConsumer 
{ 
    public void ConsumeApplicationState(ApplicationState appState); 
} 

Bất kỳ thành phần trực quan nào trong cây đều có thể triển khai giao diện này và chỉ cần chuyển trạng thái Ứng dụng tới ViewModel.

Sau đó, trong cửa sổ gốc, khi sự kiện được tải được kích hoạt, tôi duyệt qua cây thị giác và tìm các điều khiển muốn trạng thái ứng dụng (IApplicationStateConsumer). Tôi giao cho họ appState và hệ thống của tôi được khởi tạo. Đó là tiêm phụ thuộc của người nghèo.

Mặt khác, Prism giải quyết tất cả những vấn đề này. Tôi ước gì mình có thể quay trở lại và tái kiến ​​trúc bằng Prism ... nhưng hơi muộn để tôi có thể tiết kiệm chi phí.

11

Tôi thường có cảm giác xấu về mã có mô hình xem trực tiếp giao tiếp với người khác. Tôi thích ý tưởng rằng phần VVM của mẫu nên về cơ bản có thể cắm được và không có gì bên trong khu vực mã đó phụ thuộc vào sự tồn tại của bất kỳ thứ gì khác trong phần đó. Lý do đằng sau điều này là không tập trung vào logic, nó có thể trở nên khó khăn để xác định trách nhiệm. Mặt khác, dựa trên mã thực tế của bạn, nó có thể chỉ là ApplicationViewModel được đặt tên không đúng, nó không làm cho một mô hình có thể truy cập được, vì vậy đây có thể chỉ là một sự lựa chọn nghèo nàn về tên.

Dù bằng cách nào đi nữa, giải pháp đó sẽ làm hỏng trách nhiệm. Con đường tôi nhìn thấy nó, bạn có ba điều cần đạt được:

  1. Cho phép người sử dụng để yêu cầu để kết nối đến một địa chỉ
  2. Sử dụng địa chỉ đó để kết nối với một máy chủ
  3. Persist địa chỉ đó.

Tôi muốn đề nghị bạn cần ba lớp thay vì hai lớp của bạn.

public class ServiceProvider 
{ 
    public void Connect(Uri address) 
    { 
     //connect to the server 
    } 
} 

public class SettingsProvider 
{ 
    public void SaveAddress(Uri address) 
    { 
     //Persist address 
    } 

    public Uri LoadAddress() 
    { 
     //Get address from storage 
    } 
} 

public class ConnectionViewModel 
{ 
    private ServiceProvider serviceProvider; 

    public ConnectionViewModel(ServiceProvider provider) 
    { 
     this.serviceProvider = serviceProvider; 
    } 

    public void ExecuteConnectCommand() 
    { 
     serviceProvider.Connect(Address); 
    }   
} 

Điều tiếp theo để quyết định là cách địa chỉ đến Trình cài đặt chuyên biệt. Bạn có thể vượt qua nó từ ConnectionViewModel như bạn hiện tại, nhưng tôi không quan tâm đến điều đó bởi vì nó làm tăng sự ghép nối của mô hình khung nhìn và nó không phải là trách nhiệm của ViewModel để biết rằng nó cần sự bền bỉ. Một lựa chọn khác là thực hiện cuộc gọi từ ServiceProvider, nhưng nó không thực sự cảm thấy với tôi như nó phải là trách nhiệm của ServiceProvider. Trong thực tế nó không cảm thấy như trách nhiệm của bất cứ ai khác hơn là SettingsProvider. Điều này khiến tôi tin rằng nhà cung cấp thiết lập nên lắng nghe những thay đổi đối với địa chỉ được kết nối và tiếp tục tồn tại mà không có sự can thiệp.Nói cách khác một sự kiện:

public class ServiceProvider 
{ 
    public event EventHandler<ConnectedEventArgs> Connected; 
    public void Connect(Uri address) 
    { 
     //connect to the server 
     if (Connected != null) 
     { 
      Connected(this, new ConnectedEventArgs(address)); 
     } 
    } 
} 

public class SettingsProvider 
{ 

    public SettingsProvider(ServiceProvider serviceProvider) 
    { 
     serviceProvider.Connected += serviceProvider_Connected; 
    } 

    protected virtual void serviceProvider_Connected(object sender, ConnectedEventArgs e) 
    { 
     SaveAddress(e.Address); 
    } 

    public void SaveAddress(Uri address) 
    { 
     //Persist address 
    } 

    public Uri LoadAddress() 
    { 
     //Get address from storage 
    } 
} 

này giới thiệu khớp nối chặt chẽ giữa ServiceProvider và SettingsProvider, mà bạn muốn tránh nếu có thể và tôi muốn sử dụng một EventAggregator đây, mà tôi đã thảo luận trong một câu trả lời cho this question

Để giải quyết các vấn đề về khả năng kiểm tra, bây giờ bạn có một kỳ vọng rất được xác định cho những gì mỗi phương pháp sẽ làm. ConnectionViewModel sẽ gọi kết nối, ServiceProvider sẽ kết nối và SettingsProvider sẽ tiếp tục tồn tại. Để kiểm tra ConnectionViewModel có thể bạn muốn chuyển đổi các khớp nối với ServiceProvider từ một lớp học để một giao diện:

public class ServiceProvider : IServiceProvider 
{ 
    ... 
} 

public class ConnectionViewModel 
{ 
    private IServiceProvider serviceProvider; 

    public ConnectionViewModel(IServiceProvider provider) 
    { 
     this.serviceProvider = serviceProvider; 
    } 

    ...  
} 

Sau đó, bạn có thể sử dụng một khuôn khổ mocking để giới thiệu một IServiceProvider chế giễu rằng bạn có thể kiểm tra để đảm bảo rằng các phương pháp kết nối được gọi với các tham số dự kiến.

Kiểm tra hai lớp khác là khó khăn hơn vì chúng sẽ dựa vào việc có một máy chủ thực và thiết bị lưu trữ liên tục thực sự. Bạn có thể thêm nhiều lớp khác nhau để trì hoãn điều này (ví dụ như PersistenceProvider mà Trình cài đặt sử dụng) nhưng cuối cùng bạn rời khỏi thế giới kiểm tra đơn vị và nhập thử nghiệm tích hợp. Nói chung khi tôi viết mã với các mẫu bên trên các mô hình và các mô hình xem có thể có được phạm vi kiểm tra đơn vị tốt, nhưng các nhà cung cấp yêu cầu các phương pháp thử nghiệm phức tạp hơn. Tất nhiên, một khi bạn đang sử dụng EventAggregator để ngắt kết nối và IOC để tạo điều kiện thử nghiệm nó có lẽ đáng xem xét một trong các khuôn khổ tiêm phụ thuộc như Prism của Microsoft, nhưng ngay cả khi bạn quá muộn trong quá trình phát triển để trở lại -Hệ thống kiến ​​trúc rất nhiều các quy tắc và các mẫu có thể được áp dụng cho mã hiện tại theo một cách đơn giản hơn.

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