存储库模式中方法的命名约定是什么?

8
我正在尝试学习更好的软件设计,最近发现了存储库和服务层模式。据我所知,存储库基本上包含数据访问代码,服务层调用存储库获取数据,然后对该数据执行一些逻辑和处理。
到目前为止,从对这些模式的阅读中可以得出,通常没有一组固定的方法来定义存储库。但是,存储库通常具有以下方法:
- 列出 / 获取 / 读取 等。 - 创建 - 保存 - 更新 - 删除
我想理解存储库的命名约定。"List/Get/Read"等方法应该如何命名?我会给一个例子。
我目前正在开发一个从一堆目录和文件中读取内容的项目。这些文件代表由完全独立且已经存在的系统生成的传感器读数。
方法名称应特定于该特定类型的存储库,还是应采用更为通用的名称?例如:
通用名称:
interface ISensorRepository
{
    IEnumerable<Sensor> GetAll(); /  IEnumerable<Sensor> ListAll(); / etc.
    Sensor GetByID(int id);
}

实体特定名称:

interface ISensorRepository
{
    IEnumerable<Sensor> GetAllSensors(); / IEnumerable<Sensor> ListAllSensors(); / etc.
    Sensor GetSensorByID(int id);
}
3个回答

7

我会选择提问者的第一个选项,即“通用命名”。

为什么?这是为了避免“smurf命名约定”(它是一种编程术语),当你不停地重复自己时就会使用它。

你已经为存储库表示的实体类型命名了名称,为什么要费力地重复呢?如果你坚持单一责任原则,你就不必担心你的抽象层泄漏。

以下是我试图避免的内容:

Employee
- EmployeeID
- EmployeeFirstName
- EmployeeLastName
- EmployeeAddress
- EmployeeNumber
- EmployeeAge
- EmployeeEmail
- EmployeeHiringDate
- EmployeeThis
- EmployeeThat
- EmployeeBlabla
- Employee...
- Employee...

这有点疯狂,对吧?


5

在POEAA中,Martin Fowler将Repository定义为域层和数据映射层之间的中介,并且它像一个内存中的域对象集合。

由于它应该像一个内存中的集合一样运作,我喜欢按照以下方式来命名我的方法。

public interface IRepository<T> where T : class {
    T Create(T entity);

    void Create(IList<T> entities);

    T Update(T entity);

    T FirstOrDefault(Expression<Func<T, bool>> clause);

    IEnumerable<T> Where(Expression<Func<T, bool>> clause);

    IEnumerable<TResult> Select<TResult>(Expression<Func<T, TResult>> selector);

    T First();

    IEnumerable<T> All();
}

请参见http://martinfowler.com/eaaCatalog/repository.html,了解有关此设计模式的简短讨论。

3
避免返回List<T>。如果消费者在列表中添加了内容会发生什么?这是否会导致数据库更新?如果两个不同的消费者同时向列表中添加或删除内容,他们会互相影响吗?我不知道该期望什么。请使用最适合您需求的最通用类型,即IEnumerableIQueryable - JounceCracklePop
5
不要创建通用存储库。这是不良设计。 - Phill
@Phill 能否详细说明一下为什么? - 3dd
3
因为你对领域(代码设计领域)做了一些假设,所以你强制实现了“获取/全部获取/更新”等操作,但是这些操作在该领域中可能并不需要。这是糟糕的设计。 - Phill
@DDiVita - 首先,我根本不使用存储库,因为当您使用抽象ADO.NET的框架时,这是一个不必要的抽象。但是,如果我要使用存储库,我只会实现一个抽象基础存储库,其中包含一些受保护的帮助程序,然后是特定于域的接口,并注册所有实现基础的接口。然后,所有存储库仅实现它们所需的功能。 - Phill
显示剩余8条评论

0

考虑消费者可能会命名什么存储库变量。我会写一些类似于:

public void DoSomethingWithSensors(ISensorRepository sensors)
{
    //Which looks better?
    foreach (var sensor in sensors.All)
    //Or:
    foreach (var sensor in sensors.GetAllSensors())
}

我通常喜欢尽可能避免冗余。人们不太可能忘记他们正在调用哪个存储库上的All,所以我会将其命名为此,并为其他操作使用类似的通用名称。这也使您可以将基本界面抽象为一个通用类型:

public interface IRepository<T>
{
    IQueryable<T> All { get; }
    void Update(T entity);
    T Get(int id); //If you have a standard type for IDs
    //etc.
 }

我会避免像(不是针对3dd的好回答)这样的方法:

T FirstOrDefault(Expression<Func<T, bool>> clause);
List<TResult> Select<TResult>(Expression<Func<T, TResult>> selector);

如果您选择这条路线,您可能会发现自己很快就要在接口中声明所有的LINQ。只需公开一个>并依赖于LINQ提供所有这些选项。如果您正在使用EntityFramework,则应坚持使用而不是。
另一个考虑因素是,这种模式很可能会违反接口隔离原则。许多消费者只会获取IRepository实例来查询,而不是写入。是否值得将其拆分为两个接口以解决此问题的增加复杂性取决于您的软件预期生命周期以及其他系统是否有可能依赖于您的代码。

谢谢您的回答,它提出了一些很好的观点。您能详细介绍一下接口隔离原则以及这个模式如何打破它吗?您是指我应该有两个接口,一个用于读取数据,另一个用于写入数据吗? - user9993
1
不要创建通用存储库。那是糟糕的设计。 - Phill
接口隔离原则指出,如果我使用你的接口(例如将其作为方法参数请求),那么逻辑上应该遵循我将使用接口公开的每个功能。如果我只想要一个功能,但是你的接口有三个功能,那么我的代码就被迫“依赖”这另外两个功能。如果你更改了Create逻辑并且影响了IRepository,那么每个仅用于读取接口的消费者都需要重新编译。这不是什么大问题,除非你正在设计一个框架,但你确实说过一些传感器是只读的,所以请考虑一下。 - JounceCracklePop

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接