我有一个库,可以访问我们的内容管理。它由一个public ContentService和一个internal ContentRepository组成。ContentService是公共的,通过我们的解决方案注入依赖。ContentRepository仅由ContentService使用。
我的问题是,我正在向ContentService传递3-4个参数,这些参数只用于在ContentService ctor中新建一个ContentRepository。
我不喜欢这样是因为,
ContentService关键字创建ContentRepository,我在D17中创建了一个依赖项。我想通过依赖将ConentRepository注入到ContentService来解决1-3。我唯一的问题是ContentRepository是internal,这是正确的,因为它只应该被ContentService使用(不想暴露它,我只想让这个APi的消费者使用ContentService)。
那么,我如何正确地解决1-3而不公开ContentRepository呢?
发布于 2020-03-16 12:33:32
在面向对象编程中,类负责声明如何创建它。这意味着类的可访问性及其构造都在类访问修饰符的权限范围内。访问修饰符准确地决定了谁可以使用、引用和创建该类的实例。
你想要的是把这两种责任分开。您希望您的DI框架能够构造类,但不以任何其他方式使用它。实际上,您希望只为构造函数覆盖类的访问修饰符。
这不能直接完成,但是有一种设计模式专门用于外包类的构建:工厂模式。从本质上说,工厂的责任是围绕着您的类的构造器(S)来充当它的公共接口。
这意味着您可以在内部隐藏类,只要它的接口是公开的,并且它的工厂是可公开访问的。
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设置中,您引用的是工厂,而不是产品。
serviceCollection.AddScoped();如果您不想让您的服务依赖于产品工厂,而是希望它们继续直接使用产品,您仍然可以注册产品接口,但是可以依赖工厂实例化它:
serviceCollection.AddScoped();
serviceCollection.AddScoped(
serviceProvider => serviceProvider.GetService().Create("foo")
);您可以根据您的喜好/环境来改变这种方法。您提到了使用AddSingleton,这意味着您可以选择不在DI中注册工厂,而是在DI设置期间实例化工厂,并使用它创建产品(存储库):
var productFactory = new ProductFactory();
serviceCollection.AddSingleton(
serviceProvider => productFactory.Create("foo")
);你可以根据你认为最合适的东西来调整这个。
解决方案的核心是,具体的Product类型实际上不会在自己的程序集之外使用。只有ProductFactory引用它,因此它可以保持在内部。请注意,在上述所有示例中,您都不必引用具体的Product类型,因为工厂充当中间人,保护Product不受使用者(即带有DI注册的项目)的影响。
作为一个小插曲:
我正在向
ContentService传递3-4个参数,这些参数只用于在ContentServicector中新建一个ContentRepository。
您已经使用了工厂模式,但是您已经将此责任推到了ContentService类上。你应该把它分成它自己的责任,即工厂,以避免违反SRP。
然而,这是一个很好的指导方针。您已经确切地知道了如何实现它。
发布于 2019-10-12 03:54:49
是,您应该从ContentService构造函数中移除这些3-4参数,只需传递已经构造的存储库,最好是在public接口后面。
如果您创建了类internal,那么您应该有一个very的好理由。internal修饰符使您的生活更加艰难,因为它增加了代码的复杂性。您有private,意思是“没有其他人知道它”,public的意思是“其他人都知道它”,internal的意思是“只有少数人知道它”(但您选择的不多,只有your拥有少数)。
您希望API的使用者只使用ContentService 和<#>而不是 ContentRepository。这还不足以成为类internal (如果您不想让构造函数private实例化的话,那么始终可以创建构造函数D9)。
虽然您可以使用各种“技巧”来简单地“隐藏”ContentRepository,但我要说的是我头上第一件事:
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容器中注册类型有一个明显的先决条件,即类型是可见的,这就引出了.
如果出于某种原因,您已经创建了ContentRepository类internal,使其对任何其他程序集都不可见,则but希望创建一个异常,您可以始终使用InternalsVisibleTo属性。
发布于 2019-10-12 08:55:46
拥有内部依赖是一个很好的实践,这将减少受影响的范围,以防您进行更改。因为类是内部的,所以您可以自由地进行更改,而不必担心更改会阻止一些您不知道的事情。
您没有提到您正在使用的依赖注入容器,但是使用内置的ASP.NET核心实现,您可以在注册服务和存储库类的项目中为服务集合创建扩展方法。扩展方法将访问内部依赖项,并且您可以注册任何内部实现,而不需要使用public或使用Microsoft的hack作为InternalsVisibleTo。
public static IServiceCollection AddContentService(this IServiceCollection services)
{
// Conditionally you can register others 3-4 dependencies here
services.AddTransient();
services.AddTransient();
return services;
}在注册应用程序依赖项的根项目中,您可以简单地调用
services.AddContentService();https://softwareengineering.stackexchange.com/questions/399597
复制相似问题