2.4 SOLID Principles
Introduction
The SOLID design principles are a set of principles for the design of classes, functions and programs in Object-oriented software development. SOLID is an acronym for the five core principles:
- Single purpose
- Open/Close principle
- Liskov Substitution
- Interface Segregation
- Dependency Inversion
[S]ingle Purpose
When designing a function or class, it should serve a single purpose.
This principle is closely related to that of Strong Cohesion. It is important, when designing functions and classes, to be very modular in your approach. Functions and classes should be specialized in terms of cohesion and focus on solving a specific problem, but generic in the solution to the problem, so as to be useful in solving similar problems.
[O]pen/Close Principle
An abstract class should be OPEN to extension through inheritance and CLOSED to modification.
When specific situations arise where code is needed to be added to accommodate a certain condition, it should be added to the appropriate child class, and not tacked on to the parent class. The functionality of the parent class should never be modified to accommodate functionality relating to the specialization of a child class. For example, if you find yourself putting in an IF statement into the parent class which would only run code in the case of a specific child class, you have broken the Open/Close principle.
[L]iskov Substitution
A pointer to an object of a parent class should be able to be replaced with a pointer to a child object without affecting functionality of the code.
When polymorphism is used, the purpose of the parent function should be maintained. For example, if we have a parent class "Motor Vehicle" and we have the child classes "Car" and "Motor Bike". If the two child classes overload the engine_start() function, it should not result in anything other than starting a motor. The idea of the Liskov Substitution principle is that you should be able to substitute a variable pointing to the parent with a variable pointing to a child of the parent, and have the code still work as expected. This cannot happen if the parent's functions are overloaded in a way which is inconsistent with the intention or purpose of the parent function.
[I]nterface Segregation
Only fundamentally necessary function definitions should be included in an interface.
When defining interfaces, rather create multiple interfaces with fewer required functions to implement than a single interface with a large number of functions to implement. In addition, only specify the functions that are absolutely necessary to implement, and no more.
[D]ependancy Inversion
Abstract classes should never depend on implementation details of concrete classes.
The goal of the dependency inversion principle is to decouple application glue code from application logic. Thus, the concrete child classes should be able to be interchanged without the abstract parent classes being affected.
Exercises
In each of the following code examples, determine the design principle which has been broken, describe why this is the case, and provide the corrected code which adheres to all design principles.
-
Example One
import time
def calculate_age(): # Get date of birth from the user dob = "" while dob == "": user_input = raw_input("What is your date of birth? YYYY-MM-DD? ") if len(user_input) == 10: dob = user_input else: print "Please enter in your date of birth in the correct format."
# Split the date into its parts year = dob[:4] month = dob[5:7] day = dob[8:10] # Get Today's Date this_year = time.strftime("%Y") this_month = time.strftime("%m") this_day = time.strftime("%d") # Calculate Number of Years Difference diff_years = this_year - year diff_month = this_month - month diff_day = this_day - day if (diff_day < 0): diff_month = diff_month - 1 diff_day = 30 + diff_day if (diff_month < 0): diff_years = diff_years - 1 diff_month = diff_month + 12 # Display the precise age print "You are %d years, %d months, and %d days old" % (diff_year, diff_month, diff_day)
-
Example Two
class User:
username = "" password = "" type = "" def __init__(self, username, password, type): self.username = username self.password = password self.type = type def get_username(self): return self.username def authenticate(self, password): if self.password == password return True else: return False def get_type(self): return self.type def has_authority(self): if self.get_type() == "Admin": return True else: return False def change_password(self, old_password, new_password): if self.password == old_password: self.password = new_password else return False
class Administrator(User):
admin_access_level = 0 def set_level(self, access_level): self.admin_access_level = access_level
class Employee(User):
email = "" office_tel = "" def set_email(self, email): self.email = email def set_office_tel(self, tel): self.office_tel = tel
users = [ Administrator("bob", "builder", "admin"), Employee("eddie", "banana", "employee"), Employee("fred", "chopsticks", "employee") ] for u in users: print "User: %s" % (username) if u.has_authority(): print "Admin access granted" else: print "Limited access"
-
Example Three
class Device:
device_id = 0 type = "" def __init__(self, id, type): self.device_id = id self.type = type def get_cick(self): if (self.type == "PC" or self.type == "Server"): return get_mouse_click() elif (self.type == "Touch screen"): return get_touch() else: return False
class PC:
def __init__(self, id): self.device_id = id self.type = "PC"
class iPad:
def __init__(self, id): self.device_id = id self.type = "Touch screen"
devices = [ PC(1), iPad(2), PC(3) ] for d in devices: d.get_click()