vendredi 18 mars 2022

What exactly is a "client" in the interface segregation principle (ISP)?

This is one of a number of things that's been bugging me for a while and for which quibbling over the correct interpretation of this has been leading me in a number of attempted coding projects to more fussing around with design, than it has with making steady forward progress, because I want to make sure I've gotten it "right".

In this case, I have a question about the "interface segregation principle" (ISP) of the five "SOLID" principles of basic object oriented design. Its "canonical" statement is this:

Clients should not be forced to depend on methods they do not use.

However, the question here is, is what is the "client" - because this leads to very different interpretations, and also leads to a seeming dilemma when applied in practice, which is what I'd like to understand if first actually is one, and second, how to best solve it.

One interpretation of this is that it is a "single responsibility principle" for interfaces instead of just classes: i.e. if you have an interface IMessageReceiver, it better not include a Send method. Another possible interpretation is that it means that the implementer of an interface should not be required to implement empty methods, while a third interpretation is that the caller should not be able to see any more methods than it actually needs. And the thing is, I can see merit in all three of these interpretations, yet when you apply them all, it seems that in any suitably large project, highly counterintuitive and seemingly problematic things result. In particular, it's that third one that seems to be the rub.

For one, if something gets used in enough places, it is particularly that last one - the "caller" one - which generally tends to "bite" in that it results naturally in your interfaces being atomized down to single methods only. For example, consider a simple interface to a backend storage or database, which may have a load, save, and update method. Some callers of that, though, may not want to save anything. They may just want to peek at the data. Hence to avoid violating the caller interpretation, we must split off the load method. Add a few more use cases and now it's atomized into a IDataLoader, IDataSaver, and IDataUpdater which all have 1 method each.

And I can't help but feel this almost seems like an anti-pattern, particularly if it happens with enough objects owing to them being used in a suitably wide variety of places. Is it? Or am I misinterpreting something here? After all, nobody makes a generic container class (like the "list", "map", etc. things in a lot of languages) with single-method interfaces to be piecemealed out to whatever it gets passed to.

Aucun commentaire:

Enregistrer un commentaire