What is the principle of dependency inversion and why is it important? Dependency Inversion Principle Dependency Inversion

In fact, all principles SOLID are strongly interconnected and their main goal is to help create high-quality, scalable software. But the last principle SOLID really stands out against them. First, let's look at the formulation of this principle. So, dependency inversion principle (Dependency Inversion Principle - DIP): “Dependence on abstractions. There is no dependence on anything specific.. Notorious software development expert, Robert Martin, also highlights the principle DIP and presents it simply as the result of following other principles SOLID— the open/closed principle and the Liskov substitution principle. Recall that the first says that the class should not be modified to make new changes, and the second deals with inheritance and assumes the safe use of derived types of some base type without breaking the correct operation of the program. Robert Martin originally formulated this principle as follows:

one). Upper-level modules should not depend on lower-level modules. Modules at both levels must depend on abstractions.

2). Abstractions should not depend on details. Details should depend on abstractions.

That is, it is necessary to develop classes in terms of abstractions, and not their specific implementations. And if you follow the principles OCP and LSP, then this is exactly what we will achieve. Therefore, let's go back a little to the lesson on. There, as an example, we considered the class bard, which at the very beginning was hardwired to the class Guitar, representing a specific musical instrument:

public class Bard ( private Guitar guitar; public Bard(Guitar guitar) ( this.guitar = guitar; ) public void play() ( guitar.play(); ) )

public class Bard(

private guitar guitar;

public Bard (Guitar guitar )

this. guitar = guitar ;

public void play()

guitar. play();

In case we wanted to add support for other musical instruments, then we would somehow have to modify this class. This is a clear violation of the principle OCP. And you may have already noticed that these are also violations of the principle DIP, since in our case our abstraction turned out to be dependent on details. From the point of view of further expansion of our class, this is not good at all. To make our class meet the conditions of the principle OCP we have added an interface to the system instrument, which implemented specific classes representing certain types of musical instruments.

File Instrument.java:

public interface Instrument (void play(); )

public interface Instrument(

voidplay();

File Guitar.java:

class Guitar implements Instrument( @Override public void play() ( System.out.println("Play Guitar!"); ) )

class Guitar implements Instrument(

@Override

public void play()

System. out . println("Play Guitar!");

File lute.java:

public class Lute implements Instrument( @Override public void play() ( System.out.println("Play Lute!"); ) )

public class Lute implements Instrument(

@Override

public void play()

System. out . println("Play Lute!" ) ;

After that we changed the class bard so that, if necessary, we can replace the implementations with exactly those that we need. This brings additional flexibility to the system being created and reduces its cohesion (strong class dependencies on each other).

public class Bard ( private Instrument instrument; public Bard() ( ) public void play() ( instrument.play(); ) public void setInstrument(Instrument instrument) ( this.instrument = instrument; ) )

public class Bard(

private Instrument instrument ;

2 replies

Good point - the word inversion is somewhat surprising (since after applying DIP , the lower level dependency module apparently doesn't now depend on the higher level caller module: either the caller or the dependent is now more loosely coupled via an additional abstraction).

You may ask why I use the word "inversion". Frankly, this is because more traditional software development methods such as structured analysis and design tend to create software structures in which high-level modules depend on low-level modules and in which abstractions depend on details. In fact, one of the purposes of these methods is to define a hierarchy of subprograms that describes how high-level modules make calls to low-level modules.... Thus, the dependency structure of a well-designed object-oriented program is "inverted" with respect to the dependency structure, which is usually the result of traditional procedural methods.

One point to note when reading Uncle Bob's paper on DIP is that C++ does not (and at the time of writing, does not) have interfaces, so achieving this abstraction in C++ is usually achieved through abstract/pure virtual base class, whereas in Java or C# the abstraction to loosen the coupling is usually to unbind by abstracting the interface from the dependency and binding the higher level module(s) to the interface.

Edit Just to clarify:

"In some place I also see it's called dependency inversion"

Inversion: Invert dependency management from application to container (like Spring).

Dependency Injection:

Instead of writing a factory pattern, how about injecting the object directly into the client class. So let's let the client class refer to the interface and we should be able to inject the concrete type into the client class. With this, the client class does not need to use the new keyword and completely separated from concrete classes.

What about inversion of control (IoC)?

In traditional programming, the flow of business logic is defined by objects that are statically assigned to each other. With inversion of control, flow depends on an object graph that is instantiated by the assembler and made possible by object interactions defined through abstractions. The bundling process is achieved through dependency injection, although some argue that using a service locator also provides inversion of control.

Inversion of control as a design guide serves the following purposes:

  • There is a decoupling of the execution of a particular task from the implementation.
  • Each module can focus on what it is intended for.
  • Modules don't make any assumptions about what other systems do, but rely on their contracts.
  • Replacing modules does not affect other modules.

See for more information.

Last update: 03/11/2016

Dependency Inversion Principle(Dependency Inversion Principle) is used to create loosely coupled entities that are easy to test, modify and update. This principle can be formulated as follows:

Top-level modules should not depend on lower-level modules. Both must depend on abstractions.

Abstractions should not depend on details. Details should depend on abstractions.

To understand the principle, consider the following example:

Class Book ( public string Text ( get; set; ) public ConsolePrinter Printer ( get; set; ) public void Print() ( Printer.Print(Text); ) ) class ConsolePrinter ( public void Print(string text) ( Console.WriteLine (text); ) )

The Book class, which represents a book, uses the ConsolePrinter class to print. When defined like this, the Book class depends on the ConsolePrinter class. Moreover, we have rigidly defined that printing a book is possible only on the console using the ConsolePrinter class. Other options, for example, output to a printer, output to a file, or using some graphical interface elements - all this is in this case ruled out. The book printing abstraction is not separate from the details of the ConsolePrinter class. All this is a violation of the dependency inversion principle.

Now let's try to bring our classes into line with the dependency inversion principle by separating abstractions from low-level implementation:

Interface IPrinter ( void Print(string text); ) class Book ( public string Text ( get; set; ) public IPrinter Printer ( get; set; ) public Book(IPrinter printer) ( this.Printer = printer; ) public void Print( ) ( Printer.Print(Text); ) ) class ConsolePrinter: IPrinter ( public void Print(string text) ( Console.WriteLine("Print to Console"); ) ) class HtmlPrinter: IPrinter ( public void Print(string text) ( Console.WriteLine("Print to html"); ) )

Now the book printing abstraction is separated from concrete implementations. As a result, both the Book class and the ConsolePrinter class depend on the IPrinter abstraction. In addition, now we can also create additional low-level implementations of the IPrinter abstraction and apply them dynamically in the program:

Book book = new Book(new ConsolePrinter()); book.Print(); book.Printer = new HtmlPrinter(); book.Print();

14 responses

It basically says:

  • Abstractions should never depend on details. Details should depend on abstractions.

As to why this is important, in a word: changes are risky, and depending on the concept, not the implementation, you reduce the need for change at call sites.

Effectively, DIP reduces the coupling between different parts of the code. The idea is that while there are many ways to implement, say, a logger, how you use it should be relatively stable over time. If you can extract an interface that represents the concept of logging, that interface should be much more stable over time than its implementation, and calling sites should be much less susceptible to changes you might make by keeping or extending that logging mechanism .

Because the implementation depends on the interface, you can choose at runtime which implementation is best for your particular environment. Depending on the case, this can also be interesting.

The books Agile Software Development, Principles, Patterns and Practices and Agile Principles, Patterns and Practices in C# are the best resources for fully understanding the original goals and motivations behind the Dependency Inversion Principle. The Dependency Reversal Principle article is also a good resource, but due to being a condensed version of a draft that ended up in the previously mentioned books, it leaves some important discussions about the concept of package ownership and interfaces that are key to distinguishes this principle from the more general advice "program for the interface, not the implementation" found in Design Patterns (Gamma, et al.).

To summarize, the dependency inversion principle primarily aims at change the traditional direction of dependencies from "higher-level" components to "lower-level" components, so that "lower-level" components depend on interfaces, owned"higher-level" components, (Note: A "higher-level" component here refers to a component that requires external dependencies/services, and not necessarily its conceptual position in a layered architecture.) decreases as much as she is shifting from components that are theoretically less valuable to components that are theoretically more valuable.

This is achieved by developing components whose external dependencies are expressed as an interface, for which the consumer of the component must provide an implementation. In other words, defined interfaces express what the bean needs, not how you use the bean (e.g. "INeedSomething", not "IDoSomething").

What the Dependency Reversal Principle does not refer to is the simple practice of abstracting dependencies with interfaces (like MyService → ). While this separates the bean from the specific implementation detail of the dependency, it does not invert the relationship between the consumer and the dependency (e.g. ⇐ Logger.

The importance of the dependency inversion principle can be reduced to a single goal - the ability to reuse software components that rely on external dependencies for part of their functionality (registration, validation, etc.)

Within this general goal of reuse, we can distinguish two subtypes of reuse:

    Using a software component in multiple applications with dependency implementations (for example, you have developed a DI container and want to provide logging, but do not want to associate your container with a specific logger, so that everyone using your container must also use the logging library of your choice) .

    Using software components in an evolving context (for example, you have developed business logic components that remain the same across application versions where implementation details evolve).

In the first case of reusing components across multiple applications, such as with a framework library, the goal is to provide consumers with the underlying infrastructure without having your consumers tied to your own library's dependencies, since obtaining dependencies from such dependencies requires consumers to also require the same dependencies . This can be problematic when consumers of your library decide to use a different library for the same infrastructure needs (such as NLog and log4net), or if they decide to use a later version of a required library that is not backwards compatible with the version required by your library.

In the second case of reusing business logic components (i.e. "Higher Level Components"), the goal is to isolate the application's implementation in the main scope from the changing needs of your implementation details (i.e. changing/updating persistent libraries, interchange libraries messages). encryption strategies, etc.). Ideally, changing application implementation details should not break the components that encapsulate the application's business logic.

Note. Some may object to describing this second case as actual reuse, believing that components such as business logic components used in a single evolving application represent only one use. The idea here, however, is that every change in the implementation details of the application maps a new context and therefore a different use case, though ultimate goals can be distinguished as isolation and portability.

While following the dependency inversion principle in the second case may be of some benefit, it should be noted that its importance as applied to modern languages ​​such as Java and C# has been greatly reduced, perhaps to the point of being irrelevant. As discussed earlier, DIP involves the complete separation of implementation details into separate packages. In the case of an evolving application, however, simply using interfaces defined in terms of the business domain will protect against the need to modify higher-level components due to the changing needs of implementation detail components, even if the implementation details end up in the same package. . This part of the principle reflects aspects that were relevant to the language at the time of its codification (eg C++) that are not relevant to newer languages. However, the importance of the Dependency Inversion Principle is primarily related to the development of reusable software components/libraries.

A more detailed discussion of this principle, as it relates to the simple use of interfaces, dependency injection, and the separated interface pattern, can be found.

When we develop software applications, we can consider low-level classes, classes that implement basic and primary operations (disk access, network protocols, and...) and high-level classes, classes that encapsulate complex logic (business flows,... ).

The latter rely on low level classes. The natural way to implement such structures would be to write low level classes and once we are forced to write complex high level classes. Since high level classes are defined in terms of others, this seems to be the logical way to do it. But it's not a flexible design. What happens if we need to replace a low level class?

The Dependency Inversion Principle states that:

  • High level modules should not depend on low level modules. Both should depend on abstractions.

This principle aims to "reverse" the usual notion that high-level modules in software should depend on lower-level modules. Here, high-level modules own an abstraction (for example, solving interface methods) that are implemented by lower-level modules. Thus, lower-level modules depend on higher-level modules.

Applying dependency inversion effectively gives you flexibility and stability throughout your application's architecture. This will allow your application to develop in a more secure and stable way.

Traditional layered architecture

Traditionally, the user interface of a layered architecture depended on the business layer, and this, in turn, depended on the data access layer.

You must understand a layer, package, or library. Let's see how the code will be.

We would have a library or package for the data access layer.

// DataAccessLayer.dll public class ProductDAO ( )

// BusinessLogicLayer.dll using DataAccessLayer; public class ProductBO ( private ProductDAO productDAO; )

Layered architecture with dependency inversion

Dependency inversion indicates the following:

High level modules should not depend on low level modules. Both should depend on abstractions.

Abstractions should not depend on details. Details should depend on abstractions.

What are high level and low level modules? Thinking of modules like libraries or packages, high-level modules would be those that traditionally have dependencies and low-level ones that they depend on.

In other words, the high level of the module will be where the action is called, and the low level where the action is performed.

From this principle, a reasonable conclusion can be drawn: there should be no dependence between concretions, but there should be a dependence on the abstraction. But according to the approach we're taking, we can misuse investment dependency, but that's an abstraction.

Imagine we adapt our code like this:

We would have a library or package for the data access layer that defines the abstraction.

// DataAccessLayer.dll public interface IProductDAO public class ProductDAO: IProductDAO( )

And other business logic of the library or package level, which depends on the data access level.

// BusinessLogicLayer.dll using DataAccessLayer; public class ProductBO ( private IProductDAO productDAO; )

Although we depend on the abstraction, the relationship between business and data access remains the same.

To get dependency inversion, the persistence interface must be defined in the module or package where the high-level logic or domain resides, not in the low-level module.

First, define what a domain layer is, and the abstraction of its relationship is determined by persistence.

// Domain.dll public interface IProductRepository; using DataAccessLayer; public class ProductBO ( private IProductRepository productRepository; )

Once the persistence level is domain dependent, it is now possible to invert if a dependency is defined.

// Persistence.dll public class ProductDAO: IProductRepository( )

Deepening the principle

It is important to grasp the concept well, deepening the purpose and benefits. If we stay in the mechanics and study a typical repository, we won't be able to determine where we can apply the dependency principle.

But why do we invert the dependency? What is the main goal beyond specific examples?

This is usually allows the most stable things that don't depend on the less stable things to change more often.

The persistence type is easier to change, either database or technology to access the same database, than domain logic or actions designed to be associated with persistence. Because of this, the dependence is reversed, because it is easier to change the persistence if this change occurs. This way we won't have to change the domain. The domain layer is the most stable of all, so it shouldn't depend on anything.

But there is not only this storage example. There are many scenarios where this principle applies, and there are architectures based on this principle.

architecture

There are architectures where dependency inversion is the key to defining it. In all domains, this is the most important, and it is the abstractions that will specify the communication protocol between the domain and the rest of the packages or libraries.

Clean Architecture

For me, the dependency inversion principle described in the official article

The problem with C++ is that header files usually contain private field and method declarations. So if a high-level C++ module contains a header file for a low-level module, it will depend on the actual implementation details of this module. And this is obviously not very good. But this is not a problem in the more modern languages ​​that are commonly used today.

High level modules are inherently less reusable than low level modules because the former are usually more application/context specific than the latter. For example, the component that implements the UI screen is top level and also very (completely?) application specific. Attempting to reuse such a component in another application is counterproductive and can only lead to over development.

Thus, creating a separate abstraction at the same level of component A that depends on component B (which is independent of A) can only be done if component A would actually be useful for reuse across different applications or contexts. If it doesn't, then applying DIP would be bad design.

A clearer way to state the dependency inversion principle is:

Your modules that encapsulate complex business logic should not directly depend on other modules that encapsulate business logic. Instead, they should only depend on interfaces down to simple data.

I.e., instead of implementing your Logic class like people usually do:

Class Dependency ( ... ) class Logic ( private Dependency dep; int doSomething() ( // Business logic using dep here ) )

you should do something like:

Class Dependency ( ... ) interface Data ( ... ) class DataFromDependency implements Data ( private Dependency dep; ... ) class Logic ( int doSomething(Data data) ( // compute something with data ) )

Data and DataFromDependency must live in the same module as Logic , not with Dependency .

Why is this?

good answers and good examples already given by others here.

The point of dependency inversion is to make software reusable.

The idea is that instead of two pieces of code relying on each other, they rely on some abstract interface. You can then reuse any part without the other.

This is usually achieved by inverting a Control Container (IoC) such as Spring in Java. In this model, the properties of the objects are configured through the XML configuration, rather than the objects going out and finding their dependency.

Imagine this pseudocode...

Public class MyClass ( public Service myService = ServiceLocator.service; )

MyClass directly depends on both the Service class and the ServiceLocator class. This is required for both if you want to use it in another application. Now imagine this...

Public class MyClass ( public IService myService; )

MyClass now uses one interface, the IService interface. We would let the IoC container actually set the value of this variable.

Let there be a hotel that will ask the food manufacturer for its supplies. The hotel gives the name of the food (say, chicken) to the Food Generator, and the Generator returns the requested food to the hotel. But the hotel doesn't care about the type of food it receives and serves. Thus, the Generator delivers food labeled "Food" to the hotel.

This implementation in JAVA

FactoryClass with factory method. Food Generator

Public class FoodGenerator ( Food food; public Food getFood(String name)( if(name.equals("fish"))( food = new Fish(); )else if(name.equals("chicken"))( food = new Chicken(); )else food = null; return food; ) )

Class Annotation/Interface

Public abstract class Food ( //None of the child class will override this method to ensure quality... public void quality()( String fresh = "This is a fresh " + getName(); String tasty = "This is a tasty " + getName(); System.out.println(fresh); System.out.println(tasty); ) public abstract String getName(); )

Chicken implements Food (Concrete Class)

Public class Chicken extends Food ( /*All the food types are required to be fresh and tasty so * They won"t be overriding the super class method "property()"*/ public String getName()( return "Chicken"; ) )

Fish sells Food (Concrete Class)

Public class Fish extends Food ( /*All the food types are required to be fresh and tasty so * They won"t be overriding the super class method "property()"*/ public String getName()( return "Fish"; ) )

Finally

Hotel

Public class Hotel ( public static void main(String args)( //Using a Factory class.... FoodGenerator foodGenerator = new FoodGenerator(); //A factory method to instantiate the foods... Food food = foodGenerator.getFood( "chicken"); food.quality(); ) )

As you can see the hotel doesn't know if it's chicken or fish. It is only known that this is a food object, i.e. The hotel depends on the food class.

You might also notice that the Fish and Chicken class implements the Food class and is not directly related to the hotel. those. chicken and fish also depends on the class of food.

This means that the high-level component (hotel) and the low-level component (fish and chicken) depend on the abstraction (food).

This is called dependency inversion.

The Dependency Inversion Principle (DIP) states that

i) High level modules should not depend on low level modules. Both should depend on abstractions.

ii) Abstractions should never depend on details. Details should depend on abstractions.

Public interface ICustomer ( string GetCustomerNameById(int id); ) public class Customer: ICustomer ( //ctor public Customer()() public string GetCustomerNameById(int id) ( return "Dummy Customer Name"; ) ) public class CustomerFactory ( public static ICustomer GetCustomerData() ( return new Customer(); ) ) public class CustomerBLL ( ICustomer _customer; public CustomerBLL() ( _customer = CustomerFactory.GetCustomerData(); ) public string GetCustomerNameById(int id) ( return _customer.GetCustomerNameById(id); ) ) public class Program ( static void Main() ( CustomerBLL customerBLL = new CustomerBLL(); int customerId = 25; string customerName = customerBLL.GetCustomerNameById(customerId); Console.WriteLine(customerName); Console.ReadKey(); ) )

Note. A class should depend on abstractions like an interface or abstract classes, not specific details (interface implementation).

share

The formulation of the dependency inversion principle consists of two rules, the observance of which has an extremely positive effect on the structure of the code:

  • top-level modules should not depend on lower-level modules. Both must depend on the abstraction.
  • abstractions should not depend on details. Details should depend on abstractions.

At first, it doesn’t sound very attractive, and the reader has probably already prepared for the most boring article with a bunch of terms, complex turns of speech and examples, of which nothing is clear anyway. But in vain, because, in principle, subject to the principle of dependency inversion, everything again comes down to the correct use and in the context of the main idea - code reuse.

Another concept that will be relevant here is the weak binding of types, that is, the reduction or elimination of their dependence on each other, which, in fact, is achieved with the help of abstraction and polymorphism. This, in fact, is the essence of the principle of dependency inversion.

Now let's look at an example that will visually demonstrate what loose binding looks like in action.

Let's say we decide to order a birthday cake. To do this, we went to a wonderful bakery on the corner of the street. We found out if they could bake a cake for us under the loud name “Luxury”, and having received a positive answer, we ordered it. Everything is simple.

And now let's decide what abstractions need to be provided in this code. To do this, just ask yourself a few questions:

  • Why a cake? You could also order cake or cupcakes.
  • Why birthday? What if it was a wedding or graduation?
  • Why exactly “Luxury”, and what if I like “Anthill” or “Prague” more?
  • Why to this particular bakery, and not to a pastry shop in the city center or somewhere else?

And each of these “what if” and “what if” are the points at which code extensibility is required, and, accordingly, loose coupling and type definition through abstractions.

Now let's take a look at the construction of the types themselves.

Let's define an interface for any confectionery masterpiece that we could order.

Interface IPastry ( string Name ( get; set; ) )

And here is a specific implementation, for our case - a birthday cake. As you can see, traditionally a birthday cake involves candles)))

Class BirthdayCake: IPastry ( public int NumberOfCandles ( get; set; ) public string Name ( get; set; ) public override string ToString() ( return String.Format("(0) with (1) nice candles", Name, NumberOfCandles ) ; ) )

Now if we need a cake for a wedding or just for tea, or we want cupcakes or custards, we have a basic interface for everyone.

The next question is how confectionery products differ from each other (except for the name, of course). Of course, the recipe!

And the concept of a recipe includes a list of ingredients and a description of the cooking process. Therefore, for the concept of ingredient, we will have a separate interface:

Interface IIngredient ( string IngredientName ( get; set;) double Quantity ( get; set; ) string Units ( get; set; ) )

And here are the ingredients for our cake: flour, butter, sugar and cream:

Class Flour:IIngredient ( public string IngredientName ( get; set; ) public double Quantity ( get; set; ) public string Units ( get; set; ) public string Quality ( get; set; ) ) class Butter: IIngredient ( public string IngredientName ( get; set; ) public double Quantity ( get; set; ) public string Units ( get; set; ) ) class Sugar: IIngredient ( public string IngredientName ( get; set; ) public double Quantity ( get; set; ) public string Units ( get; set; ) public string Kind ( get; set; ) ) class Creme: IIngredient ( public string IngredientName ( get; set; ) public double Quantity ( get; set; ) public string Units ( get; set; ) public double Fat ( get; set; ) )

The list of ingredients may differ in different recipes and different confectionery products, but for our recipe this list is enough.

And now it's time to move on to the concept of the recipe. Whatever we cook, we in any case know what it is called, what it is, what ingredients are included in the dish and how to cook it.

Interface IRecipe ( Type PastryType ( get; set; ) string Name ( get; set;) IList Ingredients ( get;set;) string Description ( get;set;) )

Specifically, the class representing the birthday cake recipe looks like this:

Class BirthdayCakeRecipe: IRecipe ( public Type PastryType ( get; set; ) public string Name ( get; set;) public IList Ingredients ( get; set; ) public string Description ( get; set; ) public BirthdayCakeReipe() ( Ingredients = new List (); } }

Now let's move on to our wonderful bakery on the street corner.

Of course, we could apply to many other bakeries, so we will define a basic interface for it too. And what is the most important thing for a bakery? Ability to bake products.

Interface IBakery ( IPastry Bake(IRecipe recipe); )

And here is the class representing our bakery:

Class NiceBakeryOnTheCornerOFMyStreet ( Dictionary menu = new Dictionary (); public void AddToMenu(IRecipe recipe) ( if (!menu.ContainsKey(recipe.Name)) ( menu.Add(recipe.Name, recipe); ) else ( Console.WriteLine("It is already on the menu"); ) ) public IRecipe FindInMenu(string name) ( if (menu.ContainsKey(name)) ( return menu; ) Console.WriteLine("Sorry...currently we don"t have " + name); return null; ) public IPastry Bake (IRecipe recipe) ( if (recipe != null) ( IPastry pastry = Activator.CreateInstance(recipe.PastryType) as IPastry; if (pastry != null) ( pastry.Name = recipe.Name; return pastry as IPastry; ) ) return null; ) )

It remains only to test the code:

Class Program ( static void Main() ( //creating an inctance of the bakery class var bakery = new NiceBakeryOnTheCornerOFMyStreet(); //preparing ingredients for the recipe var flour = new Flour() ( IngredientName = "Flour", Quantity = 1.5 , Units = "kg" ); var butter = new Butter() ( IngredientName = "Butter", Quantity = 0.5, Units = "kg" ); var sugar = new Sugar() ( IngredientName = "Sugar", Quantity = 0.7 , Units = "kg" ); var creme = new Creme() ( IngredientName= "Creme", Quantity = 1.0, Units = "liters" ); //and this is the recipe itself var weddingCakeRecipe = new BirthdayCakeRecipe() ( PastryType = typeof(BirthdayCake), Name = "Birthday Cake Luxury", Description = "description on how to make a beautiful birthday cake" ); weddingCakeRecipe.Ingredients.Add(flour); weddingCakeRecipe.Ingredients.Add(butter); weddingCakeRecipe.Ingredients .Add(sugar); weddingCakeRecipe.Ingredients.Add(creme); //adding our cake recipe to the bakery"s menu bakery.AddToMenu(weddingCakeRec ipe); //now let"s order it!! BirthdayCake cake = bakery.Bake(bakery.FindInMenu("Birthday Cake Luxury")) as BirthdayCake; //adding some candles ;) cake.NumberOfCandles = 10; //and here we are !!!Console.WriteLine(cake); ) )

Now let's look at the whole code again and evaluate it. The code is quite simple, the types, their abstractions, data and functionality are clearly delineated, the code provides for extension and reuse. Each segment can be painlessly replaced by another corresponding to the base type, and this will not bring down the rest of the code.

You can endlessly add types of ingredients, recipes for different types confectionery, create other classes that describe bakeries, pastry shops and other similar establishments.

Not a bad result. And all thanks to the fact that we tried to make the classes minimally related to each other.

Now let's think about the consequences of violating the dependency inversion principle:

  1. rigidity (it would be very difficult to make any changes to the system, because each change affected many different parts of it).
  2. fragility (when any change is made to one part of the system, other parts of it become vulnerable, and sometimes this is not too obvious at first glance).
  3. immobility (you can forget about reusing code in other systems, since the modules are strongly interconnected).

Well, now draw your own conclusions to what extent the principle of dependency inversion is useful in code. I think the answer is obvious.