【问题标题】:Avoiding usage of Enums for abstraction and dependency injection, struggling at concept避免使用枚举进行抽象和依赖注入,在概念上苦苦挣扎
【发布时间】:2017-11-23 01:17:08
【问题描述】:

所以现在我正在尝试设计一个新的招聘计划,该计划授予对活动目录组的访问权限,生成包含他们的信息和位置的文档。

现在我正在使用枚举执行此操作,使用 switch 语句设置 ViewModel 上的详细信息,如下所示:

                case CaneRidgeSettings.Departments.SCSC:
                Model.ScannerFolder = @"scan1\Supply Chain Service Center\" + Model.UserId;
                Model.ExtensionRanges = "list station 8000 to-ext 8349";
                Model.AdministrativeAssistant = Loader.SCSCAdminAssistant;
                Model.DuoCode = "Franklin TN - 8175";
                Model.PrinterSelectedIndex = (int)CaneRidgeSettings.PrinterGroups.Cane_Ridge_5th_Floor_West;
                return await find.FindNextComputer("800SCSC");

我对这个设计的问题是,如果我向这座大楼添加更多部门,我必须手动更新这个开关。所以我尝试了一些关于这个的东西,比如字典,但它似乎并没有很好地绑定到组合框(即使在实现我自己的 INotifyCollectionChanged 时)。

所以我创建了一个包含这些信息的界面,为了简单起见,我们只说界面是这样做的:

  public interface IDepartmentInfo
{
    string DepartmentName { get; }
    List<string> ActiveDirectoryGroups { get; }
    string AdministrativeAssistant { get; }
    string Floor { get; }
}

然后我创建了一个实现这个接口的新类

public class SCSC : IDepartmentInfo
{
    public string DepartmentName { get; } = "Shared Services";
    public List<string> ActiveDirectoryGroups { get; } = new List<string>() {"Example_AD_GRP","Domain_Users"};
    public string AdministrativeAssistant { get; } = "Lisa_Smith@outlook.com";
    public string Floor { get; } = "5th Floor East";

    public override string ToString() => DepartmentName;
}

然后,在我的主要建筑类中,我有一个可观察的集合,它需要一个 IDepartmentInfo 并初始化这些部门

   public class CaneRidgeBuilding : IBuilding
{
    public ObservableCollection<IDepartmentInfo> Departments { get; set; } = new ObservableCollection<IDepartmentInfo>() {new SCSC(), new ARS()};

    public override string ToString()
    {
        return "CaneRidge";
    }
}

在我的视图模型上,我实现了一些属性,主要是 BuildingSelectedIndex 和 DepartmentSelectedIndex。

我还有一个 IDepartmentInfo 属性,它会在更改时通知它,因为它是数据绑定到我 UI 上的多个标签的。

public class MainWindowViewModel : BindableBase
{

    public ObservableCollection<IBuilding> Buildings { get; set; } = new ObservableCollection<IBuilding>() { new CaneRidgeBuilding() };
    private ObservableCollection<IDepartmentInfo> _departmentInfos =  new ObservableCollection<IDepartmentInfo>();
    public ObservableCollection<IDepartmentInfo> DepartmentInfos
    {
        get { return _departmentInfos; }
        set { SetProperty(ref _departmentInfos, value); }
    }

    private int _buildingIndex = -1;
    public int BuildingIndex
    {
        get { return _buildingIndex; }
        set
        {
            SetProperty(ref _buildingIndex, value);
            SetDepartments();
        }
    }

    private void SetDepartments()
    {
        if (BuildingIndex != -1)
            DepartmentInfos = Buildings[BuildingIndex].Departments;
    }


    private int _departmentIndex = -1;
    public int DepartmentIndex
    {
        get { return _departmentIndex; }
        set
        {
            SetProperty(ref _departmentIndex, value);
            LoadDepartmentSettings();
        }
    }


    private IDepartmentInfo _departmentInformation;
    public IDepartmentInfo DepartmentInformation
    {
        get { return _departmentInformation; }
        set { SetProperty(ref _departmentInformation, value); }
    }

    private void LoadDepartmentSettings()
    {
        if (DepartmentIndex != -1)
            DepartmentInformation = DepartmentInfos[DepartmentIndex];
    }

    private string _title = "Prism Application";
    public string Title
    {
        get { return _title; }
        set { SetProperty(ref _title, value); }
    }

    public MainWindowViewModel()
    {

    }
}

它完全按照我想要的方式工作,但是我现在遇到的问题是我将如何处理依赖注入?如果我有 10 个部门实施 IDepartmentInfo,我究竟如何将它传递给可观察的集合?

因为在我引入新建筑的那一刻,如果我告诉 Unity 解析所有 IDepartmentInfos,我会得到每个部门,即使它不属于 CaneRidge。

如果我将部门拆分到每个建筑物,那么我会遇到无法轻松将部门加载到 ViewModel 的问题,因为它需要一个 IDepartmentInfo 集合。如果我将它限制为仅一种类型的集合,那么它就行不通了。

我是不是把事情复杂化了?

【问题讨论】:

  • 我不熟悉如何将 unity 连接到 WPF,但您可以在服务注册时使用反射在所有引用的程序集中查找 IDepartmentInfo 的所有后代,并注册它们。跨度>

标签: c# wpf enums


【解决方案1】:

我能够弄清楚如何注入不同的建筑物和部门,可能不是最好的方法

编辑:更新它以使用反射以减少维护

    protected override void ConfigureContainer()
    {
        base.ConfigureContainer();
        Container.RegisterTypes(AllClasses.FromLoadedAssemblies()
           .Where(type => typeof(IDepartment).IsAssignableFrom(type)), WithMappings.FromAllInterfaces, WithName.TypeName, WithLifetime.None);

        ObservableCollection<IBuilding> Buildings = new ObservableCollection<IBuilding>()
        {
            Container.Resolve<Building1>(new ParameterOverride("departments",GetDepartmentCollection("Building1"))),
            Container.Resolve<Building2>(new ParameterOverride("departments",GetDepartmentCollection("Building2")))

        };

        Container.RegisterInstance(typeof(ObservableCollection<IBuilding>), Buildings,
            new ExternallyControlledLifetimeManager());



    }


    private ObservableCollection<IDepartment> GetDepartmentCollection(string buildingName)
    {
        var departments = new List<IDepartment>();
        foreach (var registration in Container.Registrations.Where( s => s.MappedToType.Namespace.Contains(buildingName)))
        {
            departments.Add((IDepartment)Container.Resolve(registration.MappedToType));
        }
        return new ObservableCollection<IDepartment>(departments);
    }

现在我能够完全消除枚举,并且可以在将来扩展它而无需破坏任何代码或要求我进行任何更改。

【讨论】:

  • 在我看来,这不是一个好的解决方案。你刚刚把你的问题转移到了另一个层面。现在,如果有新建筑物或其他东西,您将不得不更改ConfigureContainer 方法。太丑了!更改数据会导致基础架构代码发生变化——维护是一场噩梦!
  • 我不确定这个问题,这只是初始根级别注册。如果我添加新的建筑物或部门,我不需要更新容器,因为它可以使用装饰器模式进行扩展。或者,我可以使用反射来扫描命名空间中的类型并解析所有实现该接口的类。无论如何,我不必依赖对较低级别模块的依赖,而只有一个地方可以担心我的依赖,并且与我现在正在做的枚举相比,它使维护变得更容易。
  • @Ambdex 您编辑的解决方案的代码(和反射)比我建议的要少(如果您要注册所有实现 IDepartmentInfo 的类,您可能会失去我的一些反射)。不利的一面是,您必须遵循部门位于建筑物命名空间下的约定,并且您在启动时为所有建筑物实例化所有部门。
  • @djomlastic 我的目标是让非程序员能够轻松更新项目。从技术上讲,我不是开发人员,现在在 IT 部门工作。我现在自学了大约5个月。最终,我可能会考虑调换到开发人员的职位,当这种情况发生时,我为自动化新员工所做的实用程序,非程序员需要知道如何改变事情。我认为这样做会很容易,因为他们只需要更改属性而不是实际代码。
【解决方案2】:

这是一个想法。

自定义属性

引入BuilingAttribute,这样每个IDepartmentInfo 实现都可以声明它所属建筑物的Type(如果一个部门可以属于多个建筑物,则允许多个,我知道它不能)。

[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
public class BuildingAttribute : Attribute
{
    public Type BuildingType { get; private set; }

    public BuildingAttribute(Type buildingType)
    {
        this.BuildingType = buildingType;
    }
}

部门信息收集工厂

知道如何为每个建筑物Type 创建DepartmentInfo 集合的界面。

public interface IDepartmentInfoCollectionFactory
{
    void RegisterDepartment<T>(Func<IDepartmentInfo> departmentCreator) where T : class, IBuilding;

    ObservableCollection<IDepartmentInfo> GetDepartments<T>() where T : class, IBuilding;
}

并且实现(将被注册为单例)。

public class DepartmentInfoCollectionFactory : IDepartmentInfoCollectionFactory
{
    private readonly Dictionary<Type, List<Func<IDepartmentInfo>>> departmentCreators =
        new Dictionary<Type, List<Func<IDepartmentInfo>>>();

    void IDepartmentInfoCollectionFactory.RegisterDepartment<T>(Func<IDepartmentInfo> departmentCreator)
    {
        Type buildingType = typeof(T);

        if (!this.departmentCreators.ContainsKey(buildingType))
            this.departmentCreators.Add(buildingType, new List<Func<IDepartmentInfo>>());

        if (!this.departmentCreators[buildingType].Contains(departmentCreator))
            this.departmentCreators[buildingType].Add(departmentCreator);
    }

    ObservableCollection<IDepartmentInfo> IDepartmentInfoCollectionFactory.GetDepartments<T>()
    {
        Type buildingType = typeof(T);

        if (!this.departmentCreators.ContainsKey(buildingType))
            throw new InvalidOperationException(
                string.Format("No departments have been registered for {0}.", buildingType.ToString()));

        ObservableCollection<IDepartmentInfo> departmentInfos = new ObservableCollection<IDepartmentInfo>();

        foreach(Func<IDepartmentInfo> creator in this.departmentCreators[buildingType])
        {
            departmentInfos.Add(creator());
        }

        return departmentInfos;
    }
}

配置工厂,使其知道如何创建IDepartmentInfo 集合。

protected override void ConfigureContainer()
{
    Container.RegisterType<IDepartmentInfoCollectionFactory, DepartmentInfoCollectionFactory>(
            new ContainerControlledLifetimeManager());

    this.ConfigureDepartmentInfoCollectionFactory(Container.Resolve<IDepartmentInfoCollectionFactory>());
}

private void ConfigureDepartmentInfoCollectionFactory(IDepartmentInfoCollectionFactory factory)
{
    // Types implementing IDepartmentInfo
    var deptInfoTypes = AppDomain.CurrentDomain
                                    .GetAssemblies()
                                    .SelectMany(s => s.GetTypes())
                                    .Where(t => typeof(IDepartmentInfo).IsAssignableFrom(t)  && !t.IsInterface);

    foreach(Type type in deptInfoTypes)
    {
        // Get collection of BuildingAttribute for the type
        var buildingAttributes = type.GetCustomAttributes(typeof(BuildingAttribute), false)
                                        .OfType<BuildingAttribute>();

        if (buildingAttributes.Count() < 1)
            throw new InvalidOperationException(
                string.Format("The type {0} didn't declare BuildingArgument.", type.ToString()));

        var buildingType = buildingAttributes.First().BuildingType;

        if (buildingType == null || !buildingType.GetInterfaces().Contains(typeof(IBuilding)))
            throw new InvalidOperationException(
                string.Format("{0}: BuildingType is not an IBuilding.", type.ToString()));

        var registerMethod = typeof(IDepartmentInfoCollectionFactory).GetMethod("RegisterDepartment")
                                                                .MakeGenericMethod(new Type[] { buildingType });

        registerMethod.Invoke(factory, new object[]
        {
            new Func<IDepartmentInfo>(() => (IDepartmentInfo)Container.Resolve(type))
        });
    }
}

注入工厂。

public class FooBuilding : IBuilding
{
    private IDepartmentInfoCollectionFactory factory;
    private readonly ObservableCollection<IDepartmentInfo> departmentInfos;

    public string Name { get; } = "FooBuilding";

    public ObservableCollection<IDepartmentInfo> DepartmentInfos
    {
        get { return this.departmentInfos; }
    }

    public FooBuilding(IDepartmentInfoCollectionFactory factory)
    {
        this.factory = factory;
        this.departmentInfos = factory.GetDepartments<FooBuilding>();
    }
}

添加新部门

它不需要任何编辑,只需创建具有该属性的新类。

[Building(typeof(FooBuilding))]
public class BarDepartment : IDepartmentInfo
{
    public string Name { get; } = "Bar department";
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-05-11
    • 2013-02-20
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多