C#如何将类“插件”“注册”成服务类?- 截至今日

Dry*_*ods 13 .net c# design-patterns

自从这个问题提出以来已经过去了 6 年,我本来希望今天能有一个简单的解决方案......但似乎没有。

注意:请阅读其他问题以理解这个概念:

几分钟后,我尝试实现一个简单的示例,并且几乎已经完成了。同时我仍然看到一些问题。我想知道是否有人有关于如何让它变得更好的想法。

使用.NET 6(代码如下)。

  • 问题 1:我不喜欢我们所说的泛型,使用TTargetas User,我们还需要传递T ID类型......为什么通过传递User不足以让编译器知道ID数据类型?示例:class UserService : IBaseDBOperation1<User, Guid>为什么不呢class UserService : IBaseDBOperation1<User>

  • 问题2:我知道现在我们可以使用带有代码的方法的接口,但是为什么我们仍然需要用两种数据类型精确地定义变量类型并且使用var还不够?好吧,我们可以使用var,但是这样方法就不可见了。而不是:IBaseDBOperation1<User, Guid> serviceU1 = new UserService();........ var serviceU2 = new UserService();...... 这第二个变量将看不到其他方法。

最后一点:如果 C# 允许我们用多个抽象类扩展一个类,那么一切都会变得容易多了……(截至目前,我们的抽象类仅限于 1 个)。

目标:完成6 年前提出的问题中提出的问题......换句话说......避免复制/粘贴,并以某种方式将多个“操作类”“注入/关联/注册/定义”到服务中....那些“操作类”将在多个不同的服务中大量重用...并且我确实希望有一种“干净/漂亮”的方式来设置它,但同时,消费者不应该担心“较低/更深”的杠杆继承泛型。

代码

public abstract class BaseDBEntry<T> where T : struct
{
    protected BaseDBEntry()
    {
        CreatedOn = DateTime.Now;
    }

    public T Id { get; set; }
    public DateTime CreatedOn { get; set; }
    public DateTime? DeletedOn { get; set; }
}

public class User : BaseDBEntry<Guid>
{
    public User() { Id = Guid.NewGuid(); }
    public string Name { get; set; }
}

public class Color : BaseDBEntry<long>
{
    public Color() { Id = DateTime.Now.Ticks; }
    public string RGB { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

服务

public interface IBaseDBOperation1<in TTarget, out T> 
                 where TTarget : BaseDBEntry<T> where T : struct
{
    public bool IsValid(TTarget model) { return true; }

    T GiveMeId(TTarget model) { return model.Id; }
}

public interface IBaseDBOperation2<in TTarget, T> 
                 where TTarget : BaseDBEntry<T> where T : struct
{
    public bool IsValidToDoSomethingElse(TTarget model) { return false; }
}

public class UserService : IBaseDBOperation1<User, Guid>, IBaseDBOperation2<User, Guid> { }

public class ColorService : IBaseDBOperation1<Color, long>, IBaseDBOperation2<Color, long> { }
Run Code Online (Sandbox Code Playgroud)

消费者

public class Consumer
{
    public void Run()
    {
        IBaseDBOperation1<User, Guid> serviceU1 = new UserService();
        IBaseDBOperation2<User, Guid> serviceU2 = new UserService();
        var u = new User { Name = "Carl" };
        var resU1 = serviceU1.IsValid(u);
        var resU2 = serviceU1.GiveMeId(u);
        var resU3 = serviceU2.IsValidToDoSomethingElse(u);

        var serviceU3 = new UserService();
        //serviceU3.XXXXX() --> has no information about the methods we need

        IBaseDBOperation2<Color, long> serviceC1 = new ColorService();
        var c = new Color { RGB = "#FFFFFF" };
        var resC1 = serviceC1.IsValidToDoSomethingElse(c);


        var adasda = "";
    }
}



var consumer = new Consumer();
consumer.Run();
Run Code Online (Sandbox Code Playgroud)

Gur*_*ron 4

我将从一个小评论开始 - 请尝试遵循标准命名约定,在本例中是这样的:

接口名称以大写字母开头I

至于问题:

问题 1:我不喜欢我们所说的泛型,使用TTargetas User,我们还需要传递T ID类型。

在过去的 6 年里,这里没有太大变化,interface BaseDBOperation1<TTarget, T>..仍然需要 2 个泛型类型参数,并且您仍然可以拥有一个带有一个类型参数的接口,即interface BaseDBOperation1<TTarget>这对于编译器来说是不明确的(因此添加interface BaseDBOperation1<TTarget>将成为一项重大更改,这是一个问题如果这些类作为库分发)。

也许类似的事情可以通过更高级的类型或类似的语言功能来实现,但 ATM 在 C# 中不可用。

要跟踪的相关问题:

问题 2:...第二个变量不会看到其他方法。

这是设计使然(默认接口方法草案规范):

请注意,类不会从其接口继承成员;而是从其接口继承成员。此功能不会改变这一点:

interface IA
{
    void M() { WriteLine("IA.M"); }
}
class C : IA { } // OK
new C().M(); // error: class 'C' does not contain a member 'M'
Run Code Online (Sandbox Code Playgroud)

为了调用接口中声明和实现的任何方法,变量必须是接口的类型

var serviceU2 = new UserService();您需要转换到相应的接口:

var resU1 = ((BaseDBOperation1<User, Guid>)serviceU2).IsValid(u); 
Run Code Online (Sandbox Code Playgroud)

这种行为的另一个原因可能类似于所谓的脆弱基类问题

就我个人而言,我不太喜欢这个功能,无论是概念上还是由于一些极端情况(例如这个)。

至于实现此类功能并减少手动编写代码的方法(如果您有很多此类存储库) - 您可以使用源生成器查看一些编译时代码生成,但这绝对不是一个简单的选择。至少是第一次。