This article prompted me to reflect: http://blog.byndyu.ru/2009/10/blog-post_29.html Let me give you a slightly revised example from it:

public interface IList { public void add(int e); } public class List implements IList{ @Override public void add(int e) { // добавляет элемент } } public class DoubleList implements IList{ @Override public void add(int e) { // добавляет элемент // добавляет элемент } } 

The add method in the DoubleList class adds an element twice. In the client code, you can write something like this:

 public void LSPTest(IList list){ int oldLen = list.getLength(); list.add(1); int newLen = list.getLength(); if(newLen - oldLen == 1){ // делать что то полезное } } 

It is obvious that the behavior of the program will be different, depending on whether the LSPTest function LSPTest an object of the List or DoubleList . But does it break the LSP? After all, DoubleList does not inherit the List class, but the IList interface. The interface cannot set any preconditions and postconditions (in this case it does not specify exactly). And the interfaces are written in order to have different implementations, sometimes having very little in common. I have always believed that if, for example, DoubleList were inherited from List , then selecting the interface and lowering classes one level is just the solution to a problem when an LSP is violated. In my opinion this is called factorization. And besides, the LSP says that the program should not change if instead of the base class object we substitute the derived object. But how can you substitute something instead of an object of the base class, if the base class is abstract? Or even more interface? Instead, you can not substitute anything, because it simply can not be created.

  • Begin by defining the length() method of your IList and writing to it and to add( int e ) javadoc. What you write there will be the expected behavior, and it is these expectations that the implementing objects must meet. - zRrr
  • @zRrr, well, I have a toy example. There are no real lists. This is not a list, but in the LSP. The getLength () method simply returns the length of the list. That is, everything is determined by Javadoc? - Alexander Elizarov

2 answers 2

The interface can not set any preconditions and postconditions

Formally, you are right. An interface declaration in itself does not define a “material” (let's call it that) contract — that is, contract, which can be verified at compile time or execution. Those. There are no means to ensure that all classes that implement the interface will implement it in the same way. Lsp.

The trick is that when using interfaces, we are always talking about an "intangible" contract. It is expressed in the name of the methods and in the comments. This is such an informal agreement among the developers. In the case of the IList from .NET, it looks like this *:

 // Adds an item to the list. The exact position in the list is // implementation-dependent, so while ArrayList may always insert // in the last available location, a SortedList most likely would not. // The return value is the position the new element was inserted in. int Add(Object value); 

As can be seen from the comment, if the heir class adds two elements to the list at once, it will break two items from the comment: what should one element add and what method returns the position of the added element (and what should return in the case of two elements?).

At the same time, comments on the ICollection<T>.Add() method do not say anything about what this method is supposed to do. And, as we see, this is consistent with the fact that, for example, HashSet<T> (implementing ICollection<T> ) after two calls to the Add() method with the same arguments will contain only one element.

This is thin ice, because, I repeat, there are no means to ensure the fulfillment of the designated contract - this lies entirely on the programmer’s conscience. Moreover, the interface may not provide any comments and will have to guess about its contract from the documentation, the Internet, from colleagues.

Nevertheless, 99.9% of developers, seeing the name of the IList interface, will expect the Add() method to add one element to the list. If suddenly this method will add two elements, not add anything, or even delete, it will greatly surprise these 99.9%. This, in fact, is the violation of the LSP.

If you do not really understand LSP, then forget about the interfaces for a while and look at examples of using class inheritance. For example, the classical problem of a rectangle and a square .


* Those who wish to powder their brains to read below.

The IList.Add() method, as we have already seen, clearly indicates its contract in the commentary. What about IList<T> ? As you can see, it is inherited from the ICollection<T> , which means that the Add() method is not subject to restrictions. So DoubleList<T> : IList<T> class DoubleList<T> : IList<T> formally will no longer violate the LSP. But I bet your colleagues will not be happy with this implementation :).

  • probably therefore java.util.List explicitly overrides all java.util.Collection methods with corresponding comments. - zRrr
  • @andreycha, thanks, I understood) In this respect, probably much more convenient in the Eifel? I heard that it is possible to formalize a contract there. - Alexander Elizarov
  • @AleksandrElizarov yes, I also heard about this with the edge of my ear. This possibility absolutely makes not violate the LSP. - andreycha

It was this question that I considered in some detail in the article "Inheritance of Interfaces and Contracts" .

I duplicate the conclusions made in my article: different types of collections in .NET (for Java, you need to conduct separate research) have different postconditions. The IList interface postcondition is more restrictive: the Add method must add an element, and only one. The postcondition of the ICollection base collection is weaker: the Add method does not guarantee that the element will be added at all, since there are types such as “sets” ( HashSet and others) that should not contain duplicates and the Add method in this case will do nothing .

In this case, if our collection implements IList , then the Add method of this collection should add an element, and only one. If our collection implements only ICollection , then the Add method may well not add elements.

The interface can not set any preconditions and postconditions

In general, this is not the case. The language may not have tools for the formal definition of preconditions / postconditions for interfaces or abstract classes, but the interface has them.

For example, in .NET you can use the Code Contracts library, which allows you to specify interface contracts. At the same time, there is an annotation of the standard library in which you can see which contracts have standard interfaces. Here, for example, the contract method ICollection<T>.Add :

 public void Add(T item) { //Contract.Requires(!@this.IsReadOnly); // The Ensures below is tricky for wrappers. Needs quantified invariant in wrapper to prove wrapper correct // Forall(value => !m_backing.Contains(value) || this.Contains(value) ) //Contract.Ensures(this.Contains(item)); Contract.Ensures(this.Count >= Contract.OldValue(this.Count)); throw new global::System.NotImplementedException(); } 

There is a popular opinion that the implementation of the interface can do anything, but in this case, the client of this interface loses the opportunity to understand what to expect from this beast.

The principle of LSP is generally impossible without contracts. The contract defines the expected behavior, and the violation of the LSP just talks about the violation of this very behavior. Here you can draw an analogy with bugs: in addition to explicit crashes, we cannot say whether the behavior is an error or not, if we do not know which behavior is expected.

  • Thanks, it became clearer. But not until the end. That is, if the behavior of the interface is simply commented out, then by breaking these comments, in the class implementing the interface, we are already breaking the LSP? - Alexander Elizarov