# 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.