【问题标题】:Creational Pattern for simple types whose properties are retrieved from a database?从数据库中检索属性的简单类型的创建模式?
【发布时间】:2013-02-27 18:32:49
【问题描述】:

背景

就我自己的理解而言,我正在尝试为一个简单的场景创建一个分层架构。我有一个简单的域类 Car。

public class Car {

  public string Make;
  public string Model;
  public int Year;

  public Accelerate() { }
  public Decelerate() { }
}

我想创建一个 Car 的新实例,从数据库中填充它的值。 这就是我卡住的地方。

认为在持久层中应该有另一个类(称为 CarRepository),它将根据 CarID 从数据库中检索这些值。

我希望在运行时将这个 CarRepository 依赖注入到 Car 域类中,以允许不同的实现(例如用于单元测试的假货)。

所以,我考虑将其添加到 Car 类中:

public class Car() {

  private ICarRespository _carRepository;

  public Car(ICarRepository carRepository) {
    _carRespository = carRepository;
  }

  public void Find(int carId) {
    var car = _carRepository.FindById(carId);
    this.Make = car.Make;
    this.Model = car.Model;
    // etc.
  }

  // other properties and methods here
}

我会将 ICarRepository 定义为:

public interface ICarRepository {
  DtoCar FindById(int carId);
}

public class DtoCar {
  public string Make;
  public string Model;
  public int Year;
}

假设我正在使用“穷人的依赖注入”并允许聚合根根据项目类型(生产代码与单元测试)传递正确的具体 CarRespository。显然我在这里选择使用构造函数注入。

问题

(1) 这个解决方案感觉不对。当我创建一辆新车时,我觉得它应该在此时初始化并填充其所有属性。但是,只有在我调用 Find() 方法之后,它才能真正成为一辆有效的汽车。如果我没有将有效的 CarID 传递给 Find,会发生什么?那么 Car 对象永远不会是一个有效的对象!这不应该打扰我吗?有没有更好的办法?

(2) 我正在使用构造函数注入来传递存储库依赖项……我也可以传递 CarID 以在构造函数中找到此处……然后我可以消除 Find() 方法吗?所以:

public Car(ICarRepository carRepository, int carId) {
  _carRespository = carRepository;
  var car = _carRepository.FindById(carId);

  this.Make = car.Make;
  this.Model = car.Model;
  // etc.
}

...我从来没有见过这样的做法,所以我很犹豫要不要使用它。这样可以吗?

(3) 我在这里缺少一些创造模式吗?这是一个简单的场景……我不相信像 Factory、Singleton 或 Prototype 这样的东西在这里适用(我可能弄错了)。那些感觉沉重和专业。这个场景——一个来自数据库的简单领域对象——看起来很基础,必须有一些指导。

【问题讨论】:

    标签: c# design-patterns n-tier-architecture


    【解决方案1】:

    您的问题是您没有关注Single Responsibility Principle。您应该将汽车的创建工作交给专门从事此工作的服务。

    class CarService
    {
        CarService(ICarRepository carRepo)
        { 
            _carRepo = carRepo;
        }
    
        Car GetById(int id)
        {
            var carDTO = _carRepo.GetById(id);       
            var car = Mapper.Map<CarDTO, Car>(carDTO);
            return car;
        }
    }
    

    *这里是AutoMapper的链接

    **你甚至不需要CarDTO,但是如果需要它会添加一个抽象层。

    这就像一个工厂,但实现更有意义,并且不像您担心的那样繁重。代码将更像这样:

    var car = carService.GetById(id);
    

    我相信这应该可以解决你所有的问题。

    【讨论】:

    • 太棒了。这个 ___Service 类是否有“模式名称”?只是在寻找可以进一步研究的东西。谢谢!
    • 正如我所说,这更像是工厂模式。它只是 N 层应用程序的一部分。这是存储库模式服务层的谷歌上的 SO:stackoverflow.com/questions/5645013/…stackoverflow.com/questions/5049363/… 另外,如果您正在寻找它,请不要忘记将其标记为答案:)
    • @Justing Pihony ... 获奖,感谢您的洞察力!所以,这个 CarService。如果你把这个类放在一个层中,并且每一层都在它自己的项目/程序集/包/不管,你会把它放在领域层吗?还是“服务”层(Microsoft 的应用程序架构指南称之为“应用程序外观”)?我问是因为我理解他们的“应用程序外观”/服务层包含厚实的方法,您可以在其中将功能从几个领域层类方法编排成提供给表示层的功能。
    • 本例中的服务层是领域层。尝试实现任何 DDD 甚至 DDD-lite 时要非常小心。通常,它们用于不需要它们的项目中。只是我的两分钱。在不知道您的具体情况的情况下,我不能肯定地说任何话,但这只是过度设计时的警告
    【解决方案2】:

    Car 类的设计违反了Single responsibility principle:它是一个域实体,它包含数据持久化逻辑。

    所以,让Car 只是一个域实体(ID 属性已被引入):

    public class Car
    {
        public Car(int id, string make, string model, int year)
        {
            ID = id;
            Make = make;
            Model = model;
            Year = year;
        }
    
        public int ID { get; private set; }
        public string Make { get; private set; }
        public string Model { get; private set; }
        public int Year { get; private set; }
    
        public void Accelerate() { }
        public void Decelerate() { }
    }
    

    存储库接口不得返回数据传输对象:

    public interface ICarRepository
    {
        Car FindById(int carId); // No DTO here - DTO is the detail of implementation
    }
    

    然后,只需为汽车服务设计界面。服务层公开业务逻辑。在这个简单的例子中,服务层没有增加任何价值:

    public interface ICarService
    {
        Car FindById(int carId);
    }
    
    public class CarService : ICarService
    {
        private readonly ICarRepository _repository;
    
        public CarService(ICarRepository repository)
        {
            _repository = repository;
        }
    
        #region Implementation of ICarService
    
        public Car FindById(int carId)
        {
            var car = _repository.FindById(carId);
            // The additional business logic (if required) could be added here.
            return car;
        }
    
        #endregion
    }
    

    存储库的实际实现将使用其构造函数创建Car

    【讨论】:

    • 查看我的具有 SO 链接的 cmets。通常最好在存储库之上添加一层抽象
    • @JustinPihony,感谢您的评论。当然,一般情况下还是考虑服务层比较好(为简单起见,之前没有介绍过)。请查看更新。
    • @SergeyBrunov 我要感谢您的宝贵贡献和解释。非常感谢。
    猜你喜欢
    • 1970-01-01
    • 2014-04-04
    • 2013-07-21
    • 2011-12-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-03-02
    • 1970-01-01
    相关资源
    最近更新 更多