简体   繁体   English

如何重载/替换/扩展通用代码中使用的C#接口

[英]How to overload/substitute/extend C# Interface used in common code

I will give you a little context first 我先给你一点背景

My company chose a design approach for our applications (windows services) to allow more code reuse through centralization (internal nuget repo) of core common code. 我公司为我们的应用程序(Windows服务)选择了一种设计方法,以通过核心通用代码的集中化(内部nuget回购)来实现更多代码重用。 This makes it easier to maintain test and build new services up until now.. 到目前为止,这使维护测试和构建新服务变得更加容易。

Essentially all of the services use SqlDependency to get notified from the database that work is available for them to process. 本质上,所有服务都使用SqlDependency从数据库中获得可以处理的通知。 When the SqlDependency object gets notified it calls the following 当SqlDependency对象得到通知时,它将调用以下命令

public interface IRequestReceiver : IDisposable
{
    IEnumerable<IRequest> GetRequests(int maxRequestNumber);
}

which is implemented independently by each application and therefore allows each application to retrieve it's requests from it's known location and process them etc. 它由每个应用程序独立实现,因此允许每个应用程序从其已知位置检索其请求并进行处理等。

Now for the problem 现在解决问题

This all worked well up until recently as all the IRequest s were very similar and so we could use a single IRequest interface in common code but put different processing logic in each. 由于所有IRequest都非常相似,所以直到最近,这一切都工作良好,因此我们可以在通用代码中使用单个IRequest接口,但在每个代码中放置不同的处理逻辑。 unfortunately I have been assigned the task of building a new service whose implementation of IRequest differs significantly from all the previous ones before it. 不幸的是,我被分配了构建新服务的任务,该服务的IRequest实现与之前的所有服务都大不相同。

My issue is I cannot alter IRequest as it is used by multiple live services and I am obliged to stick within the design pattern of our existing services yet I have to include serveral more properties in IRequest that have not existed before and have no relevance to the existing services. 我的问题是我无法更改IRequest,因为它已被多个实时服务使用,并且我不得不坚持现有服务的设计模式,但我必须在IRequest中包含以前不存在的,与服务器无关的更多服务器属性。现有服务。

And finally my question 最后是我的问题

Given I know that an interface is a contract and should therefore not be overloaded, how would I overload or expand or even substitute the existing IRequest interface for just this new service without exposing irrelevant properties to the existing services. 鉴于我知道一个接口是一个契约,因此不应被重载,我如何在不向现有服务暴露无关属性的情况下,将重载或扩展或什至将现有IRequest接口替换为该新服务。

The following are samples of the existing and new IRequest interface 以下是现有和新IRequest接口的示例

Existing 现有

public interface IRequest
{
    int Id { get; }

    string CreatedBy { get; }

    bool IsActive { get; set; }

    DateTimeOffset CreatedDateTime { get; set; }

    string LastUpdatedBy { get; set; }#

    DateTimeOffset LastUpdatedDateTime { get; set; }

    IRequestDetail Detail { get; }

    bool Process(CancellationToken token);
}

New

public interface IRequest
{
    int Id { get; }

    string CreatedBy { get; }

    bool IsActive { get; set; }

    DateTimeOffset CreatedDateTime { get; set; }

    string LastUpdatedBy { get; set; }#

    DateTimeOffset LastUpdatedDateTime { get; set; }

    int NotificationId { get; set; }

    int StatusId { get; set; }

    string StatusCode { get; set; }

    string StatusMessage { get; set; }

    string Destination { get; set; }

    string BusinessProcessCode { get; set; }

    string MsgType { get; set; }

    string MessageId { get; set; }

    bool Process(CancellationToken token);
}

As you can see it's just additional properties but I would not like to start introducing these to the existing IRequest interface. 如您所见,它只是其他属性,但我不想开始将这些属性引入现有的IRequest接口。

Thank you 谢谢

Think about the open closed principle. 考虑一下开放封闭原则。

You have IRequest already out in production. 您已经有IRequest投入生产。 You have clients that already use IRequest out in production too. 您也有已经在生产中使用IRequest的客户。

What you care about is the new clients( or old ones too) that would like to use the new features. 您关心的是想要使用新功能的新客户端(或旧客户端)。

So in a nutshell. 简而言之。 1. Do not change anything in IRequest 2. make instead IRequest Extensions for example: 1.请勿在IRequest中进行任何更改。2.以IRequest Extensions为例,例如:

public interface IRequestExtensions
{
    int NotificationId { get; set; }

    int StatusId { get; set; }

    string StatusCode { get; set; }

    string StatusMessage { get; set; }

    string Destination { get; set; }

    string BusinessProcessCode { get; set; }

    string MsgType { get; set; }

    string MessageId { get; set; }
}

However what I see is that in the new IRequest is that you have backward incompatible types. 但是我看到的是,在新的IRequest中,您具有向后不兼容的类型。 For this explanation (since you did not mentions about incompatible types), I did not include them in the IRequestExtensions. 对于这种解释(因为您没有提到不兼容的类型),所以我没有在IRequestExtensions中包括它们。

You can then tie up your system and provide a new interface like IRequestNew which would include both old and new things. 然后,您可以捆绑您的系统,并提供一个像IRequestNew这样的新界面,其中将包括新旧事物。

public interface IRequestNew : IRequest, IRequestExtensions
{
}

Now the new(and old existing) clients should use the IRequestNew (just a name, not pretty but you get the idea) and for old clients that want to implement the new interface would just add an extra, IRequestExtension to their class. 现在,新的(和旧的)现有客户端应该使用IRequestNew(只是一个名称,不是很漂亮,但是您知道了),对于想要实现新接口的旧客户端,只需在其类中添加一个额外的IRequestExtension即可。

How do you migrate everyone to use IRequest as the real new name with all the features. 如何迁移所有人,以使用IRequest作为具有所有功能的真实新名称。 Well, that's another story. 好吧,那是另一个故事。

Another way to solve this is to use a generic method for GetRequests 解决此问题的另一种方法是对GetRequests使用通用方法

IEnumerable<T> GetRequests<T>(int maxRequestNumber);

But the class who invokes GetRequest should know what is the type they are expecting. 但是调用GetRequest的类应该知道他们期望的类型。 If they Expect IRequestOld/IRequestNew, they should pass it while calling GetRequests. 如果他们期望IRequestOld / IRequestNew,则应在调用GetRequests时传递它。

IRequestOld request = GetRequest<RequestOld>(maxRequestNumber);
IRequestNew request = GetRequest<RequestNew>(maxRequestNumber);

An example program 一个示例程序

class Program
{

    public static void Main()
    {
        Number n = new Number();
        IOne one = n.GetNumberObject<One>();
        one.PrintOne();
        ITwo two = n.GetNumberObject<Two>();
        two.PrintTwo();
    }

    public interface INumbers
    {
        T GetNumberObject<T>() where T : new();
    }
    public class Number : INumbers
    {
        public T GetNumberObject<T>() where T : new()
        {
            return new T();
        }
    }

    public interface IOne
    {
        void PrintOne();
    }
    public interface ITwo
    {
        void PrintTwo();
    }

    class One : IOne
    {
        public void PrintOne()
        {
            Console.WriteLine("One");
        }
    }
    class Two : ITwo
    {
        public void PrintTwo()
        {
            Console.WriteLine("Two");
        }
    }
}

Create a V2 interface that either derives from the initial version or is totally independent of it (will require quite some more work). 创建一个V2接口,该接口要么衍生自初始版本,要么完全独立于初始版本(将需要更多工作)。 Then use the V2 where you need it: check if the request is a V2, cast the object and then call the V2 properties. 然后在需要的地方使用V2:检查请求是否为V2,强制转换对象,然后调用V2属性。

This will probably quite some work to enable V2 properties everywhere you need it. 在您需要的任何地方启用V2属性都可能需要做很多工作。 If that is not possible, you could opt to keep two separate systems alive, running side by side. 如果无法做到这一点,则可以选择让两个单独的系统并排运行。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM