通过接口/工厂填充集合?

问题描述 投票:0回答:2

我正在使用一种模式,其中实现接口的具体 ViewModel 被传递到存储库,然后存储库填充 ViewModel 对象,但仅使用该接口。这使得存储库稍微重一些,但允许在不同的场景中重用存储库。例如,具体实现可以是 MVC ViewModel,也可以是实现接口的 asp.net Page,其中每个属性的 set 访问器实际上将值放入 GUI,例如文本框。接口的实现充当映射并消除了额外的复制步骤。广泛使用 AutoMapper,现在接触到这种模式,我更喜欢这个。

public interface IPerson
{
  int Id{set};
  string Name{set};
  string Address{set};
}

public class PersonRepository
{
   GetPerson(int id, IPerson person)
   {
      //query...    
      person.Id = result.Id;      
      person.Name = result.Name;
      person.Address = result.Address;    
   }
}

//...controller action
PersonViewModel person = new PersonViewModel();
rep.GetPerson(5, person);

棘手的部分来了。有时,ViewModel 需要一个项目集合,无论是用于索引页面还是下拉列表,或者显示一组嵌套的子对象。存储库无法实例化接口,因此我们提供它是一个工厂。在与协方差斗争了一段时间后,我放弃了公开任何类型的集合,最终得到了一种既创建又添加集合项的方法:

public interface IPerson
{
  //...
  IJobRole CreateAndAddJobRole();    
}

public class PersonViewModel:IPerson
{
  //collection not part of the interface
  ICollection<JobRoles> JobRoles {get;set;} //= new List<JobRoles> in constructor

  public CreateAndAddJobRole()
  {
    role = new JobRole();
    JobRoles.Add(role);
    return role;
  }
}

public class PersonRepository
{
   GetPerson(int id, IPerson person)
   {
      //...
      foreach(var result...)
      {
        IJobRole role = person.CreateAndAddJobRole();
        role.SomeProperty = //...
      }
   }
}

显然,我可能会让处理工作角色的存储库实际上是填充集合的存储库。我可能实际上有更细粒度的接口,以便不同的存储库负责填充它们处理的数据。 ViewModel 将简单地实现多个接口。也就是说,我意识到还有改进的空间,但我特意来这里是因为我没有任何处理收集问题的好主意。

这种设计的一个好处是没有暴露的集合可能被存储库滥用。永远不会猜测谁负责实例化集合本身,或者谁填充它,或者如果您只有一个 getter,存储库可能会获取集合并以无效的方式修改它。我认为这种情况很少发生,因为团队知道这种模式,但完全没有陷阱总是好的,而不是有陷阱而每个人都必须记住不要介入。

事实上,感觉有点脏。

当执行此操作的方法仅了解接口时,您将如何设计/公开实例化具体类型并将其添加到集合中的能力?

c# factory-pattern
2个回答
1
投票

听起来最好的选择是使每个接口通用,并传入集合的类型。例如:

public interface IPerson<TJob> where TJob : IJobRole
{
  ICollection<TJob> JobRoles {get;set;} 
  void AddJobRole(TJob role);
}

public JobRole : IJobRole
{
}

public class PersonViewModel:IPerson<JobRoles>
{
  //collection is now part of the interface
  ICollection<JobRoles> JobRoles //= new List<JobRoles> in constructor

  public void AddJobRole(JobRoles role)
  {
    JobRoles.Add(role);
  }
}

public class PersonRepository
{
   GetPerson(int id, IPerson<JobRoles> person)
   {
      //...
      foreach(var result...)
      {
        person.AddJobRole(new JobRole { 
            SomeProperty = //... 
            SomeOther = //...
        }
      }
   }
}

当然,这假设您在调用

IPerson<>
时知道想要哪种类型的
GetPerson()
。但是,如果您需要它来处理那里的任何
IPerson
,那么问题就更大了。


0
投票

重新审视这一点以及我和其他人在类似场景中所做的事情是利用泛型类型的

new()
约束来允许更深层次的代码实例化调用代码所请求的具体类型。

public interface IPerson<R> where R : IJobRole, new()
{
    IList<R> JobRoles { get; set; }
}

public class JobRole : IJobRole
{
    public string SomeProperty { get; set; }
    public int SomeOther { get; set; }
}

public interface IJobRole
{
    string SomeProperty { get; set; }
    int SomeOther { get; set; }
}

public class PersonViewModel : IPerson<JobRole>
{
    public IList<JobRole> JobRoles { get; set; } = new List<JobRole>();
}

public class PersonRepository
{
    public P GetPerson<P,R>(int departmentId, P person) 
        where R : IJobRole, new() where P: IPerson<R>//, new()
    {
        // Optionally instead of passing in the root view model, 
        // we could also instantiate it with new P()
        // var person = new P();

        List<PersonEntity> entities = db.GetPeople(departmentId);
        foreach (PersonEntity entity in entities)
        {
            // Use new R() to create a new instance of whatever concrete type the caller asked for
            person.JobRoles.Add(new R {
                SomeProperty = entity.Some,
                SomeOther = entity.Other
            });
        }
        return person;
    }
}

用法,调用代码在泛型类型参数中指定所需的具体类型:

PersonViewModel person = GetPerson<PersonViewModel, JobRole>(1, new PersonViewModel());
© www.soinside.com 2019 - 2024. All rights reserved.