Principles of object-oriented design by Robert C. Martin
https://gist.github.com/dmmeteo/f630fa04c7a79d3c132b9e9e5d037bfd
There should never be more than one reason for a class to change.
-- Robert C. Martin in "SRP: The Single Responsibility Principle"
A class (method, module) should have exactly one purpose
Separation of Concerns
Avoid the "egg-laying wool-milk-sow":
1 def percentage_of_word_in_localfile(search, file):
2 search = search.lower()
3 content = open(file, "r").read()
4 words = content.split()
5 number_of_words = len(words)
6 occurrences = 0
7 for word in words:
8 if word.lower() == search:
9 occurrences += 1
10 return occurrences / number_of_words
What do you think of this?
1 def read_localfile(file):
2 return open(file, "r").read()
3
4 def number_of_words(content):
5 return len(content.split())
6
7 def count_word_occurrences(word, content):
8 counter = 0
9 for e in content.split():
10 if word.lower() == e.lower():
11 counter += 1
12 return counter
13
14 def percentage_of_word(word, content):
15 total_words = number_of_words(content)
16 word_occurrences = count_word_occurrences(word, content)
17 return word_occurrences/total_words
18
19 def percentage_of_word_in_localfile(word, file):
20 content = read_localfile(file)
21 return percentage_of_word(word, content)
Modules should be both open (for extension) and closed (for modification).
- Bertrand Meyer in Object Oriented Software Construction
1 class Animal:
2 def __init__(self, name: str):
3 self.name = name
4
5 def get_name(self) -> str:
6 pass
7
8 def animal_sound(animals: list):
9 for animal in animals:
10 if animal.name == 'lion':
11 print('roar')
12
13 elif animal.name == 'mouse':
14 print('squeak')
15
16 # client code
17 animals = [
18 Animal('lion'),
19 Animal('mouse')
20 ]
21
22 animal_sound(animals)
1 class Animal:
2
3 ... # __init__ and get_name same as before
4
5 def make_sound(self):
6 pass
7
8 class Lion(Animal):
9 def make_sound(self):
10 return 'roar'
11
12 class Mouse(Animal):
13 def make_sound(self):
14 return 'squeak'
15
16 def animal_sound(animals: list):
17 for animal in animals:
18 print(animal.make_sound())
19
20 # client code
21 animals = [Lion('lion'), Mouse('mouse')]
22 animal_sound(animals)
Subtype Requirement: Let Φ(x) be a property provable about objects x of type T. Then Φ(y) should be true for objects y of type S where S is a subtype of T.
-- Liskov, B. H. & Wing, J. M. in A behavioral notion of subtyping. (1994)
In other words: A base-class must be substitutable by its sub-class (anywhere!)
Implications on method parameters and return types
Clients should not be forced to depend on methods that they do not use.
-- Robert C. Martin in "Agile Software Development: Principles, Patterns, and Practices"
1 class Lamp(object):
2 def __init__(self):
3 self._is_shining = False
4
5 def turn_on(self):
6 self._is_shining = True
7
8 def turn_off(self):
9 self._is_shining = False
10
11 class Switch(object):
12 def __init__(self, lamp: Lamp):
13 self._lamp = lamp
14 self._pressed = False
15
16 def press(self):
17 self._pressed = not self._pressed
18 if self._pressed:
19 self._lamp.turn_on() # <---- PROBLEM IS HERE
20 else:
21 self._lamp.turn_off() # <---- AND HERE
1 #### module: lamp.py
2 import switch
3 class Lamp(Switchable):
4 def __init__(self):
5 self._is_shining = False
6
7 def turn_on(self):
8 self._is_shining = True
9
10 def turn_off(self):
11 self._is_shining = False
1 #### module: switch.py
2 import abc
3
4 class Switchable(object, metaclass=abc.ABCMeta):
5 @abc.abstractmethod
6 def turn_on():
7 pass
8
9 @abc.abstractmethod
10 def turn_off():
11 pass
12
13 class Switch(object):
14 def __init__(self, switched: Switchable):
15 self._switched = switched
16 self._pressed = False
17
18 def press(self):
19 self._pressed = not self._pressed
20 if self._pressed:
21 self._switched.turn_on() # <---- No Ref to concrete implementation
22 else:
23 self._switched.turn_off() # <---- No Ref to concrete implementation
Tasks:
We will write a "filter" that replaces every second pixel value with a black pixel.
We design it as an implementation of a more generic FilterInterface
.
Behavioral
Structural
Creational
1 import abc
2
3 class AbstractAlgorithm(object, metaclass=abc.ABCMeta):
4 """
5 Defines an algorithm template, sub-classes implement part of it.
6 """
7
8 def template_method(self) -> None:
9 """
10 Algorithm skeleton.
11 """
12 self.base_operation()
13 self.required_operation1()
14 self.hook()
15 self.required_operation2()
16
17 # These operations already have implementations.
18
19 def base_operation(self) -> None:
20 print("AbstractAlgorithm says: I am doing some alway necessary preparation work")
1 # These operations have to be implemented in subclasses.
2
3 @abc.abstractmethod
4 def required_operation1(self) -> None:
5 pass
6
7 @abc.abstractmethod
8 def required_operation2(self) -> None:
9 pass
10
11 # These are "hooks." Subclasses may override them optionally. Adds flexibility
12
13 def hook(self) -> None:
14 pass
1 class ConcreteAlgo1(AbstractAlgorithm):
2 """
3 needs to implement all abstract operations of the base
4 class. They can also override some operations with a default implementation.
5 """
6
7 def required_operation1(self) -> None:
8 print("ConcreteAlgo1 says: Implemented Operation1")
9
10 def required_operation2(self) -> None:
11 print("ConcreteAlgo1 says: Implemented Operation2")
12
13 class ConcreteAlgo2(AbstractAlgorithm):
14 # accordingly
1 def client_code(abstract_class: AbstractAlgorithm) -> None:
2 """
3 The client code calls the template method to execute the algorithm. Note
4 that we operate with the interface
5 """
6 # ...
7 abstract_class.template_method()
8 # ...
9
10
11 if __name__ == "__main__":
12 print("Same client code can work with different subclasses:")
13 client_code(ConcreteAlgo1()) # Or: client_code(ConcreteAlgo2())
14 print("")
if elif
blocks• all sub-classes of a class may be obtained from Parent.__sub_classes__()
• you may also check sub-class by issubclass(object, ClassName)
1 assert issubclass(some_object, MyParentClass), "NOT a subclass of MyParentClass"
2
3 # alternatively
4 if type(some_object) not in MyParentClass.__sub_classes()__:
5 raise AssertionError("NOT a subclass of MyParentClass")
Tasks: Study a simple factory - Standalone Exercise
Triangle
type of shapeclass
in class
(Factory
). How can we avoid it?Shape
sub-class? 1 class Singleton:
2 __instance = None # NOTE: class variable!
3
4 @staticmethod
5 def get_instance():
6 if Singleton.__instance is None:
7 Singleton() # first time initialization
8 return Singleton.__instance
9
10 def __init__(self):
11 if Singleton.__instance is not None:
12 # in python we have to raise:
13 raise Exception("Do not call the constructor!! This is a Singleton")
14 else:
15 Singleton.__instance = self
__iter__
and __next__
magic methodsTasks:
Bitmap
class that iterates over all pixels in the bitmap.day-2/exercise-1.py
Tasks:
Write a smoother (filter) that replaces each pixel value with the arithmetic
mean of itself and its eight direct neighbors. We design it as another implementation of
a more generic FilterInterface
Thanks!
Table of Contents | t |
---|---|
Exposé | ESC |
Presenter View | p |
Source Files | s |
Slide Numbers | n |
Toggle screen blanking | b |
Show/hide next slide | c |
Notes | 2 |
Help | h |