扩展接口签名而不在多个地方编辑代码

huangapple go评论81阅读模式
英文:

Extending interface signature without editing code in multiple places

问题

I have an interface and existing code which implements this interface:

public interface IBusinessService<T> where T : class
{
    Task Add(T category);

    Task Delete(T category);

    Task Update(T category);

    Task<IEnumerable<T>> GetAll();

    Task<T> GetById(int id);     
}

Now some id has the type of Guid. So I cannot use this method to send a Guid id:

var id = Guid.NewGuid()
var result = GetById(id);

So we need that id parameter can be of the type Guid. It would be ideal if it is possible:

public interface IBusinessService<T> where T : class
{
    /* ... the other code is omitted for brevity */
    Task<T> GetById(Guid or int id );     
}

What I thought to implement is to create a new method with a parameter type of Guid:

public interface IBusinessService<T> where T : class
{
    Task Add(T category);

    Task Delete(T category);

    Task Update(T category);

    Task<IEnumerable<T>> GetAll();

    Task<T> GetById(int id);     

    Task<T> GetById(Guid id);     
}

But if I do this, then I need to edit so much code. So it looks like it is not a good solution.

Is there a way to add another type of id to the interface method GetById without breaking changes?

英文:

I have an interface and existing code which implements this interface:

public interface IBusinessService&lt;T&gt; where T : class
{
	Task Add(T category);

    Task Delete(T category);

    Task Update(T category);

    Task&lt;IEnumerable&lt;T&gt;&gt; GetAll();
	
    Task&lt;T&gt; GetById(int id);     
}

Now some id has type of Guid. So I cannot use this method to send Guid id:

var id = Guid.NewGuid()
var result = GetById(id);

So we need that id parameter can be type of Guid. It would be ideal if it is possible:

public interface IBusinessService&lt;T&gt; where T : class
{
    /* ... the other code is omitted for the brevity */
    Task&lt;T&gt; GetById(Guid or int id );     
}

What I thought to implement is to create a new method with parameter type of Guid:

public interface IBusinessService&lt;T&gt; where T : class
{
    Task Add(T category);

    Task Delete(T category);

    Task Update(T category);

    Task&lt;IEnumerable&lt;T&gt;&gt; GetAll();
	
    Task&lt;T&gt; GetById(int id);     
	
	Task&lt;T&gt; GetById(Guid id);     
}

But if I will do this, then I need to edit so many code. So it looks like it is not a good solution.

Is there a way to add another type of id to the interface method GetById without breaking changes?

答案1

得分: 1

这似乎是一个设计问题(接口/API设计)首先。从一开始就应该清楚ID是由Guid还是原始的int值表示。不应该两者兼容。通常,您尝试使用协议来控制客户端-服务器通信,或者至少通过定义服务器API来实现。这个协议将定义API期望的值,客户端必须确保以正确的格式输入它们。

考虑继续使用int ID,并在需要时使用自定义转换从Guidint。或者引入一个表(Dictionary),在其中存储Guidint值的对。因此,对于进入您的应用程序的每个Guid,您内部生成一个整数,它将替换Guid。但在使用int值时,必须有一些约束或算法来确保ID的唯一性。但我假设当您使用int作为唯一ID时,已经有一个生成这些ID的算法了。

向接口添加成员始终是一个破坏性的更改。

由于您没有使用C# 8,无法使用新的默认接口方法功能。

因此,如果可以接受破坏性更改,那么可以重构接口以接受一个额外的泛型参数用于id参数。您还可以将id参数的类型从int更改为object。但这也会引入装箱/拆箱的成本(以将值类型如int转换为object)。我个人认为使用objectstring中的任何一个都是最好的选择。我不了解您的Web接口,但string可能是首选的ID类型。

因此,在无法接受这些破坏性更改的情况下,最好的解决方案是实现一个新的接口,例如IGuidIdBusinessService<T>

public interface IGuidIdBusinessService<T> : IBusinessService<T> where T : class
{
  Task<T> GetByGuid(Guid id);
}

实现这些接口:

public class MyClass : IGuidIdBusinessService<object>
{
  #region Implementation of IBusinessService<object>

  ...

  // Either throw NotSupportedException or implement conversion
  public async Task<object> GetById(int id)
  {
    Guid guid = ConvertIntToGuid(id);
    return GetByGuid(guid);
  }

  #endregion

  #region Implementation of IGuidIdBusinessService<object>

  public async Task<object> GetByGuid(Guid id) => throw new NotImplementedException();

  #endregion
}

希望这些信息对您有所帮助。

英文:

This seems to be a design problem (interface/API design) in the first place. It should be clear from the beginning whether IDs are represented by a Guid or a raw int value. But not both. Usually you try to use a protocol to control the client-server communication or at least by defining a server API. This protocol would define what values the API expects and the client must take care to input them in the correct format.

Consider to stay with the int ID an use a custom conversion from Guid to int where required. Alternatively introduce a table (Dictionary) where you store pairs of Guid and int values. So for each Guid that enters your application you internally generate a integer which will replace the Guid. But there must be some constraints or algorithm to guarantee the uniqueness of the IDs especially when using int values. But I assume when you are using int for unique IDs you already have an algorithm to generate those IDs.

Adding members to an interface is always a breaking change.

Since you are not using C# 8 you can't make use of the new Default Interface Method feature.

So, if you can accept breaking changes then you could refactor the interface to take one more generic parameter for the id parameter.
You could also change the type of the id parameter from int to object. But this would also introduce the costs of boxing/unboxing (in order to convert value types like int to object). I personally think using either object or string would be the best. I don't know your web interface but string could be the preferred ID type.

So the best solution, in case you can't accept those breaking changes, would be to implement a new interface e.g., IGuidIdBusinessService&lt;T&gt;:

public interface IGuidIdBusinessService&lt;T&gt; : IBusinessService&lt;T&gt; where T : class
{
  Task&lt;T&gt; GetByGuid(Guid id);
}

Implementing the interfaces:

public class MyClass : IGuidIdBusinessService&lt;object&gt;
{
  #region Implementation of IBusinessService&lt;object&gt;
 
  ...

  // Either throw NotSupportedException or implement conversion
  public async Task&lt;object&gt; GetById(int id)
  {
    Guid guid = ConvertIntToGuid(id);
    return GetByGuid(guid);
  }

  #endregion

  #region Implementation of IGuidIdBusinessService&lt;object&gt;

  public async Task&lt;object&gt; GetByGuid(Guid id) =&gt; throw new NotImplementedException();

  #endregion
}

答案2

得分: 1

I really don't see the problem as big as you describe -- "But if I will do this, then I need to edit so many code"

How many implementations do you have. 20? Key here, implementation vs usages. Usages can be many or very many.

Looks like your goal is to keep usage intact as

var id = Guid.NewGuid();
var result = someInst.GetById(id);
// or
var id = 5;
var result = someInst.GetById(id);

No problem, you need minimal changes. This happens all the time

public interface IBusinessService<T> where T : class
{
    Task<T> GetById(object id);    // NOTE - change to 'object' 
}

Now, you just need to change only a few of your implementations that you have to distinguish Guid from int

public Task<T> GetById(object id)
{
    if (id is int i)
        return ProcessIdAsInt<T>(i);
    else if (id is Guid g)
        return ProcessIdAsGuid<T>(g);
    else
        throw new InvalidOperationException("Supplied data type not supported");
}

So, the changes are minimal. Usages will remain intact. Nothing unusual.

英文:

I really don't see the problem as big as you describe -- "But if I will do this, then I need to edit so many code"

How many implementations do you have. 20? Key here, implementation vs usages. Usages can be many or very many.

Looks like your goal is to keep usage intact as

var id = Guid.NewGuid();
var result = someInst.GetById(id);
// or
var id = 5;
var result = someInst.GetById(id);

No problem, you need minimal changes. This happens all the time

public interface IBusinessService&lt;T&gt; where T : class
{
    Task&lt;T&gt; GetById(object id);    // NOTE - change to &#39;object&#39; 
}

Now, you just need to change only few of your implementations that you have to distinct Guid from int

public Task&lt;T&gt; GetById(object id)
{
    if (id is int i)
        return ProcessIdAsInt&lt;T&gt;(i);
    else if (id is Guid g)
        return ProcessIdAsGuid&lt;T&gt;(g);
    else
        throw new InvalidOperationException(&quot;Supplied data type not supported&quot;);
}

So, the changes are minimal. Usages will remain intact. Nothing unusual.

huangapple
  • 本文由 发表于 2020年1月3日 21:25:17
  • 转载请务必保留本文链接:https://go.coder-hub.com/59579402.html
  • c#
  • design-patterns
  • interface
  • oop

为所有缺失的枚举添加到List中。 go 66
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定

  • 开发者交流平台

    本页二维码