Skip to content

Commit 278bfe8

Browse files
committed
Add Open/Closed Principle
1 parent d1c091a commit 278bfe8

File tree

1 file changed

+119
-0
lines changed

1 file changed

+119
-0
lines changed

README.md

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -826,6 +826,125 @@ accepts a string.
826826
As an added bonus, the `get_version()` is now reusable elsewhere.
827827

828828
### **Open/Closed Principle (OCP)**
829+
830+
> “Incorporate new features by extending the system, not by making
831+
modifications (to it)”, Uncle Bob.
832+
833+
Objects should be open for extension, but closed to modification. It should be
834+
possible to augment the functionality provided by an object (for example, a class)
835+
without changing its internal contracts. An object can enable this when it
836+
is designed to be extended cleanly.
837+
838+
In the following example, we try to implement a simple web framework that
839+
handles HTTP requests and returns responses. The `View` class has a single
840+
method `.get()` that will be called when the HTTP server will receive a GET
841+
request from a client.
842+
843+
`View` is intentionally simple and returns `text/plain` responses. We would
844+
also like to return HTML responses based on a template file, so we subclass it
845+
using the `TemplateView` class.
846+
847+
**Bad**
848+
```python
849+
from dataclasses import dataclass
850+
851+
852+
@dataclass
853+
class Response:
854+
"""An HTTP response"""
855+
856+
status: int
857+
content_type: str
858+
body: str
859+
860+
861+
class View:
862+
"""A simple view that returns plain text responses"""
863+
864+
def get(self, request) -> Response:
865+
"""Handle a GET request and return a message in the response"""
866+
return Response(
867+
status=200,
868+
content_type='text/plain',
869+
body="Welcome to my web site"
870+
)
871+
872+
873+
class TemplateView(View):
874+
"""A view that returns HTML responses based on a template file."""
875+
876+
def get(self, request) -> Response:
877+
"""Handle a GET request and return an HTML document in the response"""
878+
with open("index.html") as fd:
879+
return Response(
880+
status=200,
881+
content_type='text/html',
882+
body=fd.read()
883+
)
884+
885+
```
886+
887+
The `TemplateView` class has modified the internal behaviour of its parent
888+
class in order to enable the more advanced functionality. In doing so,
889+
it now relies on the `View` to not change the implementation of the `.get()`
890+
method, which now needs to be frozen in time. We cannot introduce, for example,
891+
some additional checks in all our `View`-derived classes because the behaviour
892+
is overridden in at least one subtype and we will need to update it.
893+
894+
Let's redesign our classes to fix this problem and let the `View` class be
895+
extended (not modified) cleanly:
896+
897+
**Good**
898+
```python
899+
from dataclasses import dataclass
900+
901+
902+
@dataclass
903+
class Response:
904+
"""An HTTP response"""
905+
906+
status: int
907+
content_type: str
908+
body: str
909+
910+
911+
class View:
912+
"""A simple view that returns plain text responses"""
913+
914+
content_type = "text/plain"
915+
916+
def render_body(self) -> str:
917+
"""Render the message body of the response"""
918+
return "Welcome to my web site"
919+
920+
def get(self, request) -> Response:
921+
"""Handle a GET request and return a message in the response"""
922+
return Response(
923+
status=200,
924+
content_type=self.content_type,
925+
body=self.render_body()
926+
)
927+
928+
929+
class TemplateView(View):
930+
"""A view that returns HTML responses based on a template file."""
931+
932+
content_type = "text/html"
933+
template_file = "index.html"
934+
935+
def render_body(self) -> str:
936+
"""Render the message body as HTML"""
937+
with open("index.html") as fd:
938+
return fd.read()
939+
940+
941+
```
942+
943+
Note that we did need to override the `render_body()` in order to change the
944+
source of the body, but this method has a single, well defined responsibility
945+
that **invites subtypes to override it**. It is designed to be extended by its
946+
subtypes.
947+
829948
### **Liskov Substitution Principle (LSP)**
830949
### **Interface Segregation Principle (ISP)**
831950
### **Dependency Inversion Principle (DIP)**

0 commit comments

Comments
 (0)