Compare commits
No commits in common. "598d49935629e27f9575e9c8df11ec9175c6a0a5" and "3fa1ede36935f49a6c03a1bae39b1f81c923b253" have entirely different histories.
598d499356
...
3fa1ede369
|
@ -1,341 +0,0 @@
|
||||||
# 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.
|
|
||||||
|
|
||||||
<center>
|
|
||||||
<img src="./assets/UML_Solid" alt="" width="1000"/>
|
|
||||||
</center>
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>
|
|
||||||
Subclasses
|
|
||||||
</summary>
|
|
||||||
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
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
### 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.
|
|
BIN
assets/UML_Solid
BIN
assets/UML_Solid
Binary file not shown.
Before Width: | Height: | Size: 32 KiB |
Loading…
Reference in New Issue