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. + +
+ +
+ +That will be more simple to do extension. + +### Liskov Substitution Principle + +This convention tells us to create a substitutable subclass for their parents. +So, if you want to create an object in the subclass, you have to be able to pass it in the interface. + +Let's make an example ! +We will write a _Walk_ class and his subclass, _Jump_ class. +```python +class Walk(abc.ABC): + def __init__(self): + abc.ABC.__init__(self) + @abc.abstractmethod + def Speed(self): + pass + @abc.abstractmethod + def Ahead(self): + pass + '''the player walk ahead''' + + @abc.abstractmethod + def Behind(self): + pass + '''the player walk behind''' +``` +And the _Jump_ subclass. +```python +class Jump(Walk): + def __init__(self): + pass + + def Speed(self): + pass + + def Ahead(self): + pass + + def Behind(self): + pass +``` +As you can see, the _Jump_ subclass has all abstract method of _Walk_ class. +But we have a big problem ! +The _Jump_ subclass need a new method, the _height_ method. +And he does not need the _Ahead_ and _Behind_ method. +If you remove abstract method and add a new method in _Jump_ subclass, you will be in a big trouble. +This subclass will be different about mother class. +The simple way to resolve this trouble is to create a new mother class for _Walk_ and _Jump_ class. + +```python +class Movement(abc.ABC): + def __init__(self): + abc.ABC.__init__(self) + + @abc.abstractmethod + def Speed(self): + pass + + @abc.abstractmethod + def Animation(self): + pass + + @abc.abstractmethod + def Direction(self): + pass +``` +The _Movement_ class will be the mother class of _Walk_ and _Jump_ classes. +These subclasses will have three methods (_Speed_, _Animation_ and _Direction_ methods). +The problems that we had is resolved with the _Direction_ method. +With this method, you have choices to go ahead, behind, height, etc. + +
+ +Subclasses + +The _Walk_ subclass: + +```python +class Walk(Movement): + def __init__(self): + pass + + def Speed(self): + pass + + def Animation(self): + pass + + + def Direction(self): + pass + +``` +And the _Jump_ subclass: + +```python +class Jump(Movement): + def __init__(self): + pass + def Speed(self): + pass + + def Animation(self): + pass + + def Direction(self): + pass + +``` + +
+ +### Interface Segregation Principle + +The _Interface Segregation Principle_ means that all interfaces have to be separated. +That means that clients has not functions that they do not need. + +OK ! So, let's make an example with client manager. + +```python +class GoodCustomer(abc.ABC): + def __init__(self): + abc.ABC.__init__(self) + + @abc.abstractmethod + def FirstName(self): + pass + @abc.abstractmethod + def LastName(self): + pass + @abc.abstractmethod + def Address(self): + pass + + @abc.abstractmethod + def Work(self): + pass +``` +The _GoodCustomer_ mother class is a class where good customer information are saved. + +```python +class BadCustomer(GoodCustomer): + def __init__(self): + pass + + def FirstName(self): + pass + + def LastName(self): + pass + + def Address(self): + pass + + def Work(self): + pass +``` +The _BadCustomer_ subclass is a class where bad customer information are saved. +For this example, we don't want to know the addresses of bad guys. +So what can we do ? + +You have to create a new mother class for these two classes like the preceding example. +### Dependency Inversion Principle + +This convention says that all classes that we use depends on the interface or abstract classes. +Like precedents example, all methods was abstracts. + +>The Dependency Inversion principle states that our classes should depend upon interfaces or abstract classes instead of concrete classes and functions. +>We want our classes to be open to extension, so we have reorganized our dependencies to depend on interfaces instead of concrete classes. \ No newline at end of file diff --git a/assets/UML_Solid b/assets/UML_Solid new file mode 100644 index 0000000..a2581d6 Binary files /dev/null and b/assets/UML_Solid differ