模式来创建类型取决于另一个对象类型的新对象?

时间:2019-07-08 04:37:39

标签: c# design-patterns

我有这样的东西:

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的类型必须在运行时解析。

  • 向我的应用添加新产品和Viewmodel不需要更改现有代码。

哪种是实现此功能的最佳方法? 有一些显而易见的方法,但并不令人满意:

  • 创建类型(productType <-> vmType)的映射(字典)。但是当我想添加更多产品时,我将不得不更改此地图。
  • 在每个产品类中创建一个工厂方法,例如GetViewModel()。但这疯狂地违反了SOLID的SRP。
  • 创建工厂或建造者。但是这些模式不能满足我的要求。

2 个答案:

答案 0 :(得分:1)

警告:这可能不是遵循的良好设计模式。

在实践中,依赖关系链应为View => ViewModel => Model=>表示“依赖”)。 ModelViewModel一无所知(即通过属性,在下面的代码中)。

下面的答案仅是您在问题中所要求的内容。


我认为不使用反射就没有真正的“干净”方法。但是,我们可以使用反射使其余代码干净(其价格是使用反射:))。

我现在提出使用属性的解决方案。

请记住:仅当您为每个ViewModel类都具有一个Product对象作为参数的公共构造函数时,此方法才有效!否则,Varun的解决方案是更好(在这种情况下,您仍然需要一个映射功能)。

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)
    {

    }
}

答案 1 :(得分:0)

如果您只想在运行时执行此操作,那么我看到的唯一选择是按名称约定进行映射。在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.
    }