Quote:
Original post by Emmanuel Deloget
interfaces
Some people agree, some people don't. I don't know which side I'm on personally as I'm still trying to find out how to best use them. Sometimes I have the impression inheritance as we know it should be thrown away as it combines two things which should better be separated: it defines subtype-relationships, and offers some kind of efficient composition. My idea is to only be able to use objects through interfaces, and let only interfaces define subtype relationships.
Anyway, regarding contracts of interfaces, a while ago I had a discussion with my professor regarding this issue. He claimed that it was very much necessary to provide interfaces with contracts, while my idea was that none should be given. Instead, the entities using the interfaces would have to indicate how they are being used. For example, if I have a map-function which applies some function to every item in an array:
interface IFunction<T>{ T Apply(T t);}void Map<T>(T[] array, IFunction<T> func){ for ( int i = 0; i != array.Length; ++i ) { array = func.Apply( func ); }}
Theoretically, the C# interfaces have contracts, but it's implicit, hidden in the type system: it establishes that IFunction<T> objects have a method Apply taking one T-argument and returning a T-value. But adding any more information to it would just limit Map's possible uses. So, IFunction should make no (extra) guarantees, and Map should specify in its contracts that it will call Apply on each item in the array, in order, and store the results in-place.
However, there are cases where it's more intuitive for interfaces to actually have contracts. Consider different searches: breadth-first, depth-first and dijkstra. They're basically the same algorithm, but internally use a different data structure: depth first uses a stack, breadth first a queue, dijkstra a priority queue.
interface ISearchContainer{ void Push(State s); State Pop(); bool IsEmpty { get; }}Solution GeneralSearch(ISearchContainer container, State initState){ ...}Solution DepthFirstSearch(State initState){ return GeneralSearch( new Stack(), initState);}Solution BreadthFirstSearch(State initState){ return GeneralSearch( new Queue(), initState );}// ...
Does ISearchContainer need a contract? My professor says yes, because the GeneralSearch algorithm clearly depends on it. I say no, because in order to allow any kind of search, you'll have to be extremely vague anyway about how items are Pushed and Poped. It would be better for GeneralSearch to specify how it will be using the container it receives. From the outside, you still have your full specification and everything is guaranteed to work as established in the contracts: if you want a depth-first search, you'll pass a Stack (which has a complete specification) to GeneralSearch (which indicates clearly how it will use this stack), and voilà, you have your working depth first search.
The problem is of course that GeneralSearch will probably have a pretty complex contract. But at least, it will be fully specified. This would not be the case with vague contracts as my professor would want them.
Anyway, I'm not sure who of us is right. I guess Liskov's substitution principle would be thrown out the window using my way. Maybe not a bad thing :) I'll have to bring up the subject again someday.