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.