left-icon

Object-Oriented Programming in C# Succinctly®
by Sander Rossel

Previous
Chapter

of
A
A
A

CHAPTER 1

The Three Pillars of OOP

The Three Pillars of OOP


Object-oriented programming has three characteristics that define the paradigm, the so-called “three pillars of OOP.” In this chapter, we’ll look at each one of them in detail. They all play an important role in the design of your applications. That’s not to say that your systems will have a great design by simply applying these concepts. In fact, there is no single correct way of applying these concepts, and applying them correctly can be difficult.

Inheritance

Inheritance is an important part of any object-oriented language. Classes can inherit from each other, which means the inheriting class gets all of the behavior of the inherited class, also known as the base class. Let’s look at the Person example I used earlier.

Code Listing 3: Inheritance Example

public class Person

{

    public string FirstName { get; set; }

    public string LastName { get; set; }

    public string GetFullName()

    {

        return FirstName + " " + LastName;

    }

}

public class Employee : Person

{

    public decimal Salary { get; set; }

}

The trick is in the Employee : Person part. That part basically says “Employee inherits from Person”. And remember everything inherits from Object. In this case Employee inherits from Person and Person (because it’s not explicitly inheriting anything) inherits from Object. Now let’s look at how we can use Employee.

Code Listing 4: Subclass usage

Employee employee = new Employee();

employee.FirstName = "Sander";

employee.LastName = "Rossel";

string fullName = employee.GetFullName();

employee.Salary = 1000000; // I wish! :-)

That’s pretty awesome! We got everything from Person just by inheriting from it! In this case we can call Person a base class or superclass and Employee a subclass. Another common way of saying it is that Employee extends Person.

There’s a lot more though! Let’s say we’d like to write some common behavior in some base class, but we’d like subclasses to be able to extend or override that behavior. Let’s say we’d like subclasses to change the behavior of GetFullName in the previous example. We can do this using the virtual keyword in the base class and override in the subclass.

Code Listing 5: Method overriding

public class Person

{

    public string FirstName { get; set; }

    public string LastName { get; set; }

    public virtual string GetFullName()

    {

        return FirstName + " " + LastName;

    }

}

public class Employee : Person

{

    public decimal Salary { get; set; }

    public override string GetFullName()

    {

        string originalValue = base.GetFullName();

        return LastName + ", " + FirstName;

    }

}

As you can see we can override, or re-define, GetFullName because it was marked virtual in the base class. We can then call the original method using the base keyword (which points to the implementation of the base class) and work with that, or we can return something completely different. Calling the original base method is completely optional, but keep in mind that some classes may break if you don’t.

Now here’s an interesting thought: Employee has the type Employee, but it also has the type Person. That means that in any code where we need a Person we can actually also use an Employee. When we do this we can’t, of course, access any Employee specific members, such as Salary. So here’s a little question: what will the following code print?

Code Listing 6: What will the code print?

Person person = new Employee();

person.FirstName = "Sander";

person.LastName = "Rossel";

string fullName = person.GetFullName();

Console.WriteLine(fullName);

// Press any key to quit.

Console.ReadKey();

If you answered “Rossel, Sander” (rather than "Sander Rossel") you were right!

What else can we do? We can force a subclass to inherit certain members. When we do this we must mark a method, and with that the entire class, as abstract. An abstract class can’t be instantiated and must be inherited (with all abstract members overridden).

Code Listing 7: An Abstract Class

public abstract class Person

{

    public string FirstName { get; set; }

    public string LastName { get; set; }

    public abstract string GetFullName();

}

public class Employee : Person

{

    public decimal Salary { get; set; }

    public override string GetFullName()

    {

        return LastName + ", " + FirstName;

    }

}

Of course we can’t make a call to base.GetFullName() anymore, as it has no implementation. And while overriding GetFullName was optional before, it is mandatory now. Other than that the usage of Employee stays exactly the same.

On the other end of the spectrum, we can explicitly state that a class or method may not be inherited or overridden. We can do this using the sealed keyword.

Code Listing 8: A Sealed Class

public sealed class Person

{

    public string FirstName { get; set; }

    public string LastName { get; set; }

    public string GetFullName()

    {

        return FirstName + " " + LastName;

    }

}

Now that Person is sealed no one can inherit from it. That means we can’t create an Employee class and use Person as a base class.

Methods can only be sealed in subclasses. After all, if you don’t want people to override your method, simply don’t mark it virtual. However, if you do have a virtual method and a subclass wants to prevent further overriding behavior it’s possible to mark it as sealed.

Code Listing 9: A sealed method

public class Person

{

    public string FirstName { get; set; }

    public string LastName { get; set; }

    public virtual string GetFullName()

    {

        return FirstName + " " + LastName;

    }

}

public class Employee : Person

{

    public decimal Salary { get; set; }

    public sealed override string GetFullName()

    {

        return LastName + ", " + FirstName;

    }

}

No subclass of Employee can now override GetFullName.

Why would you ever use sealed on your classes or methods? First, there is a small performance gain because the .NET runtime doesn’t have to take overridden methods into account. The gain is negligible though, so that’s not really a good reason. A better reason is perhaps because a class implements some security checks that really shouldn’t be extended in any way.

Note: Some languages, like C++, know multiple inheritance. That means a subclass can inherit from more than one base class. In C# this is not possible; each subclass can inherit from, at most, one base class.

Inheritance vs. Composition

While inheritance is very important in OOP, it can be a real pain, too, especially when you get huge inheritance trees with branches to all sides. There is an alternative to inheritance: composition. Inheritance implies an “is-a” relationship between classes. An Employee is a Person. Using composition, we can define a “has-a” relationship.
Let’s take a car. A car is built up from many components, like an engine. When we model a car do we inherit Car from Engine? That would be problematic, because a car also has doors, chairs and a backseat.

Code Listing 10: No Go

public class Engine

{

    // ...

}

public class Car : Engine

{

    // ...

}

How would this look when using composition?

Code Listing 11: Composition

public class Engine

{

    // ...

}

public class Car

{

    private Engine engine = new Engine();

    // ...

}

Car can now use the Engine, but it is not an Engine!

We could’ve used this approach with Person and Employee as well, but how would we set FirstName and LastName? We could make Person public, but we are now breaking a principle called encapsulation (as discussed in the next chapter). We could mimic Person by defining a FirstName and LastName property, but we now have to change the public interface of Employee every time the public interface of Person changes. Additionally, Employee will not be of type Person anymore, so the type of Employee changes and it will not be interchangeable with Person anymore.


A solution will be presented in Chapter 2: Interfaces.

Encapsulation

Encapsulation is the process of hiding the internal workings of our classes. That means we specify a public specification, used by consumers of our class, while the actual work is hidden away. The advantage is that a class can change how it works without needing to change its consumers.

In C# we have four access modifiers keywords which enable five ways of controlling code visibility:

  • private—only visible to the containing class.
  • protected—only visible to the containing class and inheritors.
  • internal—only visible to classes in the same assembly.
  • protected internal—only visible to the same assembly or in derived classes, even in other assemblies.
  • public—visible to everyone.

Let’s say we’re building some class that runs queries on a database. Obviously we need some method of RunQuery that’s visible to every consumer of our class. The method for accessing the database could be different for every database, so perhaps we’re leaving that open for inheritors. Additionally, we use some helper class that’s only visible to our project. Last, we need to store some private state, which may not be altered from outside our class as it could leave it in an invalid state.

Code Listing 12: Access Modifiers

public class QueryRunner

{

    private IDbConnection connection;

    public void RunQuery(string query)

    {

        Helper helper = new Helper();

        if (helper.Validate(query))

        {

            OpenConnection();

            // Run the query...

            CloseConnection();

        }

    }

    protected void OpenConnection()

    {

        // ...

    }

    protected void CloseConnection()

    {

        // ...

    }

}

internal class Helper

{

    internal bool Validate(string query)

    {

        // ...

        return true;

    }

}

If we were to compile this into an assembly and access it from another project we’d only be able to see the QueryRunner class. If we’d create an instance of the QueryRunner we could only call the RunQuery method. If we were to inherit QueryRunner we could also access OpenConnection and CloseConnection. The Helper class and the connection field will be forever hidden from us though.

I should mention that classes can contain nested private classes, classes that are only visible to the containing class. Private classes can access private members of their containing classes.
Likewise, an object can access private members of other objects of the same type.

Code Listing 13: A private nested class

public class SomeClass

{

    private string someField;

    public void SomeMethod(SomeClass otherInstance)

    {

        otherInstance.someField = "Some value";

    }

    private class InnerClass

    {

        public void SomeMethod(SomeClass param)

        {

            param.someField = "Some value";

        }

    }

}

When omitting an access modifier a default is assigned (internal for classes and private for everything else). I’m a big fan of explicitly adding access modifiers though.

A last remark, before moving on, is that subclasses cannot have an accessibility greater than their base class. So if some class has the internal access modifier an inheriting class cannot be made public (but it could be private).

Polymorphism

We’ve seen inheritance and that we can alter the behavior of a type through inheritance. Our Person class had a GetFullName method which was altered in the subclass Employee. We’ve also seen that whenever, at run-time, an object of type Person is expected we can throw in any subclass of Person, like Employee. This is called polymorphism.

In the following example the PrintFullName method takes an object of type Person, but it prints “Rossel, Sander” because the parameter that’s passed into the method is actually of subtype Employee, which overrides the functionality of GetFullName.

Code Listing 14: Polymorphism

class Program

{

    static void Main(string[] args)

    {

        Person p = new Employee();

        p.FirstName = "Sander";

        p.LastName = "Rossel";

        PrintFullName(p);

        // Press any key to quit.

        Console.ReadKey();

    }

    public static void PrintFullName(Person p)

    {

        Console.WriteLine(p.GetFullName());

    }

}

public class Person

{

    public string FirstName { get; set; }

    public string LastName { get; set; }

    public virtual string GetFullName()

    {

        return FirstName + " " + LastName;

    }

}

public class Employee : Person

{

    public decimal Salary { get; set; }

    public sealed override string GetFullName()

    {

        return LastName + ", " + FirstName;

    }

}

We’re going to see a lot more of this in the next chapter.

The Takeaway

The Three Pillars of OOP are the foundation of object-oriented programming. They haven’t been implemented for nothing and they do solve real problems. It’s crucial that you know these features by heart and practice them in your daily code. Think about encapsulation every time you create a class or method. Use inheritance when necessary, but don’t forget it brings extra complexity to the table as well. Be very wary of polymorphism, know which code will run when you inherit classes and override methods. Even if you don’t practice it you’ll come across code that does. Throughout this book we’ll see these be used extensively.

Scroll To Top
Disclaimer
DISCLAIMER: Web reader is currently in beta. Please report any issues through our support system. PDF and Kindle format files are also available for download.

Previous

Next



You are one step away from downloading ebooks from the Succinctly® series premier collection!
A confirmation has been sent to your email address. Please check and confirm your email subscription to complete the download.