Idioms and Patterns Story
Basic DIP
Home  Repo
Top, Bottom

Basic DIP

Minimal demonstration of Dependency Inversion Principle (DIP)


The Dependency Inversion Prinicple states:
High-level components should not depend on low-level components. They should both depend on abstractions.
The principle tells us that a software component should depend on abstract interfaces of components it uses instead of directly binding to the used compenents implementations. To completely sever the using component from a used component, the abstract interface must provide a factory function that removes user creational dependencies.
The code below shows how to do that.
C++ hide ///////////////////////////////////////////// // BasicDIP.cpp // // // // Jim Fawcett, 23 Jan 2021 // ///////////////////////////////////////////// /* Demonstrate Dependency Inversion Principle: "High level modules should not depend on low level modules. Both should depend on abstractions." This demonstration builds a basic demo with self-annunciating low level components. - High level part: Demo<T> - Low level parts: First, Second - Abstraction defined in this package: - trait Say The definitons of First and Second could be changed in any way that is compatible with trait Say without affecting compilation of Demo<T>. */ #include <iostream> using Byte = unsigned short; /*------------------------------------------- Interface Say provides abstraction that Demo<T> uses to avoid depending on types First and Second. */ struct Say { static Say create(); void set_id(Byte id); void say(); }; /*------------------------------------------- - First is a component that Demo<T> depends on when executive (main) declares Demo<First>. - Demo's compilation only depends on Say, not on details of First. */ class First : public Say { public: First() : id_(0) {} Say create() { return First(); } void set_id(Byte id) { id_ = id; } void say() { std::cout << "\n First here with id = " << id_; } private: Byte id_; }; /*------------------------------------------- - Second is a component that Demo<T> depends on when executive (main) declares Demo<Second>. - Demo's compilation only depends on Say, not on details of Second. */ class Second : public Say { public: Second() : id_(0) {} Say create() { return Second(); } void set_id(Byte id) { id_ = id; } void say() { std::cout << "\n Second here with id = " << id_; } private: Byte id_; }; /*------------------------------------------- Demo is high level type that uses low level types First and Second without incurring compilation dependencies on their impl's. */ template<typename T> class Demo { public: void set_id(Byte id) { my_say.set_id(id); } Byte get_id() { return my_say.get_id(); } void say_it() { my_say.say(); } private: T my_say; }; /*------------------------------------------- main() is program executive. It depends directly on Demo, First, and Second. DIP allows reusable component, which Demo<T> pretends to be, to be used in applications without change, even though parts it uses through Say Interface change. */ int main() { std::cout << "\n -- basic_dip demo --\n"; Demo<First> demo1; demo1.set_id(1); demo1.say_it(); Demo<Second> demo2; demo2.set_id(2); demo2.say_it(); std::cout << "\n That's all Folks!\n\n"; } Output "./debug/BasicDIP" -- basic_dip demo -- First here with id = 1 Second here with id = 2 That's all Folks! Rust unhide ///////////////////////////////////////////// // basic_dip::main.rs // // // // Jim Fawcett, 23 Jan 2021 // ///////////////////////////////////////////// /* Demonstrates Dependency Inversion Principle: "High level modules should not depend on low level modules. Both should depend on abstractions." This demonstration builds a basic demo with self-annunciating low level components. - High level part: Demo<T> - Low level parts: First, Second - Abstraction defined in this package: - trait Say The definitons of First and Second could be changed in any way that is compatible with trait Say without affecting compilation of Demo<T>. */ #![allow(dead_code)] /*------------------------------------------- Trait Say provides abstraction that Demo<T> uses to avoid depending on types First and Second. */ pub trait Say { fn new() -> Self; // factory function fn set_id(&mut self, id: u8); fn get_id(&self) -> u8; fn say(&self); } /*------------------------------------------- - First is a component that Demo<T> depends on when executive (main) declares Demo<First>. - Demo's compilation only depends on Say, not on details of First. */ pub struct First { id: u8 } impl Say for First { fn new() -> First { First { id: 0 } } fn set_id(&mut self, id: u8) { self.id = id; } fn get_id(&self) -> u8 { self.id } fn say(&self) { print!( "\n First here with id = {:?}", self.id ); } } /*------------------------------------------- Second is a component that Demo<T> depends on when executive (main) declares Demo<Second>. Demo's compilation only depends on Say, not on details of Second. */ pub struct Second { id: u8 } impl Say for Second { fn new() -> Second { Second { id: 0 } } fn set_id(&mut self, id: u8) { self.id = id; } fn get_id(&self) -> u8 { self.id } fn say(&self) { print!( "\n Second here with id = {:?}", self.id ); } } /*------------------------------------------- Demo is high level type that uses low level types First and Second without incurring compilation dependencies on their impl's. */ struct Demo<T> where T: Say { my_say: T } impl<T> Demo<T> where T: Say { fn new() -> Demo<T> { Demo { my_say: T::new() // factory func } } fn set_id(&mut self, id:u8) { self.my_say.set_id(id); // trait meth } fn get_id(&self) -> u8 { self.my_say.get_id() // trait meth } fn say_it(&self) { self.my_say.say(); // trait meth } } /*------------------------------------------- main() is program executive. It depends directly on Demo, First, and Second. DIP allows reusable component, which Demo<T> pretends to be, to be used in applications without change, even though parts it uses through traits change. */ fn main() { print!("\n -- basic_dip demo --\n"); let mut demo = Demo::<First>::new(); demo.set_id(1); demo.say_it(); let mut demo = Demo::<Second>::new(); demo.set_id(2); demo.say_it(); println!("\n\n That's all Folks!\n\n"); } Output C:\...\DepInvPrinciple\BasicDip> cargo run -q -- basic_dip demo -- First here with id = 1 Second here with id = 2 That's all Folks! C# hide ///////////////////////////////////////////// // basic_dip::Program.cs // // // // Jim Fawcett, 14 Feb 2021 // ///////////////////////////////////////////// /* Demonstrates Dependency Inversion Principle: "High level modules should not depend on low level modules. Both should depend on abstractions." This demonstration builds a basic demo with self-annunciating low level components. - High level part: Demo<T> - Low level parts: First, Second - Abstraction defined in this package: - interface ISay The definitons of First and Second could be changed in any way that is compatible with ISay without affecting compilation of Demo<T>. */ using System; namespace BasicDip_C_ { /*------------------------------------------- Interface ISay provies abstraction that Demo<T> uses to avoid depending on types First and Second. */ public interface ISay { void set_id(uint id); uint get_id(); void say(); } /*------------------------------------------- - First is a component that Demo<T> depends on when executive (main) declares Demo<First>. - Demo's compilation only depends on ISay, not on details of First. */ public class First : ISay { public First() { id_ = 0; } public void set_id(uint id) { id_ = id; } public uint get_id() { return id_; } public void say() { Console.Write( "\n First here with id = {0}", id_ ); } uint id_; } /*------------------------------------------- - Second is a component that Demo<T> depends on when executive (main) declares Demo<Second>. - Demo's compilation only depends on ISay, not on details of Second. */ public class Second : ISay { public Second() { id_ = 0; } public void set_id(uint id) { id_ = id; } public uint get_id() { return id_; } public void say() { Console.Write( "\n Second here with id = {0}", id_ ); } uint id_; } /*------------------------------------------- Demo is high level type that uses low level types First and Second without incurring compilation dependencies on their impl's. */ public class Demo<T> where T : ISay, new() { public Demo() { my_say = new T(); } public void set_id(uint id) { my_say.set_id(id); } public uint get_id() { return my_say.get_id(); } public void say_it() { Console.Write( "\n Demo with id {0} here", get_id() ); my_say.say(); } T my_say; } /*------------------------------------------- main() is program executive. It depends directly on Demo, First, and Second. DIP allows reusable component, which Demo<T> pretends to be, to be used in applications without change, even though parts it uses through interface change. */ class Program { static void Main(string[] args) { Demo<First> df = new Demo<First>(); df.set_id(1); df.say_it(); Console.WriteLine(); Demo<Second> ds = new Demo<Second>(); ds.set_id(2); ds.say_it(); Console.Write( "\n\n That's all Folks!\n\n" ); } } } Output dotnet run -- basic_dip demo --- Demo with id 1 here First here with id = 1 Demo with id 2 here Second here with id = 2 That's all Folks!

4. Epilogue

The following pages provide sequences of code examples for idioms and principles in each of the three languages cited here, e.g. C#, C++, and Rust. Object model differences will often be pointed out in comments within the code blocks.