Learner
Learner

Reputation: 603

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 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<T> where T : class
{
    /* ... the other code is omitted for the brevity */
    Task<T> GetById(Guid or int id );     
}

What I thought to implement is to create a new method with 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 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?

Upvotes: 1

Views: 135

Answers (2)

T.S.
T.S.

Reputation: 19394

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 few of your implementations that you have to distinct 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.

Upvotes: 1

BionicCode
BionicCode

Reputation: 29028

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<T>:

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

Implementing the interfaces:

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
}

Upvotes: 1

Related Questions