@@ -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
835836Objects should be open for extension, but closed to modification. It should be
836837possible 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