Click here to Skip to main content
15,880,503 members
Articles / Programming Languages / C#

Software Design Principles and Patterns in Pictures

Rate me:
Please Sign up or sign in to vote.
4.86/5 (52 votes)
22 May 2018CPOL11 min read 42.6K   377   103   12
In this article, I try to explain some design patterns and principles using analogies and pictures from the non-software world.

Image 1

Introduction

Purpose of this Article

The purpose of this article is to explain the programming concepts and patterns using pictures.

When thinking about software or mathematical abstractions, it always helps to map the abstraction into something else, something that can be imagined or drawn.

They say better one time to see, than ten times to hear. Picture use does not mean that the article is light - in fact I am trying to teach a pretty sophisticated concept in an easy, accessible way.

Some knowledge of an object oriented language is a requirement for reading this article. Specifically, I recommend C#, Java, C++ or TypeScript.

All the samples for this article are written in my beloved C#.

What is Engineering?

I like thinking about Engineering as an art of putting together various building blocks in such a way that the result is what the client wants. This is true about any engineering - software, hardware mechanical, civil, etc.

Image 2

What is Software Engineering

Software engineering is everything that generic engineering is, plus a lot of powerful extra capabilities:

  1. Creating a building block is much more simple in software than in hardware. In Object Oriented Programming (OOP) - this simply means creating another class.
  2. You can create various blocks yourself and then re-use them within your application. Note that it does not happen often in hardware engineering - usually the company that builds a building block and the company that uses the block for an assembly are two different companies, or at least it happens in two different factories.
  3. Because of the previous point, one can create software building blocks at different levels - some more generic and some more specific adding them correspondingly to different libraries.
  4. It is much easier to fix a software rather than a hardware problem or add or change a software feature.
  5. In Object Oriented languages, mentioned above, when creating a block, you actually create a block factory, meaning that once you create a class, you can create as many objects of that class as you need. So rather than hardware building blocks, the classes are more like icons in video games where you can drag as many objects corresponding to the same icon into the screen as you want or a shape within MS Word, which you can drag and drop onto a page:

    Image 3

  6. Software classes can be manipulated to fit each other - by adapting the class to an interface (by interface implementation) or by extending the class's functionality using implementation inheritance. The Object Oriented Programming features are simply language 'tricks' that allow to build some new object types (classes) from already existing object types.

Sample Code Location

The code for the samples can be downloaded from the link at the top of the article. Also, it is part of the GitHub repository: Patterns In Pictures.

Picture and Code Samples

Examples of Interfaces with Different Implementations

Interface determines how a building block is being used by other building blocks or by the user. Interface does not care about the building block's implementation. When it comes to pictures, I imagine an interface being the surface or a set of connections which restrict the building block's usage:

Image 4

Pacifier and bottle have a similar 'nipple' surface and can be used by baby's mouth interchangeably. In a sense, they have a common interface though different implementations.

For a chip, the interface are its connections:

Image 5

The power interface of an electric device is its power plug and the power interface of a refrigerator is the same power plug. Even if the two devices are completely different, their power interface is the same.

This brings me to discussing building blocks implementing multiple interfaces. For example, an electric device (e.g., fridge or lamp) is being used in many more ways than just plugging them into an electric outlet. They have switches that can be changed, they produce something for the end user (e.g., refrigerating food or giving light), yet from the point of view of being an electric device, they implement the same interface - the power plug.

In software, one side of a usage functionality (usually implemented by a single interface) is called a concern. We can say that both the fridge and the lamp implement the same concern of a device with an electric plug.

Usage Inheritance Code Sample

The code sample that demonstrates usage inheritance is located under MultipleInterfaces.sln solution. Here is the inheritance diagram:

Image 6

Note that here, in order to demonstrate the usage inheritance, I introduced more interfaces than necessary for this small sample. You do not have to have interfaces for every class - only when you have two different implementations of a similar concern, you need to introduce interfaces as was pointed in Software Project Development and Decision Making article.

Here is the code for all the classes and interfaces involved:

C#
public interface IPowerPlug
{
    bool IsPlugged { get; set; }
}  

It only has a boolean property specifying whether the device is plugged or not.

C#
public interface ILamp : IPowerPlug
{
    bool IsOn { get; }

    bool IsSwitchOn { get; set; }
}  

ILamp derives from IPowerPlug and adds two properties to it: getter only IsOn specifies whether the lamp is really on (producing light), IsSwitchOn specifies whether the lamp's switch is on. Obviously, in order for the lamp to be on, it has to be plugged into the power outlet and the switch has to be on also - which is how it is implemented in Lamp class:

C#
public class Lamp : ILamp
{
    bool _isSwitchOn = false;
    public bool IsSwitchOn
    {
        get => _isSwitchOn;
        set
        {
            if (_isSwitchOn == value)
                return;

            _isSwitchOn = value;

            // set IsOn to be true iff 
            // IsSwitchOn and IsPlugged are true
            IsOn = IsSwitchOn && IsPlugged;
        }
    }

    bool _isOn = false;
    public bool IsOn
    {
        get => _isOn;

        set
        {
            if (_isOn == value)
                return;

            _isOn = value;

            // print to console when IsOn changes
            Console.WriteLine($"The lamp is {(_isOn ? "On": "Off")}");
        }
    }

    bool _isPlugged = false;
    public bool IsPlugged
    {
        get => _isPlugged;

        set
        {
            if (_isPlugged == value)
                return;

            _isPlugged = value;

            // set IsOn to be true iff 
            // IsSwitchOn and IsPlugged are true
            IsOn = IsSwitchOn && IsPlugged;
        }
    }
}  

Unlike ILamp, IFridge interface is implemented using 'multiple usage inheritance' (simply in order to demonstrate the feature). It inherits from IPowerPlug and ITemperatureSetter interfaces:

C#
public interface IFridge : ITemperatureSetter, IPowerPlug
{

}  

Here is the code for ITemperatureSetter:

C#
public interface ITemperatureSetter
{
    // the temperature set by hand
    double SetTemperature { get; set; }

    // the real fridge temperature
    double RealTemperature { get; }
} 

For simplicity, we assume, that RealTemplerature becomes SetTemperature as soon as the Fridge is plugged. Here is the Fridge implementation:

C#
public class Fridge : IFridge
{
    bool _isPlugged = false;
    public bool IsPlugged
    {
        get => _isPlugged;

        set
        {
            if (_isPlugged == value)
                return;

            _isPlugged = value;

            RealTemperature = IsPlugged ? SetTemperature : 0;
        }
    }

    double _setTemperature = double.NaN;
    public double SetTemperature
    {
        get => _setTemperature;

        set
        {
            if (_setTemperature == value)
                return;

            _setTemperature = value;

            RealTemperature = IsPlugged ? SetTemperature : 0;
        }
    }

    double _realTemperature = 0;
    public double RealTemperature
    {
        get => _realTemperature;
        private set
        {
            if (_realTemperature == value)
                return;

            _realTemperature = value;

            Console.WriteLine($"Real Temperature is {RealTemperature} degrees");
        }
    }
} 

And here is the main method of the sample:

C#
static void Main(string[] args)
{
    //Create the lamp
    ILamp lamp = new Lamp();

    // plugin the lamp  
    lamp.IsPlugged = true;

    // turn it on 
    // at this point it should print
    // to console that the lamp is on
    lamp.IsSwitchOn = true;

    // create a fridge
    IFridge fridge = new Fridge();

    // set the temperature to 58 degrees
    fridge.SetTemperature = 58d;

    // at this point, it should print 
    // temperature to console. 
    fridge.IsPlugged = true;
}  

Note that Java and C# usage (interface) inheritance have an important feature that cannot be mapped to a hardware feature (and correspondingly cannot be displayed on a picture) - the so called merging of members from several interfaces. If a property or event with the same name or a method with the same name and signature belongs to two super-interfaces, the two members are merged into a single one within sub-interface.

Wrapper/Adapter Pattern

Image 7

The concept of adapter pattern can be very well displayed by a simple hardware European to American power adapter. This is a pattern that changes the public interface of an object without changing its implementation.

The code for Adapter sample is located under AdapterSample.sln solution.

Here is the code diagram:

Image 8

We have AmericanLamp class which implements IAmericanPowerPlug interface:

C#
public interface IAmericanPowerPlug
{
    bool IsPluggedIntoAmericanPowerOutlet { get; set; }
}  

We want to adapt it to the IEuropeanPowerPlug interface:

C#
public interface IEuropeanPowerPlug
{
    bool IsPluggedIntoEuropeanPowerOutlet { get; set; }
}  

We use the adapter patters from the famous gang of four book in order to adapt the AmericalLamp to European outlet, creating a class AdaptedLamp:

C#
public class AdaptedLamp : AmericanLamp, IEuropeanPowerPlug
{
    // wrapping (adapting) the property.
    public bool IsPluggedIntoEuropeanPowerOutlet
    {
        get => IsPluggedIntoAmericanPowerOutlet;
        set
        {
            IsPluggedIntoAmericanPowerOutlet = true;
        }
    }
}  

As you can see, the class AdaptedLamp inherits from AmericanLamp and implements IEuropeanPowerPlug interface by wrapping (renaming) IPluggedIntoAmericanPowerOutlet method.

The implementation is not changed, but now the adapted object can be passed to methods expecting IEuropeanPowerPlug:

C#
class Program
{
    static void PlugIntoEuropeanOutlet(IEuropeanPowerPlug europeanPowerPlug)
    {
        europeanPowerPlug.IsPluggedIntoEuropeanPowerOutlet = true;
    }

    static void Main(string[] args)
    {
        IEuropeanPowerPlug adaptedLamp = new AdaptedLamp() { IsSwitchOn = true };

        PlugIntoEuropeanOutlet(adaptedLamp);
    }
} 

Note that if we wanted, we could also make some minor modifications to the adapted property within the setter - imagine that on top of adaptation, we also need to transform the voltage to European.

Another way to implement the adapter is by wrapping the class AmericanLamp instead of deriving from it. This is probably a better, but slightly more tedious way which can be automated by using Roxy IoC container and proxy generator. But I plan to write another article detailing Roxy usage for various pattern implementations.

Plugin (Strategy) and Proxy Patterns

Plugin (or Strategy) pattern allows to use different implementations for the same member of a class. The various plugin implementations can sometimes be swapped in the code for an already existing object or sometimes they are determined by the object's constructor argument and cannot be changed after the object's construction.

Image 9

I think a great way to demonstrate an essence of a plugin outside of the software world is to imagine a baby getting a bottle vs a pacifier. They both have similar interface but different implementations.

In the gang of four book, this pattern was called Strategy - because they were using this pattern primarily to provide different behaviors for a class. In fact, I think, the name Plugin is better and more generic.

Code for this sample is located under PluginSample.sln solution. The code usage is demonstrated within Program.Main method:

C#
static void Main(string[] args)
{
    Baby baby = new Baby();

    Console.WriteLine("Setting succable plugin to pacifier");
    baby.SetSuccablePlugin(new Pacifier());

    baby.Suck();

    Console.WriteLine("Setting succable plugin to a BottleWithMilk");
    baby.SetSuccablePlugin(new BottleWithMilk());

    baby.Suck();
}

Here is a brief description of the Program.Main method: create a Baby object, set its SuccablePlugin to new Pacifier(). Call method Suck(). Then, reset its SuccablePlugin to new BottleWithMilk() and call method Suck() again.

Here is what will be printed on the console:

Setting succable plugin to pacifier
Pacifier is sucked
Setting succable plugin to a BottleWithMilk
Milk is drunk  

Here is the Baby class:

C#
public class Baby
{
    ISuccable _succablePlugin = null;

    // set the succable plugin
    public void SetSuccablePlugin(ISuccable succablePlugin)
    {
        _succablePlugin = succablePlugin;
    }


    // method suck - call the corresponding
    // plugin method
    public void Suck()
    {
        _succablePlugin?.Suck();
    }
}  

and here are the Pacifier and BottleWithMilk plugin implementations:

C#
public class Pacifier : ISuccable
{
    public void Suck()
    {
        Console.WriteLine("Pacifier is sucked");
    }
}  
public class BottleWithMilk : ISuccable
{
    public void Suck()
    {
        Console.WriteLine("Milk is drunk");
    }
}

Note that we not only implemented the Plugin/Strategy pattern, but also the Proxy patter - which is very similar, only assumes that the public method names are the same as the plugin method names (which is true in our case - the public method we call is Baby.Suck() and the corresponding method of the plugin is ISuccable.Suck(). Another requirement specific for a Proxy pattern is that the proxy suggests a special treatment for the case when the plugin pointer is null. This also happens in this sample - here is the Baby.Suck() method:

C#
public void Suck()
{
    _succablePlugin?.Suck();
}  

The "question mark period" operator prevents the null exception to be thrown when the _succablePlugin member is null.

In fact, I think, that Plugin and Proxy patterns are sufficiently similar to call Proxy a variation of a Plugin.

Multiple Plugins and Bridge Patterns

Assume that you have a device with two or more plugins, e.g., a computer with a mouse and a keyboard.

Image 10

Assume that the plugins can be of different types - e.g., mouse can be plain mouse and fancy mouse and the keyboard can be plain keyboard and fancy keyboard:

Image 11

Fancy Mouse

Image 12

Fancy Keyboard

We have two, independent (or nearly independent) concerns or plugins (mouse and keyboard) and each of the plugins can have two implementations - (mouse vs fancy mouse and keyboard vs fancy keyboard):

Image 13

Potentially, we can have full Cartesian Product of the possibilities:

  1. Computer with plain mouse and plain keyboard
  2. Computer with fancy mouse and plain keyboard
  3. Computer with plain mouse and fancy keyboard
  4. Computer with fancy mouse and fancy keyboard

The best way to create a software representation is to use the Plugin pattern discussed above for both the mouse and the keyboard.

The sample that demonstrates Multiple Plugins pattern is located under MultiPluginSample.sln solution.

Its main class is Computer:

C#
public class Computer
{
    // reference to IMouse
    public IMouse Mouse { get; set; }

    // reference to IKeyboard
    public IKeyboard Keyboard { get; set; }

    public Computer()
    {
        // defaults are set to 
        // PlainMouse and 
        // PlainKeyboard

        Mouse = new PlainMouse();

        Keyboard = new PlainKeyboard();
    }

    // wrapper around IMouse.X and IMouse.Y
    // setters
    public void MoveMouse(double x, double y)
    {
        if (Mouse == null)
            return;

        Mouse.X = x;
        Mouse.Y = y;
    }

    // wrapper around IMouse.LeftButtonClick()
    public void MouseClick()
    {
        Mouse?.LeftButtonClick();
    }

    // Wrapper around IKeyboard.KeyClick(char c)
    public void ClickKeyboardKey(char c)
    {
        Keyboard?.KeyClick(c);
    }
}  

This class has several public methods corresponding to mouse and keyboard functionality. These methods are essentially the wrappers around the corresponding methods of the IMouse and IKeyboard plugins, e.g., Method Computer.MouseClick() is a wrapper around IMouse.LeftButtonClick():

C#
// wrapper around IMouse.LeftButtonClick()
public void MouseClick()
{
    Mouse?.LeftButtonClick();
} 

This class contains two public properties for the mouse and keyboard plugins:

C#
// reference to IMouse
public IMouse Mouse { get; set; }

// reference to IKeyboard
public IKeyboard Keyboard { get; set; }  

The implementations of the wrapper methods changes depending on what those properties are set to.

By default, they are set to PlainMouse and PlainKeyboard within the constructor:

C#
public Computer()
{
    // defaults are set to 
    // PlainMouse and 
    // PlainKeyboard

    Mouse = new PlainMouse();

    Keyboard = new PlainKeyboard();
}

But, since their setters are public, they can be changed at any moment within the program.

Here is the Main.Program method:

C#
static void Main(string[] args)
{
    // create computer with 
    // default (plain) mouse and keyboard
    Computer computer = new Computer();

    // mouse and keyboard operations
    // should result in console messages mentioning
    // the plain mouse and plain keyboard
    computer.MoveMouse(20, 50);

    computer.MouseClick();

    computer.ClickKeyboardKey('h');

    // after the keyboard is changed to
    // FancyKeyboard, the 
    // keyboard messages should mention
    // the 'Fancy' keyboard.
    computer.Keyboard = new FancyKeyboard();

    computer.ClickKeyboardKey('h');
}  

Running the sample produces the following output:

Plain Mouse: X = 20
Plain Mouse: Y = 50
Plain Mouse: Left Button Clicked
Plain Keyboard clicked 'h'
Fancy Keyboard clicked 'h' 

Here are the IMouse and IKeyboard interfaces:

C#
// IMouse interface
public interface IMouse
{
    double X { get; set; }
    double Y { get; set; }

    void LeftButtonClick();
}

//IKeyboard interface
public interface IKeyboard
{
    void KeyClick(char c);
}  

The corresponding plugin implementations are very simple also, each of which will print to console whether it is a 'Plain' or 'Fancy' implementation.

Now, if we take a moment to remember what Bridge pattern is, we can see that what we implemented matches Bridge pattern almost exactly, except that the implementation we considered here is more powerful - since the plugins can be changed - whereas in case of standard Bridge pattern, the implementations are fixed once the object is created.

Indeed, if you look at the definition of the Bridge pattern from the gang of four book, you will see that it allows to create a Cartesian product of two independent concerns without creating a separate type for each one of them. In the standard Bridge pattern, various implementations along one of the concerns are being achieved via inheritance, while the variations along the other concern are implemented as a plugin. This example demonstrates that implementing both (or more than 2) concerns as plugins is both simpler and more powerful than using one concern for inheritance.

In case someone wants to limit the power of the MultiPlugin pattern to that of the Bridge pattern from the Gang of Four book, all he needs to do, is to make the Plugin properties setters private and set them within a constructor that takes the corresponding object as arguments. This will lead to the following changes within Computer class:

The plugin properties get private setters:

C#
// reference to IMouse
public IMouse Mouse { get; private set; }

// reference to IKeyboard
public IKeyboard Keyboard { get; private set; }  

Also, the constructor will change to accept the plugin objects:

C#
public Computer(IMouse mouse, IKeyboard keyboard)
{
    Mouse = mouse;

    Keyboard = keyboard;
}  

Now all the conditions of the Bridge pattern are fulfilled - once the object is created, its plugins cannot be changed.

Conclusion

In this article, I tried to come up with visual analogies of software engineering ideas and patterns including:

  1. Usage Inheritance
  2. Adapter Pattern
  3. Plugin (or Strategy) Pattern
  4. MultiPlugin (or Bridge) Pattern

Time permitting and depending on how popular this article is, I plan to write more articles providing analogies to the software ideas from the non-software world.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Architect AWebPros
United States United States
I am a software architect and a developer with great passion for new engineering solutions and finding and applying design patterns.

I am passionate about learning new ways of building software and sharing my knowledge with others.

I worked with many various languages including C#, Java and C++.

I fell in love with WPF (and later Silverlight) at first sight. After Microsoft killed Silverlight, I was distraught until I found Avalonia - a great multiplatform package for building UI on Windows, Linux, Mac as well as within browsers (using WASM) and for mobile platforms.

I have my Ph.D. from RPI.

here is my linkedin profile

Comments and Discussions

 
GeneralMy vote of 4 Pin
Vikas K Gupta5-Oct-18 3:10
Vikas K Gupta5-Oct-18 3:10 
GeneralRe: My vote of 4 Pin
Nick Polyak5-Oct-18 5:39
mvaNick Polyak5-Oct-18 5:39 
Questionnice one Pin
Vikas K Gupta5-Oct-18 3:01
Vikas K Gupta5-Oct-18 3:01 
QuestionA title change, perhaps. Pin
lunarplasma23-May-18 23:28
lunarplasma23-May-18 23:28 
AnswerRe: A title change, perhaps. Pin
Nick Polyak24-May-18 1:58
mvaNick Polyak24-May-18 1:58 
AnswerRe: A title change, perhaps. Pin
Nick Polyak24-May-18 11:28
mvaNick Polyak24-May-18 11:28 
GeneralMy Vote of 5 Pin
Stylianos Polychroniadis23-May-18 23:05
Stylianos Polychroniadis23-May-18 23:05 
GeneralRe: My Vote of 5 Pin
Nick Polyak24-May-18 1:57
mvaNick Polyak24-May-18 1:57 
GeneralMy vote of 5 Pin
Charles Lindsay23-May-18 10:33
Charles Lindsay23-May-18 10:33 
GeneralRe: My vote of 5 Pin
Nick Polyak24-May-18 1:56
mvaNick Polyak24-May-18 1:56 
SuggestionPlease write articles on UML Pin
Mou_kol4-Mar-18 21:13
Mou_kol4-Mar-18 21:13 
GeneralRe: Please write articles on UML Pin
Nick Polyak5-Mar-18 5:14
mvaNick Polyak5-Mar-18 5:14 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.