Skip to content

Commit 587a646

Browse files
committed
Add Liskov substitution principle
1 parent bd7420d commit 587a646

1 file changed

Lines changed: 89 additions & 1 deletion

File tree

README.md

Lines changed: 89 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -830,7 +830,8 @@ As an added bonus, the `get_version()` is now reusable elsewhere.
830830
### **Open/Closed Principle (OCP)**
831831

832832
> “Incorporate new features by extending the system, not by making
833-
modifications (to it)”, Uncle Bob.
833+
> modifications (to it)”,
834+
> Uncle Bob.
834835
835836
Objects should be open for extension, but closed to modification. It should be
836837
possible to augment the functionality provided by an object (for example, a class)
@@ -1042,6 +1043,93 @@ FIXME: re-enable typechecking for the line above once it's clear how to use
10421043
`typing.Protocol` to make the type checker work with Mixins.
10431044

10441045
### **Liskov Substitution Principle (LSP)**
1046+
1047+
> “Functions that use pointers or references to base classes
1048+
> must be able to use objects of derived classes without knowing it”,
1049+
> Uncle Bob.
1050+
1051+
This principle is named after Barbara Liskov, who collaborated with fellow
1052+
computer scientist Jeannette Wing on the seminal paper
1053+
*"A behavioral notion of subtyping" (1994). A core tenet of the paper is that
1054+
"a subtype (must) preserve the behaviour of the supertype methods and also all
1055+
invariant and history properties of its supertype".
1056+
1057+
In essence, a function accepting a supertype should also accept all its subtypes
1058+
with no modification.
1059+
1060+
Can you spot the problem with the following code?
1061+
1062+
**Bad**
1063+
```python
1064+
from dataclasses import dataclass
1065+
1066+
1067+
@dataclass
1068+
class Response:
1069+
"""An HTTP response"""
1070+
1071+
status: int
1072+
content_type: str
1073+
body: str
1074+
1075+
1076+
class View:
1077+
"""A simple view that returns plain text responses"""
1078+
1079+
content_type = "text/plain"
1080+
1081+
def render_body(self) -> str:
1082+
"""Render the message body of the response"""
1083+
return "Welcome to my web site"
1084+
1085+
def get(self, request) -> Response:
1086+
"""Handle a GET request and return a message in the response"""
1087+
return Response(
1088+
status=200,
1089+
content_type=self.content_type,
1090+
body=self.render_body()
1091+
)
1092+
1093+
1094+
class TemplateView(View):
1095+
"""A view that returns HTML responses based on a template file."""
1096+
1097+
content_type = "text/html"
1098+
1099+
def get(self, request, template_file: str) -> Response: # type: ignore
1100+
"""Render the message body as HTML"""
1101+
with open(template_file) as fd:
1102+
return Response(
1103+
status=200,
1104+
content_type=self.content_type,
1105+
body=fd.read()
1106+
)
1107+
1108+
1109+
def render(view: View, request) -> Response:
1110+
"""Render a View"""
1111+
return view.get(request)
1112+
1113+
```
1114+
1115+
The expectation is that `render()` function will be able to work with `View` and its
1116+
subtype `TemplateView`, but the latter has broken compatibility by modifying
1117+
the signature of the `.get()` method. The function will raise a `TypeError`
1118+
exception when used with `TemplateView`.
1119+
1120+
If we want the `render()` function to work with any subtype of `View`, we
1121+
must pay attention not to break its public-facing protocol. But how do we know
1122+
what constitutes it for a given class? Type hinters like *mypy* will raise
1123+
an error when it detects mistakes like this:
1124+
1125+
```
1126+
error: Signature of "get" incompatible with supertype "View"
1127+
<string>:36: note: Superclass:
1128+
<string>:36: note: def get(self, request: Any) -> Response
1129+
<string>:36: note: Subclass:
1130+
<string>:36: note: def get(self, request: Any, template_file: str) -> Response
1131+
```
1132+
10451133
### **Interface Segregation Principle (ISP)**
10461134
### **Dependency Inversion Principle (DIP)**
10471135

0 commit comments

Comments
 (0)