Шаблон для создания нового объекта с типом, который зависит от типа другого объекта? - PullRequest
0 голосов
/ 08 июля 2019

У меня есть что-то вроде этого:

interface IProduct { }

class ProductA : IProduct { }

class ProductB : IProduct { }

....

interface IViewModel { }

class ProductAViewModel : IViewModel { }

class ProductBViewModel : IViewModel { }

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

IProduct prod = new ProductA();
IViewModel vm = someFactoryOrBuilderObject.CreateViewModel(prod);
// real type of vm depends on the product's type
// if prod is ProductA, vm must be ProductAViewModel
...
DisplayViewModel(VM); // working with VM as IViewModel (regardless of it's real type)

Цели:

  • Тип ViewModel должен быть разрешен во время выполнения.

  • Добавление новых продуктов и моделей представления в мое приложение НЕ должно требовать каких-либо изменений в существующем коде.

Какой лучший способ реализовать эту функцию? Есть несколько очевидных способов, но они не удовлетворяют:

  • Создание чего-то вроде карты (словаря) типов (productType <-> vmType). Но когда я захочу добавить еще продуктов, мне придется изменить эту карту.
  • Создание фабричного метода, такого как GetViewModel (), внутри каждого класса продукта. Но это безумно нарушает SRP СОЛИДА.
  • Создание фабрики или строителя. Но эти шаблоны не соответствуют моим требованиям.

Ответы [ 2 ]

1 голос
/ 08 июля 2019

Предупреждение. Возможно, это не очень хороший шаблон проектирования.

На практике цепочка зависимостей должна быть View => ViewModel => Model (=> означает «зависит от»). Model не должен ничего знать о ViewModel (то есть через атрибуты, в коде ниже).

Ответ, приведенный ниже, является всего лишь приспособлением для того, что вы просили в вопросе.


Я не думаю, что есть действительно "чистые" способы, которыми вы можете сделать это без использования рефлексии. Тем не менее, мы можем сделать оставшуюся часть кода чистой, используя отражение (цена которого заключается в использовании отражения :)).

Теперь я предлагаю решение с использованием атрибутов.

Помните: этот метод работает, только если у вас есть открытый конструктор для каждого ViewModel класса, который принимает один Product объект в качестве параметра! Если нет, решение Варуна было бы лучше (вам нужен функция отображения в любом случае в этом случае).

using System;
using System.Diagnostics;
using System.Linq;

[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
sealed class UseViewModelAttribute : Attribute
{
    public Type ViewModelType { get; }
    public UseViewModelAttribute(Type viewModelType)
    {
        Debug.Assert(typeof(IViewModel).IsAssignableFrom(viewModelType));
        ViewModelType = viewModelType;
    }
}

class ViewModelFactory
{
    private readonly static Type[] ctorParams = new Type[] { typeof(IProduct) };
    public IViewModel CreateViewModel(IProduct product)
    {
        var vmType = (product.GetType().GetCustomAttributes(false).FirstOrDefault(attr
            => attr is UseViewModelAttribute) as UseViewModelAttribute)?.ViewModelType;
        Debug.Assert(!(vmType is null));
        return Activator.CreateInstance(vmType, product) as IViewModel;
    }
}

Чтобы использовать это, добавьте UseViewModel(typeof(ViewModelForThisProductType)) перед вашими Product классами. Как:

[UseViewModel(typeof(ProductAViewModel))]
class ProductA : IProduct { }

[UseViewModel(typeof(ProductBViewModel))]
class ProductB : IProduct { }

Ваши ViewModel классы должны выглядеть следующим образом:

class ProductAViewModel : IViewModel {
    //You can use both IProduct or the derived type (i.e ProductA) as the constructor param
    public ProductAViewModel(ProductA a)
    {

    }
}

class ProductBViewModel : IViewModel {
    public ProductBViewModel(IProduct b)
    {

    }
}
0 голосов
/ 08 июля 2019

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

IViewModel CreateViewModel(IProduct product)
{
    var vmType = Assembly.getTypes().SingleOrDefault(x => x.Name == $"{product.GetType().Name}ViewModel");

    // Create instance of vmType and perform mapping of properties between product and vm using reflection based on name convention. Automapper can do this for you as well.
    }

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