# The Dependency Inversion Principle (DIP) ![Alt text](DI_cartoon.png) 1. High-level modules should not depend on low-level modules. Both should depend on abstractions. 2. Abstractions should not depend on details. Details should depend on abstractions. _- Robert C Martin_ The core takeaway from this definition is: The DIP splits the dependency between the high-level and low-level modules by introducing an abstraction between them. ![Alt text](DIP.png) ## Bad Practice ```py class Developer(object): def __init__(self): print ("developer added") class Designer(object): def __init__(self): print ("designer added") class Manager(object): def __init__(self): self.developers=[] self.designers=[] self.testers=[] def addDeveloper(self,dev : Developer): self.developers.append(dev) def addDesigners(self,design : Designer): self.designers.append(design) if __name__ == "__main__": a=Manager() a.addDeveloper(Developer()) a.addDesigners(Designer()) ``` Bad Practise, because _Manager_ depends on definitions of other "low-level" objects. ## Good Practice ```py from abc import ABC,abstractmethod class Employee(ABC): # Could be named 'IWorkable' @abstractmethod def work(self): pass class Manager(Employee): def __init__(self): self.employees=[] def addEmployee(self,e : Employee): self.employees.append(e) def work(self): # delegate tasks to employees: for e in self.employees: e.work() class Developer(Employee): def __init__(self): print ("developer added") def work(self): print ("turning coffee into code") class Designer(Employee): def __init__(self): print ("designer added") def work(self): print ("turning lines to wireframes") if __name__ == "__main__": a=Manager() a.addEmployee(Developer()) a.addEmployee(Designer()) a.work() ``` Good Practise: _Manager_ and employees depend only on abstraction _Employee_ There are many different approaches to use the DIP in different programming languages. However, an example for a straightforward, standard approach is given below. It is C++ but could be easily implemented other OO languages as well. ```c++ #include #include struct IMovable { // Interface for movements of objects virtual void move() = 0; }; class Animal { std::unique_ptr move_; std::string name; public: Animal(std::string name_, std::unique_ptr move) // lifecycle of move - Object is now bound to Animal : move_(std::move(move)), name(name_) {} std::string get_name() const { return name; } void move() { // delegate to 'IMovable' return move_->move(); } }; struct swim : public IMovable { void move() override { std::cout << "swim"; } }; struct gallop : public IMovable { void move() override { std::cout << "gallop"; } }; int main() { auto move_animal = [](Animal& animal) { std::cout << "Move Animal " << animal.get_name() << " : "; animal.move(); std::cout << "\n"; }; move_animal(Animal("Marty", std::make_unique())); move_animal(Animal("Dory", std::make_unique())); return 0; } ``` ## Good Practice @ LE A use case for dependency inversion at LE is the usage of SMILE Models in _OPTISIM_ or _UNISIM_. SMILE Models _inject_ a new functionality (i.e. a unit model) in these simulators. Lets assume there is a SMILE Model (DLL) _Membrane_. As it is a SMILE model, the model evaluation (residual or outgoing values) are made available by the [SMILE_MODEL_DLL_API](https://dev.azure.com/LindeEngineering/ITP-Smile/_git/M-SMILE?path=%2Finclude%2FSMILE_Model_DLL_API.hpp&version=GBmaster&_a=contents). Physical Properties are accessed using the [LINDE_OPTISIM_PROPERTIES_API](https://dev.azure.com/LindeEngineering/Optisim/_git/M-OS?path=/SRC/PROPERTIES/LINDE_OPTISIM_PROPERTIES/LINDE_OPTISIM_PROPERTIES_V1.h). ### Dependencies in OPTISIM ```{graphviz} digraph G{ rankdir = BT; subgraph { OPTISIM -> SMILE_MODEL_DLL_API; Membrane -> SMILE_MODEL_DLL_API; OPTISIM -> LINDE_OPTISIM_PROPERTIES_API; Membrane -> LINDE_OPTISIM_PROPERTIES_API; {rank = same; SMILE_MODEL_DLL_API;} {rank = same; OPTISIM; Membrane; } } } ``` ### Dependencies in UNISIM The same model (DLL) _Membrane_ can be used in UNISIM. Then, the dependencies are ```{graphviz} digraph G{ rankdir = BT; UNISIM -> "unisimdesign.hh" UNISIM_SMILE_Extn -> "unisimdesign.hh"; UNISIM_SMILE_Extn -> SMILE_MODEL_DLL_API; Membrane -> SMILE_MODEL_DLL_API; "LINDE_OPTISIM_PROPERTIES_V1.dll" -> LINDE_OPTISIM_PROPERTIES_API; Membrane -> LINDE_OPTISIM_PROPERTIES_API; } ``` ## Bad Practice @ LE It would be advantageous if the physical property calculation used by OPTISIM would be actually losely coupled - that would facilitate replacing it at some point considerably.