Внедрение SOLID в ViewModel с MVVM Light Framework - PullRequest
0 голосов
/ 05 апреля 2020

В настоящее время я выполняю рефакторинг своих кодов для соответствия SOLID. Принцип SOLID практикуется большинством разработчиков, и я пренебрегаю изучением этого принципа. Но сейчас мне это нужно!

Моя ViewModel содержит События, Поля, Свойства (ObseravbleCollection и другие типы данных), ICommand, Методы, которые сделаны для определенного c Представления, а некоторые из ViewModel содержат Свойства и Методы, только если коллекция будет использоваться в других видах. Так что для некоторых списков это больше похоже на виртуальную машину многократного использования. т.е. представление, где управление сотрудниками, и другое представление для перечисления сотрудников для других целей.

Я пытаюсь понять и создал простой пользовательский интерфейс входа в систему с использованием WPF, и моя ViewModel выглядит следующим образом.

using GalaSoft.MvvmLight.Command;

using System;
using System.Collections.ObjectModel;
using System.Threading.Tasks;
using System.Windows.Input;

using UnderstandingSOLID.Models;
using UnderstandingSOLID.Services.API;

namespace UnderstandingSOLID.ViewModels
{
    public class ViewModel_Login : VMBase
    {
        #region events
        public EventHandler OnLoggedIn;
        #endregion

        #region vars

        #endregion

        #region properties
        public ObservableCollection<Model_ServerType> ServerTypes { get; set; } = new ObservableCollection<Model_ServerType>();

        private Model_ServerType _SelectedServerType = new Model_ServerType();
        public Model_ServerType SelectedServerType
        {
            get { return _SelectedServerType; }
            set
            {
                Set(nameof(SelectedServerType), ref _SelectedServerType, value);
            }
        }

        private string _ServerName = null;
        public string ServerName
        {
            get { return _ServerName; }
            set { Set(nameof(ServerName), ref _ServerName, value); }
        }

        private string _Username = null;
        public string Username
        {
            get { return _Username; }
            set { Set(nameof(Username), ref _Username, value); }
        }

        private string _Password = null;
        public string Password
        {
            get { return _Password; }
            set { Set(nameof(Password), ref _Password, value); }
        }

        private bool _ShowLoginUI = false;
        public bool ShowLoginUI
        {
            get { return _ShowLoginUI; }
            set { Set(nameof(ShowLoginUI), ref _ShowLoginUI, value); }
        }
        #endregion

        #region commands
        public ICommand Command_Connect { get; set; }
        public ICommand Command_Help { get; set; }
        #endregion

        #region ctors
        public ViewModel_Login()
        {
            InitCommands();

            // used only in UWP & WPF
            // or anything that supports design time updates
            if (base.IsInDesignMode)
            {
                DesignData();
            }
        }
        #endregion

        #region command methods
        void Command_Connect_Click()
        {
            Connect();
        }

        void Command_Help_Click()
        {
            // does nothing at the moment
        }
        #endregion

        #region methods
        void InitCommands()
        {
            if (Command_Connect == null) Command_Connect = new RelayCommand(Command_Connect_Click);
            if (Command_Help == null) Command_Help = new RelayCommand(Command_Help_Click);
        }

        /// <summary>
        /// codes here are messy since they are only used for desining the UI
        /// </summary>
        void DesignData()
        {
            this.ShowLoginUI = true;
            this.ServerTypes.Clear();

            string[] serverTypes = new string[]
            {
                "Database Engine",
                "Analysis Services"
            };

            for (int i = 0; i < serverTypes.Length; i++)
            {
                this.ServerTypes.Add(new Model_ServerType()
                {
                    Id = i,
                    ServerType = serverTypes[i]
                });
            }

            this.ShowDlgMsg("Title", "Failed to login");
        }

        // this is called in Loaded event in MainPage.xaml.cs
        public async Task RefreshData()
        {
            this.ServerTypes.Clear();

            string[] serverTypes = new string[]
            {
                "Database Engine",
                "Analysis Services",
                "Reporting Services",
                "Integration Services"
            };

            for(int i = 0; i < serverTypes.Length; i++)
            {
                this.ServerTypes.Add(new Model_ServerType()
                {
                    Id = i,
                    ServerType = serverTypes[i]
                });
            }
        }

        async Task Connect()
        {
            if(this.SelectedServerType.ServerType != null && await ApiClient.I.Login(this.Username, this.Password))
            {
                this.HideDlgMsg();

                // navigate to main page
                // but we'll just Invoke an event for simplicity

                this.OnLoggedIn?.Invoke(this, null);
            }
            else
            {
                this.ShowDlgMsg("Error", "Failed to login");
            }
        }
        #endregion
    }
}

VMBase выглядит следующим образом

using GalaSoft.MvvmLight;

namespace UnderstandingSOLID.ViewModels
{
    public abstract class VMBase : ViewModelBase
    {
        private bool _ShowMessage = false;
        public bool ShowMessage
        {
            get { return _ShowMessage; }
            set { Set(nameof(ShowMessage), ref _ShowMessage, value); }
        }

        private string _MessageTitle = null;
        public string MessageTitle
        {
            get { return _MessageTitle; }
            set { Set(nameof(MessageTitle), ref _MessageTitle, value); }
        }

        private string _MessageBody = null;
        public string MessageBody
        {
            get { return _MessageBody; }
            set { Set(nameof(MessageBody), ref _MessageBody, value); }
        }

        public virtual void ShowDlgMsg(string title, string message)
        {
            this.ShowMessage = true;
            this.MessageTitle = title;
            this.MessageBody = message;
        }

        public void HideDlgMsg()
        {
            this.ShowMessage = false;
        }
    }
}

Так я обычно пишу всем членам моей ViewModel. Не обращайте внимания на то, как я вызывал метод Login in Connect в ViewModel_Login. Это было только упрощено.

Так как же реализовать принцип SOLID в этом случае?

Пример приложения для входа в систему выглядит так в простое enter image description here

Не удалось войти в систему enter image description here

вошли в систему enter image description here

Скачать репо можно здесь https://github.com/Nullstr1ng/UnderstandingSOLID

1 Ответ

1 голос
/ 05 апреля 2020

Принципы программирования похожи на пиратский код: они больше похожи на руководящие принципы, чем на настоящие правила - они не всегда улучшают вещи.

Ваша виртуальная машина выглядит нормально - именно так вы делаете ViewModels. Я использую другой фреймворк, поэтому я бы делал некоторые вещи по-другому, но:

  • вы не используете ссылку на класс представления в ViewModel = GREAT! (вы все равно должны показать нам представление, по крайней мере CodeBehind, так как это самая сложная часть в MVVM)
  • APIClien t должна быть зависимостью, экземпляр должен быть передан в конструктор as и interface od IAPIClient в отличие от stati c property
  • , если вы действительно хотите следовать SOLID, каждый ViewModel должен иметь свой собственный интерфейс: IViewModel_Login, содержащий все методы, свойства и события publi c, которые представление использует (на самом деле это можно использовать для создания ViewModel_Login_Design класса, который вы можете указать в XAML, который просто возвращает некоторые значения stati c [есть d: DataContext ])
  • Вы должны извлечь класс для обработки сообщения из VMBase - и, возможно, не использовать его в базе, а внедрить его в ViewModels, которые его используют. В базе должны быть только вещи, которые являются общими почти для ВСЕХ моделей ViewModels.

Я также добавлю сюда ссылку на ReactiveUI , поскольку я твердо верю, что ее дизайн и ее направленность вокруг IObservable и использование DynamicData для динамических c коллекций намного лучше, чем любая другая инфраструктура MVVM.

...