Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / programming / architecture

OOP Is For Humans (CPUs Don't Care): The Power of Abstraction Via Interfaces

4.44/5 (8 votes)
22 Apr 2016CPOL12 min read 14.6K   144  
Learn to leverage the power of the Interface to design your object-oriented code so you can test interactions and create more extendable more easily maintainable code. (Also see the Factory pattern implemented simply)

Introduction

Challenges of Stretching Your OOP Design Knowledge

It is natural to become very good at what we do without learning if there are better ways to design our code.

Even if you decide to delve into learning a new pattern, you may find great difficulty in finding an application for the pattern in the real code you write. Even if you find that you could use the pattern in your real code, you may find that the entire application needs to be redesigned in order to be able to implement the pattern -- since making the change alters the interactions of your objects throughout your code.

Often, this leads us back to the initial idea that we are quite good at what we do anyways and the code is good enough. After all, it is running in production and rarely bothers you.

Narrowed Focus, Real Code, Run As You Learn

That's why my idea for this article is to have a narrowed focus which allows you to actually see the real code that actually does something valuable which you can apply immediately to solutions. You will also be able to run the code as you go so you can see how the pattern actually works.

How Can You See Code Run Easily?

I suggest you download a free copy of the great tool LINQPad from http://LINQPad.net (opens in new window/tab).

This tool allows you to write C# code and run it without having to deal with creating a project and WinForms and all the rest of the requirements. Instead with this tool, you can design classes, write some code to use them and run the code with console output (at the bottom of the tool). It looks like the following:

LINQPad snapshot

That is exactly how we'll move through this article. I will provide you with the smallest code possible that runs, allow you to run it and then we'll talk about it and alter it some more.

Patterns Are Abstract Ideas

One of the reasons patterns are so difficult to discuss is because they are so abstract at times that it is difficult to see their benefit in the real world. Examples are often so convoluted and confusing, it makes it difficult to focus on what we are attempting to accomplish.

That's why I want to keep my example extremely simple and yet applicable to a real world solution.

As I said, I want to make it possible to run my code as you see it progress.
I suggest we use an IPersistable interface which supports one public method named Save(). It'll look like the following:

C#
interface IPersistable {
    bool Save();
}

Where Did that Interface Come From?

Right out of the gate, I want to mention that I made that interface up. That's right, you may find some interface somewhere else named IPersistable, but this one I just made up. The next logical question is: where did you get the idea that would make a good interface?

Why Is This A Valid Interface?

It's valid simply because an interface is made up of one or more behaviors named as actions. In this case, I want my implementation classes (which we'll see in just a moment) to contain a method named Save() which returns a bool. As you can see, the interface contains no implementation at all -- there's no code that does anything in the interface. How can that have any value at all?

Contract For Future Code

For now, just consider it a statement of the future code. For example, Sam the developer might say, "I want my class to save its data to a file." Then Jane might chime in, "Well, I want my class to save to the database."

Did you notice that the natural language that both of those developers used contained the word save? That's a future contract that they want their classes to uphold. That's an abstract way of talking about the classes they want.

The Implementation Differs

However, the implementation for each of those will be very different because one is writing to a file and the other is writing to the database. They will need different configuration information like a path and filename for the file version and a database connection with the name of the table for the database implementation.

Three Implementation Classes To Make Our Code Real

Let's create three classes which implement the IPersistable interface and it'll make things a bit more clear. Also, you'll be able to run the code and see a bit more of an example of how it'll work and how it might help you.

C#
class FileSaver : IPersistable{
    public bool Save(){
        Console.WriteLine("I'm saving into a FILE.");
        return true;
    }
}

class DatabaseSaver : IPersistable{
    public bool Save(){
      Console.WriteLine("I'm saving into a DATABASE.");
      return true;
    }
}

class TcpSaver : IPersistable{
    public bool Save(){
        Console.WriteLine("I'm saving into a WEB LOCATION.");
        return true;
    }
}

Now we have some actual implementation which will excite a lot of developers out there, because they will see that these classes actually do something.

If you take all of the code and copy it into LINQPad and add a main method, you can see something happen in LINQPad's console output area.

Here's an entire code listing that you can grab and try in LINQPad:

C#
void Main()
{
    FileSaver fs = new FileSaver();
    fs.Save();
    DatabaseSaver ds = new DatabaseSaver();
    ds.Save();
}

interface IPersistable {
    bool Save();
}

class FileSaver : IPersistable{
   public bool Save(){
       Console.WriteLine("I'm saving into a FILE.");
       return true;
   }
}

class DatabaseSaver : IPersistable{
   public bool Save(){
     Console.WriteLine("I'm saving into a DATABASE.");
     return true;
   }
}

class TcpSaver : IPersistable{
   public bool Save(){
       Console.WriteLine("I'm saving into a WEB LOCATION.");
       return true;
   }
}

If you'll run that code in LINQPad, you'll see that its output looks like the following:

first sample run

Not So Amazing (Or Is It?)

"Okay", you think, "big deal". What does that even matter? Well, it's a bit difficult to see at this point but the big deal is that you know that your implementations are guaranteed to fulfill the Save() contract that was set up by the interface.

What Does It Matter That It Fulfills A Contract?

You may not have noticed but now that you've set all of your classes to implement the interface, those classes have all been transformed into the same type (IPersistable). Why does that matter? It matters because now you can group these things. Why does it matter that you can group the things? That matters because now you don't have to create them as separate types as you did in the Main() method of our previous LINQPad example.

Grouping Similar Objects and the Benefit

Since we now know all of our objects will fulfill a specific contract, we can group them together and call the same method on them knowing that the method will be available.

Now, we can change our Main() method code to look like the following:

C#
void Main()
{
    List<IPersistable> allItems = new List<IPersistable>();
    allItems.Add(new FileSaver());
    allItems.Add(new DatabaseSaver());
    allItems.Add(new TcpSaver());
    foreach (IPersistable ip in allItems)
    {
        ip.Save();
    }
}

Please Tell Me Your Eyes Just Flew Open

If you understand what we just did in that code, a flash of lightning emerged from your brain and your eyes flew open.

Understanding this simple concept is a huge key to understanding everything in OOP.

Here's the snapshot of what the code looks like when it runs in LINQPad:

first steps to understanding all of OOP

No Need To Know What An Object Is

You no longer need to know what your object is to get it to do something. The runtime system will know what it is.

Just Need to Know What Behaviors the Object Implements

Now you just need to know what behaviors it implements so you can call them specifically in your code.

How Does This Help?

It allows you to begin to design your code at a higher level so you can begin to create code without having to write every implementation before you can try out the interactions of your objects.

Interactions of Objects? What?

If you were to summarize OOP code, you could say that it is simply,

Quote:

the creation of various object types which interact with each other to create the intended result.

What kind of malarkey are you trying to pass off on me? Sounds like a bunch of blather.

Well, think about this for a moment.

The Main Reason For Writing Code

The main reason to write code is to create software which solves a problem.

OOP Is For Humans

The main reason to write OOP code is to design organized code which solves a problem. OOP is simply a way to organize your code. The computer itself (the processor) does not know or care about OOP at all. OOP is for humans.

We write code to solve a problem. We write OOP code in order to organize complex solutions so they are more easily maintained and extended. OOP code should communicate its intent (purpose) more clearly (not perfectly) to a maintenance developer who has never seen the code before.

OOP Empowers Communication Via Code Summary

OOP can provide a good code summary so other developers can be brought into the project. When people talk about code, they talk about what it does. They talk about the codes behaviors. When you create interfaces, you create contracts for behaviors. Now when we talk about what the code I just created does, we can easily say, IPersistable helps you save your data. If a new developer comes along and says, "Hey, what does this object do?" He can look at it and see that it implements IPersistable and see the public interface is the Save() method and know that it saves data.

Next, when the new developer decides he wants his new class to be able to save itself to the database, he can look around and see that there is a DatabaseSaver which implements the IPersistable interface.

Small Systems and Developers Who Code On Their Own are Dubious

This is exactly why many developers who code in isolation or have never been on a team of even a few people are doubtful about OOP. I understand their doubts. I mean OOP is overhead to the system and overhead to the developer in some cases.

Focus On One Thing At A Time

But, when things get extremely complex, OOP can really help by allowing the developer to focus on one thing at a time. It allows this via encapsulation (data hiding) since each object should focus on doing one thing (Single-Responsibility Principle of SOLID) and only provide public members to the main things it does for the client.

Quote:

OOP done right can help you manage complexity.

Another Concrete Example of Using the Interface

Okay, let's take this another step forward while teaching you the Factory Pattern. Let's suppose that you want something else to build the object you need to do your work. Why would you do that? Because you are going to allow the user to configure where he saves his data in a configuration file so his data can be saved at the appropriate location at runtime. Think about how powerful that is for a moment. The user can easily let the software know to save his data in a database instead of a file at runtime

Very Simple Factory Pattern Implementation For Clarity

Please keep in mind I'm keeping this first revision of the Factory Pattern very simple for clarity.

Here's all the code you would have to add to LINQPad to create the Factory Pattern.

C#
class SaverFactory{
    String type;
    public SaverFactory(String type){
        this.type = type;
    }
    
    public IPersistable CreateSaver(){
        switch (type){
            case "FileSaver" :
            {
                return new FileSaver();
            }
            case "DatabaseSaver" :
            {
                return new DatabaseSaver();
            }
            case "TcpSaver" :
            {
                return new TcpSaver();
            }
            default : 
                return null;
        }
    }
}

Simple Power

This code is very simple, but quite powerful. Notice that you can construct the factory by sending in a String that represents a type. Constructing the Factory would look like the following: SaverFactory("FileSaver")

Next, notice that the CreateSaver() method simply switches on the String and calls the appropriate constructor for the implementation class we want to instantiate.

The Thing To Focus On

Notice that CreateSaver() method returns an object of type IPersistable. That's where the power comes in.

You don't have to know which specific implementation type is returned. You are only interested in the behavior that it is guaranteed to support. In this case Save().

Using SaverFactory To Generate Our Class

Now, when we want to generate a FileSaver we can do the following:

C#
IPersistable ip = SaverFactory("FileSaver").CreateSaver();

However, this small example may not allow you to see the power clearly enough.

Let's change our driver code in the main method to use the Factory now. I am including the entire code listing so you can copy it into LINQPad and run it.

C#
void Main()
{
    // imagine numerous config lines were read from DB
    List<String> fakeConfig = new List<String>();
    fakeConfig.Add("FileSaver"); fakeConfig.Add("DatabaseSaver");
    fakeConfig.Add("TcpSaver");
    
    List<IPersistable> allItems = new List<IPersistable>();
    foreach (String s in fakeConfig)
    {
        IPersistable ip = new SaverFactory(s).CreateSaver();
        if (ip != null)
            allItems.Add(ip);
    }
    foreach (IPersistable ip in allItems)
    {
        ip.Save();
    }
}

interface IPersistable {
    bool Save();
}

class FileSaver : IPersistable{
   public bool Save(){
       Console.WriteLine("I'm saving into a FILE.");
       return true;
   }
}

class DatabaseSaver : IPersistable{
   public bool Save(){
     Console.WriteLine("I'm saving into a DATABASE.");
     return true;
   }
}

class TcpSaver : IPersistable{
   public bool Save(){
       Console.WriteLine("I'm saving into a WEB LOCATION.");
       return true;
   }
}

class SaverFactory{
    String type;
    public SaverFactory(String type){
        this.type = type;
    }
    
    public IPersistable CreateSaver(){
        switch (type){
            case "FileSaver" :
            {
                return new FileSaver();
            }
            case "DatabaseSaver" :
            {
                return new DatabaseSaver();
            }
            case "TcpSaver" :
            {
                return new TcpSaver();
            }
            default : 
                return null;
        }
    }
}

Quite a Bit of Code

It's quite a bit of code, but notice how clearly it is organized. Notice that you can now iterate through these generic objects knowing that they will fulfill your original contract.

Focus On the Main Method

Also, notice that you can focus on the Main() method and determine what is happening quite easily. It's quite readable code.

Imagine Reading Configuration Information

You have to imagine that the code is reading from a database or config file which is providing various config information. That config information would also contain the name of the type (FileSaver, DatabaseSaver, etc.) the user wants to implement in relation to the configuration. That may be because the user wants his data saved to the database or whatever.

Extendable Without Breaking Code

Yet, it is also quite extendable. We can easily add new implementations of IPersistable or even extend each of our implementation classes without breaking anything else. Without even touching any of the existing code. That means no need to test previously completed and tested code. More lightning should be erupting from your brain.

Here's another snapshot of what the code output looks like. It's quite similar to the previous one.

final sample output

Next Level, Next Article

There is another level that we can take this to and I'm hoping to write the next article which will:

Show you how you might send in a Configuration object to the Factory so that each implementation class will have all the details (file name, database connection, etc.) to save its data. I just don't want to completely overwhelm my readers with too long of an article.

Points of Interest

These are just some things I've been thinking about and wanted to try to document them, hoping they might help others and start a conversation.

History

  • 04-23-2016 - Second version of article - fixed grammatical error and added code downloads (LINQPad file and VStudio 2013 C# project)
  • 04-22-2016 - First version of this article and code

License

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