Let we have a simple system:
Interface MyAPI {
def iterator(): Iterator
}
With typical usage:
myAPI.iterator().take(10)
This 'before change'.
Now, later we introduce CloseableIterator
and change the interface with the next version:
Interface MyAPI {
def iterator(): Iterator
def closeableIteraror(): CloseableIterator
}
With typical usage:
myAPI.iterator().take(10)
And
val it = myAPI.closeableIterator()
try {
it.take(10)
} finally {
it.close()
}
Then, if we preserve the semantics of method wich returns Iterator
— i.e., myAPI.iterator()
returns an Iterator that should not be disposed of by the client. (To do this, in the iterator, we can do defensive copying or allocate CloseableIteratir
with ReferenceQueue
and poll for reference queue to cleanup in a separate thread—it does not matter.)
If we have method takeFirst10(it: Iterator)
then we can use it, not caring about: it's CloseableIterator or not, i.e. for closeable iterator we should call
val it = myAPI.closeableIterator()
Try {
takeFirst10(it)
} finally {
it.close()
}
but semantics of takeFirst10 is not changed.
Maybe we have method takeFirst10AndClose(it: CloseableIterator)
.
What we can say about Liskov Substitution Principle here:
If the source code does not belong to iterator behavior (my Interpretation), then LSP is not violated. If we receive CloseableIterator, we know that it should be cleaned.
If the source code belongs to the iterator behavior (your interpretation), then if you change
Interface MyAPI {
def iterator(): Iterator
def closeableIteraror(): CloseableIterator
}
to
Interface MyAPI {
def iterator(): CloseableIterator
def closeableIteraror(): CloseableIterator
}
Then semantics will be changed, and you receive an LSP violation.
So, we have two interpretations:
- API, which we provide, is not behavior but like an environment. We keep LSP, preserving the semantics of old methods and assuming that we do not change an old API. (It's why I say that we can add CloseableIterator without violation of LSP)
- The API we provide is part of the behavior. If we change the base class to a subclass in the system's source code, then LSP is violated. (It's why you say that we can not add CloseableIterator without violation of LSP)
Note that here I tell nothing about design preferences. (It depends, and this is another big theme). But hope, that two interpretations of LSP are clear now.
It's the point of difference:
Interpretation 1: API. should be excluded. [Because API is how we define a system for the client. Client code does not contain API]
Interpretation 2: API should be included.
Or we can look on the scopt [1 = LSP in Client System], [ 2 = LSP in (Client System + API Implementation) ]
And the answer is 'yes' in the first case, and 'no' - in the second.