Open-Closed
00:00 In the previous lesson, I described the single-responsibility principle. In this lesson, I’ll talk about the open-closed principle. The open-closed principle, or OCP to its friends.
00:13 You down with OCP? Yeah, you know me. Okay, now I have to apologize twice. First, for doing that while being as pasty white as I am. Truly, I put mayonnaise to shame.
00:24 And second, for mangling the name of a thirty-year-old R & B hit and including it in an object-oriented programming course. Where was I? The open-closed principle is as follows: software entities should be open for extension but closed for modification.
00:42 In this case, open means I should be able to modify it without breaking anything dependent on it. In classes, this typically means any subclass shouldn’t break if I touch the parent class.
00:54 Closed for modification means the interface is stable. If someone is extending my class, the base class should be as stable as possible. Otherwise, it will muck with the subclass. Essentially, if adding features means you end up having to redesign your structure, you’re probably not embodying this principle.
01:14 Let’s look at a violation of OCP and then a better approach.
01:20
Another object-oriented day, another example using shapes. If you took part two of the course, you’ll remember it’s the law. This Shape
class is a bit of a factory abstraction. To create a circle or rectangle, you pass in the type of the shape you want, and the class becomes a generic wrapper to it. Of course, if you take this approach, your code is going to be littered with if it’s a rectangle, then do …
01:48
The same goes for circle. This mess isn’t just in .__init__()
, but any method that needs to understand the difference between the types of shapes is going to have the same big if
then else
block.
02:01
Besides being a bit ugly, how does this violate the open-closed principle? Well, think about adding a triangle. I now have to go back and do surgery on every one of the methods, adding an if
… else
"triangle"
clause.
02:14 This violates the closed for modification part of the open-closed principle. If someone extended this class and implemented, say, a perimeter method, they’d have to be aware of the internal mechanisms of the parent.
02:29 This violates the open part of open-closed. If I add the triangle to the parent, the child’s perimeter method would now be broken.
02:42
A better way to do this is to split out circles and rectangles into their own classes. If you want to enforce the interface that Shape
should provide, you can do that through an abstract base class in Python.
02:55
By inheriting from ABC
, I get to use the @abstractmethod
decorator inside the class. When I use that to wrap a method, it tells the extender they must implement this method. If they forget, they’ll get an exception the first time they go to instantiate their class.
03:13
By separating the code out into different classes, you get another advantage. The previous version of Shape
needed to use the **kwargs
mechanism for initial arguments because different shapes have different needs.
03:25
By using a dedicated Circle
class, I can be very specific about its need for a radius and only a radius.
03:32
If I still want the shape-type information, I can keep that in the parent and use super().__init__()
to populate it. In this simple example, you really don’t need the shape-type info, as the class makes itself evident, but I left it there so you could see an example of the base class requiring an argument.
03:50
The .calculate_area()
method now is specific to a circle without any extra if
then else
cruft.
03:58 The same goes for our rectangle. Its constructor can use width and height, and it can implement its own area formula.
04:09
If you’re finding you are violating the open-closed principle, you typically can fix it by reexamining your class hierarchy or sometimes just introducing one. You saw me use an abstract base class and inheritance to solve the Shape
class problem.
04:24 You might also use delegation or dependency injection as well. Both of these are ways of doing composition that keeps the implementation objects isolated.
04:34 If you’re coming from another language besides Python and you skipped part two of this course, you may not have seen an abstract base class before. Python implements the concept as an actual class rather than as part of the language’s syntax.
04:50
One way to consider whether you’ve nailed the open-closed principle is to think about the consequences of adding something with the better implementation of the Shape
class. Adding a new color attribute is fine.
05:02
It would just get passed to the children without breaking anything. Likewise, I can tell that it’s closed, as adding Triangle
would mean a new class and wouldn’t require surgery on Shape
, Circle
, or Rectangle
to get it to work.
Become a Member to join the conversation.