Введение
Принцип объектно-ориентированного программирования
The Liskov substitution principle (LSP) is a particular definition of a subtyping relation, called strong behavioral subtyping, that was initially introduced by Barbara Liskov in a 1987 conference keynote address titled Data abstraction and hierarchy. It is based on the concept of "substitutability" a principle in object oriented programming stating that an object (such as a class) may be replaced by a sub object (such as a class that extends the first class) without breaking the program. It is a semantic rather than merely syntactic relation, because it intends to guarantee semantic interoperability of types in a hierarchy, object types in particular. Barbara Liskov and Jeannette Wing described the principle succinctly in a 1994 paper as follows:
Subtype Requirement: Let \phi(x) be a property provable about objects x of type T. Then \phi(y) should be true for objects y of type S where S is a subtype of T.
Symbolically:
That is, if S subtypes T, what holds for T objects holds for S objects. In the same paper, Liskov and Wing detailed their notion of behavioral subtyping in an extension of Hoare logic, which bears a certain resemblance to Bertrand Meyer's design by contract in that it considers the interaction of subtyping with preconditions, postconditions and invariants.
Принцип подстановки Лискова (LSP) — это конкретное определение отношения подтипов, называемое строгим поведенческим подтипированием, которое было впервые представлено Барбарой Лисковой в 1987 году в ключевом докладе на конференции под названием «Абстракция данных и иерархия». Он основан на концепции «заместимости» — принципе объектно-ориентированного программирования, утверждающем, что объект (например, класс) может быть заменен на подобъект (например, класс, наследующий от первого класса) без нарушения работы программы. Это семантическое, а не только синтаксическое отношение, поскольку оно призвано гарантировать семантическую совместимость типов в иерархии, особенно типов объектов. Барбара Лискова и Джанетт Уинг кратко описали этот принцип в статье 1994 года следующим образом:
The Liskov substitution principle (LSP) is a particular definition of a subtyping relation, called strong behavioral subtyping, that was initially introduced by Barbara Liskov in a 1987 conference keynote address titled Data abstraction and hierarchy. It is based on the concept of "substitutability" a principle in object oriented programming stating that an object (such as a class) may be replaced by a sub object (such as a class that extends the first class) without breaking the program. It is a semantic rather than merely syntactic relation, because it intends to guarantee semantic interoperability of types in a hierarchy, object types in particular. Barbara Liskov and Jeannette Wing described the principle succinctly in a 1994 paper as follows:
Subtype Requirement: Let \phi(x) be a property provable about objects x of type T. Then \phi(y) should be true for objects y of type S where S is a subtype of T.
Symbolically:
That is, if S subtypes T, what holds for T objects holds for S objects. In the same paper, Liskov and Wing detailed their notion of behavioral subtyping in an extension of Hoare logic, which bears a certain resemblance to Bertrand Meyer's design by contract in that it considers the interaction of subtyping with preconditions, postconditions and invariants.
Требование к подтипу: пусть φ(x) — свойство, доказуемое для объектов x типа T. Тогда φ(y) должно быть истинным для объектов y типа S, где S является подтипом T.
The Liskov substitution principle (LSP) is a particular definition of a subtyping relation, called strong behavioral subtyping, that was initially introduced by Barbara Liskov in a 1987 conference keynote address titled Data abstraction and hierarchy. It is based on the concept of "substitutability" a principle in object oriented programming stating that an object (such as a class) may be replaced by a sub object (such as a class that extends the first class) without breaking the program. It is a semantic rather than merely syntactic relation, because it intends to guarantee semantic interoperability of types in a hierarchy, object types in particular. Barbara Liskov and Jeannette Wing described the principle succinctly in a 1994 paper as follows:
Subtype Requirement: Let \phi(x) be a property provable about objects x of type T. Then \phi(y) should be true for objects y of type S where S is a subtype of T.
Symbolically:
That is, if S subtypes T, what holds for T objects holds for S objects. In the same paper, Liskov and Wing detailed their notion of behavioral subtyping in an extension of Hoare logic, which bears a certain resemblance to Bertrand Meyer's design by contract in that it considers the interaction of subtyping with preconditions, postconditions and invariants.
Символически:
The Liskov substitution principle (LSP) is a particular definition of a subtyping relation, called strong behavioral subtyping, that was initially introduced by Barbara Liskov in a 1987 conference keynote address titled Data abstraction and hierarchy. It is based on the concept of "substitutability" a principle in object oriented programming stating that an object (such as a class) may be replaced by a sub object (such as a class that extends the first class) without breaking the program. It is a semantic rather than merely syntactic relation, because it intends to guarantee semantic interoperability of types in a hierarchy, object types in particular. Barbara Liskov and Jeannette Wing described the principle succinctly in a 1994 paper as follows:
Subtype Requirement: Let \phi(x) be a property provable about objects x of type T. Then \phi(y) should be true for objects y of type S where S is a subtype of T.
Symbolically:
That is, if S subtypes T, what holds for T objects holds for S objects. In the same paper, Liskov and Wing detailed their notion of behavioral subtyping in an extension of Hoare logic, which bears a certain resemblance to Bertrand Meyer's design by contract in that it considers the interaction of subtyping with preconditions, postconditions and invariants.
То есть, если S является подтипом T, то всё, что верно для объектов T, верно и для объектов S. В той же статье Лискова и Уинг подробно описали свое понимание поведенческого подтипирования в расширении логики Хоара, которое имеет некоторое сходство с подходом «Разработка на основе контрактов» Бертранда Мейера, поскольку рассматривает взаимодействие подтипирования с предусловиями, постусловиями и инвариантами.
The Liskov substitution principle (LSP) is a particular definition of a subtyping relation, called strong behavioral subtyping, that was initially introduced by Barbara Liskov in a 1987 conference keynote address titled Data abstraction and hierarchy. It is based on the concept of "substitutability" a principle in object oriented programming stating that an object (such as a class) may be replaced by a sub object (such as a class that extends the first class) without breaking the program. It is a semantic rather than merely syntactic relation, because it intends to guarantee semantic interoperability of types in a hierarchy, object types in particular. Barbara Liskov and Jeannette Wing described the principle succinctly in a 1994 paper as follows:
Subtype Requirement: Let \phi(x) be a property provable about objects x of type T. Then \phi(y) should be true for objects y of type S where S is a subtype of T.
Symbolically:
That is, if S subtypes T, what holds for T objects holds for S objects. In the same paper, Liskov and Wing detailed their notion of behavioral subtyping in an extension of Hoare logic, which bears a certain resemblance to Bertrand Meyer's design by contract in that it considers the interaction of subtyping with preconditions, postconditions and invariants.
Принцип
Понятие Лискова о поведенческом подтипе определяет понятие заменяемости объектов; то есть, если S является подтипом T, то объекты типа T в программе могут быть заменены объектами типа S без изменения каких-либо желаемых свойств этой программы (например, корректности). Поведенческое подтипирование — более сильное понятие, чем типичное подтипирование функций, определенное в теории типов, которое основывается только на контравариантности типов параметров и ковариантности типа возвращаемого значения. Поведенческое подтипирование в общем случае неразрешимо: если q — это свойство "метод для x всегда завершается", то для программы (например, компилятора) невозможно проверить, что оно верно для некоторого подтипа S типа T, даже если q действительно верно для T. Тем не менее, принцип полезен при рассуждениях о разработке иерархий классов. Принцип подстановки Лискова налагает некоторые стандартные требования к сигнатурам, которые были приняты в новых объектно-ориентированных языках программирования (обычно на уровне классов, а не типов; см. номинальное и структурное подтипирование для различия):
Contravariance of method parameter types in the subtype. Covariance of method return types in the subtype. New exceptions cannot be thrown by the methods in the subtype, except if they are subtypes of exceptions thrown by the methods of the supertype. In addition to the signature requirements, the subtype must meet a number of behavioural conditions. These are detailed in a terminology resembling that of design by contract methodology, leading to some restrictions on how contracts can interact with inheritance:
Preconditions cannot be strengthened in the subtype. Postconditions cannot be weakened in the subtype. Invariant cannot be weakened in the subtype. History constraint (the "history rule"). Objects are regarded as being modifiable only through their methods (encapsulation). Because subtypes may introduce methods that are not present in the supertype, the introduction of these methods may allow state changes in the subtype that are not permissible in the supertype. The history constraint prohibits this. It was the novel element introduced by Liskov and Wing. A violation of this constraint can be exemplified by defining a mutable point as a subtype of an immutable point. This is a violation of the history constraint, because in the history of the immutable point, the state is always the same after creation, so it cannot include the history of a mutable point in general. Fields added to the subtype may however be safely modified because they are not observable through the supertype methods. Thus, one can define a circle with immutable center and mutable radius as a subtype of an immutable point without violating the history constraint.
* Контравариантность типов параметров метода в подтипе.
* Ковариантность типов возвращаемых значений метода в подтипе.
* Новые исключения не могут выбрасываться методами в подтипе, за исключением случаев, когда они являются подтипами исключений, выбрасываемых методами супертипа.
Contravariance of method parameter types in the subtype. Covariance of method return types in the subtype. New exceptions cannot be thrown by the methods in the subtype, except if they are subtypes of exceptions thrown by the methods of the supertype. In addition to the signature requirements, the subtype must meet a number of behavioural conditions. These are detailed in a terminology resembling that of design by contract methodology, leading to some restrictions on how contracts can interact with inheritance:
Preconditions cannot be strengthened in the subtype. Postconditions cannot be weakened in the subtype. Invariant cannot be weakened in the subtype. History constraint (the "history rule"). Objects are regarded as being modifiable only through their methods (encapsulation). Because subtypes may introduce methods that are not present in the supertype, the introduction of these methods may allow state changes in the subtype that are not permissible in the supertype. The history constraint prohibits this. It was the novel element introduced by Liskov and Wing. A violation of this constraint can be exemplified by defining a mutable point as a subtype of an immutable point. This is a violation of the history constraint, because in the history of the immutable point, the state is always the same after creation, so it cannot include the history of a mutable point in general. Fields added to the subtype may however be safely modified because they are not observable through the supertype methods. Thus, one can define a circle with immutable center and mutable radius as a subtype of an immutable point without violating the history constraint.
Помимо требований к сигнатуре, подтип должен соответствовать ряду поведенческих условий. Они подробно описаны в терминологии, напоминающей методологию "проектирование по контракту", что приводит к некоторым ограничениям во взаимодействии контрактов с наследованием:
Contravariance of method parameter types in the subtype. Covariance of method return types in the subtype. New exceptions cannot be thrown by the methods in the subtype, except if they are subtypes of exceptions thrown by the methods of the supertype. In addition to the signature requirements, the subtype must meet a number of behavioural conditions. These are detailed in a terminology resembling that of design by contract methodology, leading to some restrictions on how contracts can interact with inheritance:
Preconditions cannot be strengthened in the subtype. Postconditions cannot be weakened in the subtype. Invariant cannot be weakened in the subtype. History constraint (the "history rule"). Objects are regarded as being modifiable only through their methods (encapsulation). Because subtypes may introduce methods that are not present in the supertype, the introduction of these methods may allow state changes in the subtype that are not permissible in the supertype. The history constraint prohibits this. It was the novel element introduced by Liskov and Wing. A violation of this constraint can be exemplified by defining a mutable point as a subtype of an immutable point. This is a violation of the history constraint, because in the history of the immutable point, the state is always the same after creation, so it cannot include the history of a mutable point in general. Fields added to the subtype may however be safely modified because they are not observable through the supertype methods. Thus, one can define a circle with immutable center and mutable radius as a subtype of an immutable point without violating the history constraint.
* Предусловия не могут быть усилены в подтипе.
* Постусловия не могут быть ослаблены в подтипе.
* Инвариант не может быть ослаблен в подтипе.
* Историческое ограничение ("правило истории").
Contravariance of method parameter types in the subtype. Covariance of method return types in the subtype. New exceptions cannot be thrown by the methods in the subtype, except if they are subtypes of exceptions thrown by the methods of the supertype. In addition to the signature requirements, the subtype must meet a number of behavioural conditions. These are detailed in a terminology resembling that of design by contract methodology, leading to some restrictions on how contracts can interact with inheritance:
Preconditions cannot be strengthened in the subtype. Postconditions cannot be weakened in the subtype. Invariant cannot be weakened in the subtype. History constraint (the "history rule"). Objects are regarded as being modifiable only through their methods (encapsulation). Because subtypes may introduce methods that are not present in the supertype, the introduction of these methods may allow state changes in the subtype that are not permissible in the supertype. The history constraint prohibits this. It was the novel element introduced by Liskov and Wing. A violation of this constraint can be exemplified by defining a mutable point as a subtype of an immutable point. This is a violation of the history constraint, because in the history of the immutable point, the state is always the same after creation, so it cannot include the history of a mutable point in general. Fields added to the subtype may however be safely modified because they are not observable through the supertype methods. Thus, one can define a circle with immutable center and mutable radius as a subtype of an immutable point without violating the history constraint.
Объекты считаются изменяемыми только через их методы (инкапсуляция). Поскольку подтипы могут вводить методы, отсутствующие в супертипе, введение этих методов может позволить изменение состояния в подтипе, которое недопустимо в супертипе. Историческое ограничение запрещает это. Это был новый элемент, введенный Лисковым и Уингом. Нарушение этого ограничения можно проиллюстрировать определением изменяемой точки как подтипа неизменяемой точки. Это нарушение ограничения истории, поскольку в истории неизменяемой точки состояние всегда остается прежним после создания, поэтому она не может включать в себя историю изменяемой точки в общем случае. Однако поля, добавленные к подтипу, могут быть безопасно изменены, поскольку они не наблюдаются через методы супертипа. Таким образом, можно определить круг с неизменяемым центром и изменяемым радиусом как подтип неизменяемой точки, не нарушая ограничения истории.
Contravariance of method parameter types in the subtype. Covariance of method return types in the subtype. New exceptions cannot be thrown by the methods in the subtype, except if they are subtypes of exceptions thrown by the methods of the supertype. In addition to the signature requirements, the subtype must meet a number of behavioural conditions. These are detailed in a terminology resembling that of design by contract methodology, leading to some restrictions on how contracts can interact with inheritance:
Preconditions cannot be strengthened in the subtype. Postconditions cannot be weakened in the subtype. Invariant cannot be weakened in the subtype. History constraint (the "history rule"). Objects are regarded as being modifiable only through their methods (encapsulation). Because subtypes may introduce methods that are not present in the supertype, the introduction of these methods may allow state changes in the subtype that are not permissible in the supertype. The history constraint prohibits this. It was the novel element introduced by Liskov and Wing. A violation of this constraint can be exemplified by defining a mutable point as a subtype of an immutable point. This is a violation of the history constraint, because in the history of the immutable point, the state is always the same after creation, so it cannot include the history of a mutable point in general. Fields added to the subtype may however be safely modified because they are not observable through the supertype methods. Thus, one can define a circle with immutable center and mutable radius as a subtype of an immutable point without violating the history constraint.
Происхождение
Правила предусловий и постусловий идентичны тем, что были введены Бертраном Мейером в его книге 1988 года «Объектно-ориентированное построение программного обеспечения». И Мейер, и позже Пьер Америка, который первым использовал термин «поведенческое подтипирование», дали теоретико-доказательные определения некоторых понятий поведенческого подтипирования, но их определения не учитывали алиасинг, который может возникать в языках программирования, поддерживающих ссылки или указатели. Учет алиасинга стал основным улучшением, внесенным Лисков и Вингом (1994), а ограничение истории является ключевым элементом. Согласно определениям Мейера и Америки, изменяемая точка была бы поведенческим подтипом неизменяемой точки, в то время как принцип подстановки Лисков это запрещает.
Конкретные ссылки
Основной доклад, в котором Лисков впервые сформулировал этот принцип.
Общие сведения
В этой статье рассматриваются различные понятия поведенческого подтипирования, включая подход Лискова и Уинга. Появилась обновленная версия, представляющая собой формализацию принципа его авторами. В ней содержится более доступное введение в поведенческое подтипирование в различных его формах, представленное во второй главе. Существует популярная в сообществе объектно-ориентированного программирования статья, приводящая несколько примеров нарушений принципа подстановки Лискова (LSP). В данной статье обсуждается LSP в контексте, описанном в этой статье.