• No results found

Dependency Inversion Principle

In document Leanpub.more.Coding.in.Delphi.2015 (Page 38-44)

We’ve already seen the Dependency Inversion Principle in action when we looked at the Single Responsibility Principle. We saw the printing and saving functionality “inverted” by interfaces so that the class in question stopped printing and saving.

The simplest definition I can think of for DIP is the well-known saying “Code against abstractions, not implementations.” Another way to put it is “Always depend on an interface, not an implementation.” A more formal way to say it is “High level modules should not depend on low-level modules. Both should depend on abstractions. Abstractions should not depend upon details. Details should depend upon abstractions.”

I should note here that Dependency Inversion is similar to but not exactly the same as Dependency Injection. For an excellent description of this fine distinction, I suggest that you readthis blog post by Derick Bailey on the Los Techies blog¹².

In fact, the Dependency Inversion Principle and Liskov Substitution Principle are closely aligned, as well: just as you should always be able to plug a air dryer or a lava lamp or any other electrical appliance into the wall socket of your house, you need to always be able to take one interface implementation and replace it with another implementation of that interface. If your appliances are hardwired into the wall, you can’t replace them, just as you can’t replace a concrete class with another one without performing surgery on your class (and thus violating the Open/Closed Principle). Therefore, the only thing that your code should know about is the interface. The implementation should be immaterial to your code.

You have probably heard a lot about the relationship between decoupling and writing clean code. But that is what the DIP is all about – having code that is as loosely coupled as possible. You can’t have no coupling – your classes would never work together. But as we discussed above, you do want your code as thinly coupled as possible, and in Delphi, that is coupling by interfaces.(I’m not telling you anything you don’t already know if you have read Coding in Delphi).

Let’s look at an example. Here’s some code that should be very familiar to you – it depends on concrete instances to do the main work. I see a lot of Delphi code that looks just like this.

¹²http://lostechies.com/derickbailey/2011/09/22/dependency-injection-is-not-the-same-as-the-dependency-inversion-principle/

unit uCoupled;

procedure Compress(aSourceFilename: string; aTargetFilename: string);

end;

procedure TCompressionService.Compress(aSourceFilename: string; aTargetFilename: string);

var

// Here's where you'd compress the file...

Result := aByteArray;

end;

end.

Here is code where the high level module, theTCompressionService, depends on the lower level module,

TFileStream. That isn’t the way it is supposed to work. Here, we are coupled toTFileStreamand have to do

radical surgery on the class if we want to change the way our file handling works. And what if someone needs to compress data in memory or a string? We are violating DIP because we aren’t depending on an abstraction, and we are having the high level module depend on the lower level module. TheCompressmethod actually creates two hard-coded instances ofTFileStream.TFileStreamis a detail, and we shouldn’t be depending on details; we should be abstracting them away instead.

So let’s invert things.

procedure Compress(aReader: IReader; aWriter: IWriter);

end;

implementation

procedure TCompressionService.Compress(aReader: IReader; aWriter: IWriter);

var

// Compression Algorithm would go here, preferrably an abstraction!

Result := aByteArray;

end;

end.

Here we’ve completely inverted the dependencies. Now, theTCompressionServiceclass is not dependent on the details, but instead is dependent on the two abstractions,IReaderandIWriter. No longer is there

any hard-coded dependency. Instead, we’ve put the dependency – or the details if you will – in the low-level modules, abstracted away behind the interfaces.

For completeness, here is but one example of how those details might be implemented. It’s critical to note that you can implementIReaderandIWriterin anyway you want – they are details and of no concern to the high-level moduleTCompressionService.

FS.Free;

end;

end;

constructor TFileWriter.Create(aFilename: string);

begin

inherited Create;

FFilename := aFilename;

end;

procedure TFileWriter.WriteAll(aBytes: TArray<Byte>);

var

FS: TFileStream;

begin

FS := TFileStream.Create(FFilename, fmCreate);

try

FS.Write(aBytes, Length(aBytes));

finally FS.Free;

end;

end;

end.

Thus, DIP becomes the basis for all decoupling. In the above code, you can change the implementation of

IReader and IWriter without disturbing TCompressionService (and thus following the Open/Closed Principle!). You’ve successfully made all the modules – particularly the details – depend on abstractions (i.e.

interfaces). That is the essence of the Dependency Inversion Principle.

Conclusion

Writing SOLID code isn’t that tricky. It just requires you to look at your code from a different perspective.

Follow the SOLID Principles, and you’ll end up with easy to maintain, clean, well-written code. That you end up with more classes and interfaces is a side-effect with no real downsides. Small, discrete classes are easy to test, easy to reuse, and easy to combine into powerful systems. Who doesn’t want that?

In 1995, a seminal book was released named Design Patterns: Elements of Reusable Object-Oriented Software written by four people – Eric Gamma, Richard Helm, Ralph Johnson, and John Vlissides – who came to be known as the “Gang of Four.” The book became the “Gang of Four Book,” an instant classic because it defined a set of design patterns for writing software. Patterns arise from the notion that there are common problems to be solved in software design and that they can be codified into a set of general patterns that can be recognized and reused.

Sure, the book was written over twenty years ago, but good ideas don’t go away, and the patterns in that book are as relevant today as they were then. Uncle Bob Martin defines a Design Pattern as “…a named canonical form for a combination of software structures and procedures that have proven to be useful over the years.¹³”

Things found to be useful over the past years tend to stay useful over the future years. In this next section, we’ll explore some of these patterns using Delphi code to illustrate them.

Patterns have two main uses. First, they guide your design and development and to lead you down the proper path towards clean good design. Since they have proven to work, you know that you can’t go wrong if you follow them.

The other use is to provide a context in which developers can communicate with each other. If you say “I used the Observer pattern to solve the problem,” then most developers will (or should) know what you are talking about. They become a lingua franca for developers to discuss ideas and design decisions.

Design patterns are not meant to be recipes to follow step by step; rather, they show the way towards proven solutions to familiar problems. This should not stop you from adapting the code in this book to your particular circumstances, nor from finding a different solution using a given pattern.

I am indebted to the Gang of Four as well as to the authors ofHead First Design Patterns¹⁴– Eric Freeman, Elisabeth Freeman, Kathy Sierra, and Bert Bates – for teaching about patterns and leading the way in clear, useful examples.

I can’t cover all the patterns in the Gang of Four book – that would be a whole book in itself. I will instead focus on a few of the patterns that I have found useful and interesting in my development. They include:

• Factory

• Adapter

• Observer

• Decorator

• Command

Hopefully these few patterns will give you a taste for learning more.

¹³http://blog.cleancoder.com/uncle-bob/2014/06/30/ALittleAboutPatterns.html

¹⁴http://www.amazon.com/gp/product/0596007124/ref=as_li_tl?ie=UTF8&camp=1789&creative=390957&creativeASIN=0596007124&linkCode=

as2&tag=nickhodgeshomepa&linkId=6Z6LCYW3PPWNALT3

31

Introduction

In myprevious book¹⁵andeven on my blog¹⁶, I’ve encouraged you not to create things manually. Instead, I’ve encouraged you to pass off the responsibility for creating things to some other class whose job is specifically to create things. The reason for this, of course, is that every call toCreatecauses a dependency and a coupling of code. Those things are best avoided.

So in this chapter, we are going to look at the Factory Pattern. First, we’ll take an informal look at factories and how they work. Then we’ll look at the two main “real” factory patterns from the Gang of Four book, Abstract Factory and Factory Method. There are a lot of ways to do factories – shoot, a Dependency Injection container is really a glorified factory – but we’ll try to take a look at the common ways factories are done.

Why do we use the Factory pattern? Well, because we want to write SOLID code. We want to follow the Single Responsibility Principle, and so we don’t want our classes taking on the added responsibility of creating things.

Instead, we want to create a separate class whose sole responsibility is to create things for us. Also, as we shall see, we want to follow the Open/Closed Principle. In our first example, we’ll see a class that is most definitely not closed for modification, and so we want to sequester that problem out to a separate class that makes the class closed for modification.

In document Leanpub.more.Coding.in.Delphi.2015 (Page 38-44)