diff --git a/Intro_to_Solid.md b/Intro_to_Solid.md new file mode 100644 index 0000000..a2c228c --- /dev/null +++ b/Intro_to_Solid.md @@ -0,0 +1,341 @@ +# Object-Oriented class design + + +## SOLID +SOLID coding is a principle created by Robert C.Martin, he is a famous computer scientist. +SOLID is an acronym for his five conventions of coding. +With their conventions, you can improve the structure of your code, reduce time to implement changes and technical debts, etc. +It is a collection of best practices. +And it was developed through this decade. +Principles of SOLID acronym are: + +* The Single-Responsibility Principle (**SRP**) +* The Open-Closed Principle (**OCP**) +* The Liskov Substitution Principle (**LSP**) +* The Interface Segregation Principle (**ISP**) +* The Dependency inversion Principle (**DIP**) + +The first convention is **SRP**, that means that all classes of your code must do one thing. +That is an important principle. +That is the best way to work with others people in the same project. +Version control is easier, +You will never have _Merge conflicts_, because other people work in other operations. +So, he will never have two same things in the code. + +### Single-Responsibility +Let's start something ! +We will make common mistake that violate **SRP** and correct them. +Let's code a bookstore invoice. + +```python +class Book(object): + def __init__(self, name, authorName, year, price, isbn): + self.name = name + self.authorName = authorName + self.year = year + self.price = price + self.isbn = isbn +``` + +As you can see, there is a class named Book with some fields. +This fields are public and characterize a book. + +OK ! Now we can start the invoice class. +This class will calculate the final price for a customer. + +```python +class Invoice(object): + def __init__(self, book, quantity, discountRate, taxRate, total): + self.book = book + self.quantity = quantity + self.discountRate = discountRate + self.taxRate = taxRate + self.total = total + + + def calculateTotal(self): + self.price = ((self.book.price - self.book.price * self.discountRate)*self.quantity) + + self.priceWithTaxes = self.price * (1 + self.taxRate) + + return self.priceWithTaxes + + def printInvoice(self): + print(self.quantity, "x", self.book.name,"", self.book.price, "$"); + print("Discount Rate: ", self.discountRate) + print("Tax Rate: ", self.taxRate) + print("Total: ", self.total) + + + def saveToFile(self, fileName): + pass + +``` + +Alright, now we have the _Invoice_ class, he had 3 methods (calculateTotal, printInvoice, saveToFile) and some fields too. +Why this code violate the first convention of **SOLID** ? + + +The _printInvoice_ method violate this one because the **SRP** told us to make just one thing per classes. +Here, our printing logic is in the same class than _calculateTotal_ method. +So, the printing logic is mixed with business logic in the same class. +As you think, the _saveToFile_ method violate this convention too. + +Let's correct this example. +```python +class InvoicePrinter(object): + def __init__(self, invoice): + self.invoice = invoice + def printInvoice(self): + print(self.invoice.quantity, "x", self.invoice.book.name,"", self.invoice.book.price, "$"); + print("Discount Rate: ", self.invoice.discountRate) + print("Tax Rate: ", self.invoice.taxRate) + print("Total: ", self.invoice.total) +``` + +```python +class InvoicePersistence(object): + def __init__(self, invoice): + self.invoice = invoice + + def saveToFile(self): + pass +``` + +We have now two others classes, _InvoicePrinter_ class and _InvoicePersistence_ class. +The _InvoicePrinter_ is used to print information. +And the _InvoicePersistence_ is used to save information. + +With these three classes, we respect the first principle of **SOLID**. + +### Open-Closed Principle + +This principle says that classes are open for extension and closed to modification. +Extension mean news functionalities and modification mean modifying your code. +If you want to add new functionalities, you are able to add it without manipulating the existing program. +If you touch the existing code, you have a risk to have news bugs. +So, if you want to add something else, you can use abstract classes and help of interface. + +Ok so, let's add new functionality in the _InvoicePersistence_ class. + +```python +class InvoicePersistence(object): + def __init__(self, invoice): + self.invoice = invoice + + def saveToFile(self): + pass + + def saveToDataBase(self): + pass +``` +The _saveToDataBase_ method is used to save information in a Data Base. +We have modified the _InvoicePersistence_ class. +And this class will be more difficult to make easily extendable. +So, we violate the **OCP** convention. +If you want to respect this principle, you have to create a new class. + +```python +class InvoicePersistence(abc.ABC): + @abstractmethod + def save(self, invoice): + pass +``` +The new _InvoicePersistence_ class has an abstract method. +So, if a class inherits the _InvoicePersistence_ class, you have to implement the _save_ method. + +And for example, we will create a _DataBasePersistence_ class, and this class inherits the abstract _InvoicePersistence_ class. +```python +class DatabasePersistence(InvoicePersistence): + def __init__(self, invoice): + self.invoice = invoice + self.save(self.invoice) + + def save(self, invoice): + print("Save in database ...") +``` +Let's do the same thing with _FilePersistence_ class. +```python +class FilePersistence(InvoicePersistence): + def __init__(self, invoice): + self.invoice = invoice + self.save(self.invoice) + + def save(self, invoice): + print("Save to file ...") +``` +Ok, we do a lot of things, let's make a UML to represent our class structure. + +