首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >仅通过服务访问但需要通过DI访问的内部存储库

仅通过服务访问但需要通过DI访问的内部存储库
EN

Software Engineering用户
提问于 2019-10-11 14:05:09
回答 4查看 156关注 0票数 2

我有一个库,可以访问我们的内容管理。它由一个public ContentService和一个internal ContentRepository组成。ContentService是公共的,通过我们的解决方案注入依赖。ContentRepository仅由ContentService使用。

我的问题是,我正在向ContentService传递3-4个参数,这些参数只用于在ContentService ctor中新建一个ContentRepository

我不喜欢这样是因为,

  1. 我正在向ContentService传递许多参数。
  2. ContentService的内聚性很低,因为我传递的是3-4个参数,这些参数在其他方法中没有使用,而且只在ctor中使用。
  3. 通过使用ContentService关键字创建ContentRepository,我在D17中创建了一个依赖项。

我想通过依赖将ConentRepository注入到ContentService来解决1-3。我唯一的问题是ContentRepositoryinternal,这是正确的,因为它只应该被ContentService使用(不想暴露它,我只想让这个APi的消费者使用ContentService)。

那么,我如何正确地解决1-3而不公开ContentRepository呢?

EN

回答 4

Software Engineering用户

发布于 2020-03-16 12:33:32

在面向对象编程中,类负责声明如何创建它。这意味着类的可访问性及其构造都在类访问修饰符的权限范围内。访问修饰符准确地决定了谁可以使用、引用和创建该类的实例。

你想要的是把这两种责任分开。您希望您的DI框架能够构造类,但不以任何其他方式使用它。实际上,您希望只为构造函数覆盖类的访问修饰符。

这不能直接完成,但是有一种设计模式专门用于外包类的构建:工厂模式。从本质上说,工厂的责任是围绕着您的类的构造器(S)来充当它的公共接口。

这意味着您可以在内部隐藏类,只要它的接口是公开的,并且它的工厂是可公开访问的。

代码语言:javascript
复制
public interface IProduct
{
    string  Name  { get; }
}

internal class Product : IProduct
{
    public string  Name  { get; set; }
}

public interface IProductFactory
{
    IProduct Create(string name);
}

public class ProductFactory : IProductFactory
{
    public IProduct Create(string name)
    {
        return new Product { Name = name };
    }
}

请注意,为了示例起见,我使用“产品”和“工厂”来很容易地区分两者。"Repository“是一个更适合您特定上下文的名称。

在DI设置中,您引用的是工厂,而不是产品。

代码语言:javascript
复制
serviceCollection.AddScoped();

如果您不想让您的服务依赖于产品工厂,而是希望它们继续直接使用产品,您仍然可以注册产品接口,但是可以依赖工厂实例化它:

代码语言:javascript
复制
serviceCollection.AddScoped();

serviceCollection.AddScoped(
    serviceProvider => serviceProvider.GetService().Create("foo")
);

您可以根据您的喜好/环境来改变这种方法。您提到了使用AddSingleton,这意味着您可以选择不在DI中注册工厂,而是在DI设置期间实例化工厂,并使用它创建产品(存储库):

代码语言:javascript
复制
var productFactory = new ProductFactory();

serviceCollection.AddSingleton(
    serviceProvider => productFactory.Create("foo")
);

你可以根据你认为最合适的东西来调整这个。

解决方案的核心是,具体的Product类型实际上不会在自己的程序集之外使用。只有ProductFactory引用它,因此它可以保持在内部。请注意,在上述所有示例中,您都不必引用具体的Product类型,因为工厂充当中间人,保护Product不受使用者(即带有DI注册的项目)的影响。

作为一个小插曲:

我正在向ContentService传递3-4个参数,这些参数只用于在ContentService ctor中新建一个ContentRepository

您已经使用了工厂模式,但是您已经将此责任推到了ContentService类上。你应该把它分成它自己的责任,即工厂,以避免违反SRP。

然而,这是一个很好的指导方针。您已经确切地知道了如何实现它。

票数 1
EN

Software Engineering用户

发布于 2019-10-12 03:54:49

,您应该从ContentService构造函数中移除这些3-4参数,只需传递已经构造的存储库,最好是在public接口后面。

如果您创建了类internal,那么您应该有一个very的好理由internal修饰符使您的生活更加艰难,因为它增加了代码的复杂性。您有private,意思是“没有其他人知道它”,public的意思是“其他人都知道它”,internal的意思是“只有少数人知道它”(但您选择的不多,只有your拥有少数)。

您希望API的使用者只使用ContentService 和<#>而不是 ContentRepository。这还不足以成为类internal (如果您不想让构造函数private实例化的话,那么始终可以创建构造函数D9)。

虽然您可以使用各种“技巧”来简单地“隐藏”ContentRepository,但我要说的是我头上第一件事:

代码语言:javascript
复制
public class ContentServiceFactory
{
    public ContentService CreateContentService(/* the 3 parameters */)
    {
        ContentRepository repo = new ContentRepository(/* the 3 parameters */);
        return new ContentService(repo);
    }
}

如果该类位于ContentRepository-containing程序集中,则ContentRepository将以internal形式可见。DI项目只看到ContentServiceFactory,没有内部ContentRepository类。但是可以很容易地创建一个ContentService实例。

这看起来像您的初始解决方案吗?,因为它。你所要求的是:

那么,我如何正确地解决1-3而不公开ContentRepository呢?

1-3意味着您希望<>创建和<>注入类的实例。除非您将其隐藏在另一个类后面(但为什么会遇到这种麻烦?),否则无法直接<>创建一个类的实例,该实例是另一个程序集的internal (反射显然是不可能的)。在DI容器中注册类型有一个明显的先决条件,即类型是可见的,这就引出了.

解决方案

如果出于某种原因,您已经创建了ContentRepositoryinternal,使其对任何其他程序集都不可见,则but希望创建一个异常,您可以始终使用InternalsVisibleTo属性

票数 0
EN

Software Engineering用户

发布于 2019-10-12 08:55:46

拥有内部依赖是一个很好的实践,这将减少受影响的范围,以防您进行更改。因为类是内部的,所以您可以自由地进行更改,而不必担心更改会阻止一些您不知道的事情。

您没有提到您正在使用的依赖注入容器,但是使用内置的ASP.NET核心实现,您可以在注册服务和存储库类的项目中为服务集合创建扩展方法。扩展方法将访问内部依赖项,并且您可以注册任何内部实现,而不需要使用public或使用Microsoft的hack作为InternalsVisibleTo

代码语言:javascript
复制
public static IServiceCollection AddContentService(this IServiceCollection services)
{
    // Conditionally you can register others 3-4 dependencies here
    services.AddTransient();

    services.AddTransient();

    return services;
}

在注册应用程序依赖项的根项目中,您可以简单地调用

代码语言:javascript
复制
services.AddContentService();
票数 0
EN
页面原文内容由Software Engineering提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://softwareengineering.stackexchange.com/questions/399597

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档