How to extend / define an interface using the same exposed interface?

Viewed 124

I am looking to minimize the number of code changes when I have to update interfaces in my classes.

I know about "just creating a new interface" to provide new functionality, but then I have to update all of the old interface references in my code to use the new one.

What I'd like to know is this a better idea than creating a new interface and updating all references

Let's say I start out with this:

public interface iMyWaiter : iMyWorker
{
    void takeOrder();
}

Of course my class looks like this and I am not going to show all methods and properties in the class as they are not germane to the topic at hand.

public class MyWaiter : MyWorker, iMyWaiter
{
}

now let's suppose I want to update iMyWaiter to version 2, the "normal" way to go about doing that is to create a new interface:

public interface iMyWaiter2 : iMyWaiter
{
    void getBill();
}

and derive the class from the new interface:

public class MyWaiter : MyWorker, iMyWaiter2
{
}

That incurs a cost of searching for the references of iMyWaiter and replacing it with iMyWaiter2

iMyWaiter waiter = new MyWaiter();
waiter.takeOrder();

now becomes:

iMyWaiter2 waiter = new MyWaiter();
waiter.takeOrder();

What I am thinking about doing is making a generic "iWaiter" interface that derives from the versioned one and a generic class that derives from the versioned one:

public interface iMyWaiter_1 : iMyWorker
{
    void takeOrder();
}
public class MyWaiter_1 : MyWorker, iMyWaiter_1
{
}


public interface iWaiter : iMyWaiter_1
{
}
public class MyWaiter : MyWaiter_1, iMyWaiter
{
}

now when I need to update my interface, I simply squeeze the new one in between the generic interface and the last version:

public interface iMyWaiter_1 : iMyWorker
{
    void takeOrder();
}
public class MyWaiter_1 : MyWorker, iMyWaiter_1
{
}


public interface iMyWaiter_2 : iMyWaiter_1
{
    void getBill();
}
public class MyWaiter_2 : MyWaiter_1, iMyWaiter_2
{
}

so essentially this interface and class are always available to create and my code never changes, except to use new functions exposed by the new interface

public interface iWaiter : iMyWaiter_2
{
}
public class MyWaiter : MyWaiter_2, iMyWaiter
{
}

now I can simply add the "getBill" function to my code and not worry about interfaces:

iMyWaiter waiter = new MyWaiter();
waiter.takeOrder();
waiter.getBill();

My questions:

Is this a good design practice?

What are the downsides ?

What am I not considering?

1 Answers

Suppose you have classes out there that depend on IWaiter:

public class FoodOrder
{
    private readonly IWaiter _waiter;

    public SmallRestaurant(IWaiter waiter)
    {
        _waiter = waiter;
    }

    // other methods, one of them needs the waiter.
}

... and lots of other classes like them. And this is your IWaiter:

public interface IWaiter
{
    Bill GetBill();
}

There shouldn't be a scenario in which you add a new method to IWaiter and then you have to find the classes that need it and update them to use the new method. There are two reasons for this:

  • It's not a breaking change. If one of them doesn't need the new method they can ignore it.
  • The process should flow the other way. You would add the method to IWaiter because classes need their waiters to do that new thing. The need starts with the classes that depend on IWaiter. They are the reason for the change, so you wouldn't need to search for them. It wouldn't make sense to do the opposite, adding a method to the interface that other classes don't need.

The fact that adding a method is not a breaking change should eliminate the need to create a new "versioned" interface just because a new method is added.

If you did need to create a versioned interface, the above also applies. You'd be creating the new interface because some classes need it. You don't need to update all classes that use V1 so that now they use V2. If you did there wouldn't be any need to keep V1 around at all.

Versioning is good to prevent breaking changes if we've published some library that other code depends on, and we don't want to break them by changing the interface. If it's all within our own code and for some reason we must make a breaking change to an interface, then finding the uses of that interface will be super easy. When we change the interface we'll get a compiler error wherever the interface is used.

Related