Как я могу эмулировать множественное наследование и использовать рефлексию для оптимизации этого кода? - PullRequest
2 голосов
/ 22 июня 2009

У меня есть приложение WPF, где PageItems являются объектами модели.

Моя основная ViewModel имеет ObservableCollection PageItemViewModels , каждая из которых строит себя из соответствующего объекта модели PageItem.

Каждый PageItemViewModel наследует от абстрактного класса BaseViewModel , чтобы получить функциональность INotifyPropertyChanged.

Каждый PageItemViewModel также реализует IPageItemViewModel , чтобы убедиться, что он обладает необходимыми свойствами.

В конечном итоге у меня будет около 50 страниц, поэтому я хочу устранить любой ненужный код :

  • решено (см. Ниже) : есть ли способ, которым я могу получить классы PageItemViewModel для наследования IdCode и Title , поэтому мне не нужно реализовывать их в каждом классе? Я не могу поместить их в BaseViewModel, поскольку другие ViewModel наследуют его, которым не нужны эти свойства, и я не могу поместить их в IPageItemViewModel, поскольку это всего лишь интерфейс. Я понимаю, что мне нужно множественное наследование для этого, которое C # не поддерживает
  • решено (см. Ниже) : есть ли способ избавиться от оператора switch , например, использовать вместо этого отражение ?

Ниже приведено отдельное консольное приложение , которое демонстрирует код, который у меня есть в моем WPF приложении:

using System.Collections.Generic;

namespace TestInstantiate838
{
    public class Program
    {
        static void Main(string[] args)
        {
            List<PageItem> pageItems = PageItems.GetAll();
            List<ViewModelBase> pageItemViewModels = new List<ViewModelBase>();

            foreach (PageItem pageItem in pageItems)
            {
                switch (pageItem.IdCode)
                {
                    case "manageCustomers":
                        pageItemViewModels.Add(new PageItemManageCustomersViewModel(pageItem));
                        break;
                    case "manageEmployees":
                        pageItemViewModels.Add(new PageItemManageEmployeesViewModel(pageItem));
                        break;
                    default:
                        break;
                }
            }
        }
    }

    public class PageItemManageCustomersViewModel : ViewModelBase, IPageItemViewModel
    {
        public string IdCode { get; set; }
        public string Title { get; set; }

        public PageItemManageCustomersViewModel(PageItem pageItem)
        {

        }
    }

    public class PageItemManageEmployeesViewModel : ViewModelBase, IPageItemViewModel
    {
        public string IdCode { get; set; }
        public string Title { get; set; }

        public PageItemManageEmployeesViewModel(PageItem pageItem)
        {

        }
    }

    public interface IPageItemViewModel
    {
        //these are the properties which every PageItemViewModel needs
        string IdCode { get; set; }
        string Title { get; set; }
    }

    public abstract class ViewModelBase
    {
        protected void OnPropertyChanged(string propertyName)
        {
            //this is the INotifyPropertyChanged method which all ViewModels need
        }
    }

    public class PageItem
    {
        public string IdCode { get; set; }
        public string Title { get; set; }
    }

    public class PageItems
    {
        public static List<PageItem> GetAll()
        {
            List<PageItem> pageItems = new List<PageItem>();
            pageItems.Add(new PageItem { IdCode = "manageCustomers", Title = "ManageCustomers"});
            pageItems.Add(new PageItem { IdCode = "manageEmployees", Title = "ManageEmployees"});
            return pageItems;
        }
    }

}

Исправлено: интерфейс изменен на абстрактный класс

using System;
using System.Collections.Generic;

namespace TestInstantiate838
{
    public class Program
    {
        static void Main(string[] args)
        {
            List<PageItem> pageItems = PageItems.GetAll();
            List<ViewModelPageItemBase> pageItemViewModels = new List<ViewModelPageItemBase>();

            foreach (PageItem pageItem in pageItems)
            {
                switch (pageItem.IdCode)
                {
                    case "manageCustomers":
                        pageItemViewModels.Add(new PageItemManageCustomersViewModel(pageItem));
                        break;
                    case "manageEmployees":
                        pageItemViewModels.Add(new PageItemManageEmployeesViewModel(pageItem));
                        break;
                    default:
                        break;
                }
            }

            foreach (ViewModelPageItemBase pageItemViewModel in pageItemViewModels)
            {
                System.Console.WriteLine("{0}:{1}", pageItemViewModel.IdCode, pageItemViewModel.Title);
            }
            Console.ReadLine();
        }
    }

    public class PageItemManageCustomersViewModel : ViewModelPageItemBase
    {
        public PageItemManageCustomersViewModel(PageItem pageItem)
        {
            IdCode = pageItem.IdCode;
            Title = pageItem.Title;
        }
    }

    public class PageItemManageEmployeesViewModel : ViewModelPageItemBase
    {
        public PageItemManageEmployeesViewModel(PageItem pageItem)
        {
            IdCode = pageItem.IdCode;
            Title = pageItem.Title;
        }
    }

    public abstract class ViewModelPageItemBase : ViewModelBase
    {
        //these are the properties which every PageItemViewModel needs
        public string IdCode { get; set; }
        public string Title { get; set; }
    }

    public abstract class ViewModelBase
    {
        protected void OnPropertyChanged(string propertyName)
        {
            //this is the INotifyPropertyChanged method which all ViewModels need
        }
    }

    public class PageItem
    {
        public string IdCode { get; set; }
        public string Title { get; set; }
    }

    public class PageItems
    {
        public static List<PageItem> GetAll()
        {
            List<PageItem> pageItems = new List<PageItem>();
            pageItems.Add(new PageItem { IdCode = "manageCustomers", Title = "ManageCustomers"});
            pageItems.Add(new PageItem { IdCode = "manageEmployees", Title = "ManageEmployees"});
            return pageItems;
        }
    }

}

Ответ на исключение оператора Switch:

Спасибо, Jab:

string assemblyName = System.Reflection.Assembly.GetExecutingAssembly().GetName().Name;
string viewModelName = assemblyName + ".ViewModels.PageItem" + StringHelpers.ForcePascalNotation(pageItem.IdCode) + "ViewModel";
var type = Type.GetType(viewModelName);
var viewModel = Activator.CreateInstance(type, pageItem) as ViewModelBase;
AllPageViewModels.Add(viewModel);

Ответы [ 5 ]

2 голосов
/ 22 июня 2009

Можете ли вы создать класс, который наследует от BaseViewModel, который будет реализовывать эти два свойства - ваши классы PageItemViewModel, которым это необходимо, могут потом наследовать от этого.

1 голос
/ 22 июня 2009

Одним из возможных решений является обратная связь между PageItem и PageItemViewModel в вашем коде. Прямо сейчас вы генерируете PageItemViewModel на основе PageItem, но что, если вы сначала создали PageItemViewModel s, а затем в конструкторе каждого PageItemViewModel, вы создали соответствующий PageItem? Это устраняет необходимость в switch и делает вещи чище, потому что теперь ваша модель представления отвечает за модель, а не модель отвечает за модель представления.

Пример на основе вашего текущего кода:

using System;
using System.Collections.Generic;

namespace TestInstantiate838
{
    public class Program
    {
        static void Main(string[] args)
        {
            List<ViewModelPageItemBase> pageItemViewModels = PageItemViewModels.GetAll();

            // No switch needed anymore. Each PageItem's view-model contains its PageItem
            // which is exposed as property of the view-model.
            foreach (ViewModelPageItemBase pageItemViewModel in pageItemViewModels)
            {
                System.Console.WriteLine("{0}:{1}", pageItemViewModel.PageItem.IdCode, pageItemViewModel.PageItem.Title);
            }
            Console.ReadLine();
        }
    }

    public class PageItemManageCustomersViewModel : ViewModelPageItemBase
    {
        public PageItemManageCustomersViewModel()
        {
            PageItem = new PageItem { IdCode = "manageCustomers", Title = "ManageCustomers" };
        }
    }

    public class PageItemManageEmployeesViewModel : ViewModelPageItemBase
    {
        public PageItemManageEmployeesViewModel()
        {
            PageItem = new PageItem { IdCode = "manageEmployees", Title = "ManageEmployees" };
        }
    }

    public abstract class ViewModelPageItemBase : ViewModelBase
    {
        //The PageItem associated with this view-model
        public PageItem PageItem { get; protected set; }
    }

    public abstract class ViewModelBase
    {
        protected void OnPropertyChanged(string propertyName)
        {
            //this is the INotifyPropertyChanged method which all ViewModels need
        }
    }

    public class PageItem
    {
        public string IdCode { get; set; }
        public string Title { get; set; }
    }

    // Replaces PageItems class
    public class PageItemViewModels
    {
        // Return a list of PageItemViewModel's instead of PageItem's.
        // Each PageItemViewModel knows how to build it's corresponding PageItem object.
        public static List<PageItemViewModelBase> GetAll()
        {
            List<PageItemViewModelBase> pageItemViewModels = new List<PageItemViewModelBase>();
            pageItemViewModels.Add(new PageItemManageCustomersViewModel());
            pageItemViewModels.Add(new PageItemManageEmployeesViewModel());
            return pageItemViewModels;
        }
    }
} 
1 голос
/ 22 июня 2009

Одним из решений, которое не очень красиво, но работает, было бы использование соглашения, чтобы избавиться от оператора switch. Это предполагает, что вы можете изменить IdCodes или хотя бы изменить регистр, чтобы он соответствовал ViewModel.

    var type = Type.GetType("PageItem" + pageItem.IdCode + "ViewModel");
    var viewModel = Activator.CreateInstance(type) as ViewModelBase;
    pageItemViewModels.Add(viewModel);

Обратите внимание, что здесь вы должны добавить проверку ошибок, здесь есть пара ошибок. Однако это лучше, чем поддерживать постоянно растущий оператор switch.

1 голос
/ 22 июня 2009

Почему вы не можете поместить виртуальный метод GetViewModel() в базовый класс PageItem, который возвращает подходящую модель представления?

   foreach (PageItem pageItem in pageItems)
   {
       pageItemViewModels.Add(pageItem.GetViewModel());
   }

То, что сразу выглядит как запах кода, это использование свойств "id" - обычно его можно заменить полиморфизмом. Таким образом, вы должны заменить оператор switch на приведенный выше код.

Edit:

Если ваш класс PageItem ничего не знает о вашей модели представления, то он не может быть реализован таким образом. По сути, вам нужен завод, который у вас уже есть (в некотором смысле).

У меня обычно есть список отношений (PageItem к ViewModel), который в вашем случае будет Dictionary<String, Type>. Затем вы можете заполнить этот список во время инициализации и создать соответствующую модель представления позже.

Чтобы использовать отражение для построения этого списка, вам по крайней мере необходимо программно знать, какой элемент страницы поддерживается моделью представления. Для этого вы можете использовать собственный атрибут для украшения вашего класса, например ::

public class SupportsPageItemAttribute : Attribute
{
    private readonly string _id;
    public string ID
    {
        get { return _id;}
    }

    public SupportsPageItemAttribute(string id)
    {
        _id = id;
    }
}

А затем используйте этот атрибут, чтобы определить, какой PageItem может принять ваша модель:

[SupportsPageItemAttribute("manageCustomers")
public class PageItemManageCustomersViewModel
{
   // ...
}

И затем вы используете отражение, чтобы получить все классы, реализующие IPageItemViewModel, и проверить их атрибуты, чтобы получить строку идентификатора PageItem.

Например (без особой проверки ошибок):

Dictionary<String, Type> modelsById = new Dictionary<String, Type>();
String viewModelInterface = typeof(IPageItemViewModel).FullName;

// get the assembly
Assembly assembly = Assembly.GetAssembly(typeof(IPageItemViewModel));

// iterate through all types
foreach (Type viewModel in assembly.GetTypes())
{
    // get classes which implement IPageItemViewModel
    if (viewModel.GetInterface(viewModelInterface) != null)
    {
        // get the attribute we're interested in
        foreach (Attribute att in Attribute.GetCustomAttributes(viewModel))
        {
            if (att is SupportsPageItemAttribute)
            {
                // get the page item id
                String id = (att as SupportsPageItemAttribute).ID;

                // add to dictionary
                modelsById.Add(id, viewModel);
            }
        }
    }
}

С другой стороны, существуют различные структуры инверсии контроля, которые вы могли бы рассмотреть вместо того, чтобы самостоятельно выполнять неприятную работу по отражению.

1 голос
/ 22 июня 2009

Как предложил Пэдди, я только что создал дополнительный абстрактный класс, PageViewModelBase, с определением этих авто-реквизитов:

using System.Collections.Generic;

namespace TestInstantiate838
{
    public class Program
    {
        static void Main(string[] args)
        {
            List<PageItem> pageItems = PageItems.GetAll();
            List<ViewModelBase> pageItemViewModels = new List<ViewModelBase>();

            foreach (PageItem pageItem in pageItems)
            {
                switch (pageItem.IdCode)
                {
                    case "manageCustomers":
                        pageItemViewModels.Add(new PageItemManageCustomersViewModel(pageItem));
                        break;
                    case "manageEmployees":
                        pageItemViewModels.Add(new PageItemManageEmployeesViewModel(pageItem));
                        break;
                    default:
                        break;
                }
            }
        }
    }

    public class PageItemManageCustomersViewModel : PageViewModelBase
    {
        public PageItemManageCustomersViewModel(PageItem pageItem)
        {

        }
    }

    public class PageItemManageEmployeesViewModel : PageViewModelBase
    {
        public PageItemManageEmployeesViewModel(PageItem pageItem)
        {


        }
    }

    public abstract class ViewModelBase
    {
        protected void OnPropertyChanged(string propertyName)
        {
            //this is the INotifyPropertyChanged method which all ViewModels need
        }
    }

    public abstract class PageViewModelBase : ViewModelBase
    {
        //these are the properties which every PageItemViewModel needs
        public string IdCode { get; set; }
        public string Title { get; set; }
    }

    public class PageItem
    {
        public string IdCode { get; set; }
        public string Title { get; set; }
    }

    public class PageItems
    {
        public static List<PageItem> GetAll()
        {
            List<PageItem> pageItems = new List<PageItem>();
            pageItems.Add(new PageItem { IdCode = "manageCustomers", Title = "ManageCustomers"});
            pageItems.Add(new PageItem { IdCode = "manageEmployees", Title = "ManageEmployees"});
            return pageItems;
        }
    }

}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...