kenu / solid star
dboyliao 2015-11-25
Add code for Liskov Substitution Principle.
@80812924a4279a82b0441b81f45d910a4bad9903
README.md
--- README.md
+++ README.md
@@ -10,7 +10,10 @@
         - [link](http://joelabrahamsson.com/a-simple-example-of-the-openclosed-principle/)
     - `L`: Liskov Substitution Principle.
         - [link](http://www.objectmentor.com/resources/articles/lsp.pdf)
+        - [Stack Overflow](http://stackoverflow.com/questions/56860/what-is-the-liskov-substitution-principle)
         - [Circle-Ellipse Problem](https://en.wikipedia.org/wiki/Circle-ellipse_problem)
+        - **Moral of the story: model your classes based on behaviours not on properties; model your data based on properties and not on behaviours. If it behaves like a duck, it's certainly a bird.**
+        - **This strongly suggests that inheritance should never be used when the sub-class restricts the freedom implicit in the base class, but should only be used when the sub-class adds extra detail to the concept represented by the base class as in 'Monkey' is-an 'Animal'.**
     - `I`:
     - `D`:
 
@@ -18,3 +21,4 @@
 
 - [Programming Done By Superstition](https://utcc.utoronto.ca/~cks/space/blog/programming/ProgrammingViaSuperstition)
 - [Object Mentor](http://www.objectmentor.com/resources/publishedArticles.html)
+- [Clean Coder](http://cleancoders.com/category/fundamentals)
 
python_code/bad/ISP.py (added)
+++ python_code/bad/ISP.py
@@ -0,0 +1,0 @@
 
python_code/bad/LSP.py (added)
+++ python_code/bad/LSP.py
@@ -0,0 +1,48 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# The violation of LSP here is that a `Prisoner` is not a suitable 
+# substitution of `Person` since they "behave" differently.
+# Remember, the principle is that you should model your class according
+# to their behavior rather than porperties. See "Circle-Ecllipse Problem"
+# for detail.
+import copy
+
+class Person(object):
+
+    def __init__(self, position):
+        self.position = position
+
+    def walk_North(self, dist):
+        self.position[1] += dist
+
+    def walk_East(self, dist):
+        self.position[0] += dist
+
+# `Prisoner` is a logicall natural extension of `Person`
+# since they fulfill the "is-a" relation: a `Prisoner` is a `Person`.
+# However, such extension violate LSP in this case.
+class Prisoner(Person):
+    PRISON_LOCATION = [3, 3]
+
+    def __init__(self):
+        super(Prisoner, self).__init__(copy.copy(self.PRISON_LOCATION))
+        self.is_free = False
+
+# The issue here is that `Prisoner` inherite `walk_North` and `walk_East` methods
+# from the `Person` which is not logically correct for the `Prisoner` class.
+
+def main():
+    prisoner = Prisoner()
+    print "The prisoner trying to walk to north by 10 and east by -3."
+    
+    try:
+        prisoner.walk_North(10)
+        prisoner.walk_East(-3)
+    except:
+        pass
+    
+    print "The location of the prison: {}".format(prisoner.PRISON_LOCATION)
+    print "The current position of the prisoner: {}".format(prisoner.position)
+
+if __name__ == "__main__":
+    main()(No newline at end of file)
 
python_code/good/ISP.py (added)
+++ python_code/good/ISP.py
@@ -0,0 +1,0 @@
 
python_code/good/LSP.py (added)
+++ python_code/good/LSP.py
@@ -0,0 +1,42 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# As we can see in `python_code.bad.LSP` module where a violation
+# of LSP may lead to an unexpected behaviour of sub-types. In our 
+# example, "is-a" relation can not directly applied to `Person` and 
+# `Prisoner`. The cause is that these two classes "behave" differently.
+# How to fix it? Maybe a better naming will do the trick:
+
+class FreeMan(object):
+
+    def __init__(self, position):
+        self.position = position
+
+    def walk_North(self, dist):
+        self.position[1] += dist
+
+    def walk_East(self, dist):
+        self.position[0] += dist
+
+# "is-a" relationship no longer holds since a `Prisoner` is not a `FreeMan`.
+class Prisoner(object):
+    PRISON_LOCATION = (3, 3)
+
+    def __init__(self):
+        self.position = type(self).PRISON_LOCATION
+
+def main():
+
+    prisoner = Prisoner()
+    print "The prisoner trying to walk to north by 10 and east by -3."
+    
+    try:
+        prisoner.walk_North(10)
+        prisoner.walk_East(-3)
+    except:
+        pass
+    
+    print "The location of the prison: {}".format(prisoner.PRISON_LOCATION)
+    print "The current position of the prisoner: {}".format(prisoner.position)
+
+if __name__ == "__main__":
+    main()(No newline at end of file)
Add a comment
List