Using Interfaces and Abstract Classes in C# Programming
 
 

Abstract Classes

Sealed and private modifiers control the way your parent class could be inherited. If you used private for a method, the method could not be inherited or used when the parent class was instantiated. The sealed method blocked the child class from overriding a method. Both of these modifiers were useful when you want to make the parent class available to other classes.

An abstract class takes inheritance a step further and allows child classes to inherit from the parent class but blocks other classes from instantiating it. Let's discuss why you would want to create an abstract class using the same Animal and Lion class examples.

We used the Animal class as the parent class and the Lion class as the derived child class. We were still able to instantiate the animal class, but this open access to the Animal class could create bugs in our code. Since you always need a type of animal, a coder shouldn't be able to instantiate the Animal class uninhibited. He should be forced to define an animal and use the child class to create the zoo program. The Animal class is perfect as an abstract class.

We also covered the virtual modifier in the last chapter. An abstract class can't have any virtual methods, because an abstract class is implicitly virtual. Remember that an abstract class is used by the child class for broader methods that are usually overridden in child classes. There are a few rules when you create an abstract class. The following is a list of rules for abstract classes.

  • You cannot use the sealed modifier with abstract class methods

  • Only abstract methods can be defined in abstract classes. In other words, you can't use the abstract modifier in a public open parent class.

  • You can't use the virtual modifier, because an abstract method is virtual on its own.

  • You cannot have any static methods or variables in an abstract class even though it can't be instantiated.

  • Abstract classes are similar to interfaces, but interfaces can only have declarations of methods and abstract classes can have non-abstract methods and definitions.

Let's take a look at an example using the Animal class. The following Animal class is the same as the last chapter, but we've used the abstract modifier.

abstract class Animal

{

    public Animal()

    {

        Console.WriteLine("Main Animal");

    }

    private void Hear()

    {

        Console.WriteLine("All animals have ears");

    }

    public virtual void Talk()

    {

        Console.WriteLine("All animals can talk");

    }

    public void See()

    {

        Console.WriteLine("All animals can see");

    }

}

In the above code, we only changed the class from public to abstract. We didn't change any of the other code, and as you can see the compiler will give us an error. If we review the rules for abstract classes, you'll see that we need to change the private modifier for the Hear method. We also need to change the virtual modifier, because an abstract class in its entirety is virtual implicitly.

Let's fix the class so that it compiles.

abstract class Animal

{

    public Animal()

    {

        Console.WriteLine("Main Animal");

    }

    public void Hear()

    {

        Console.WriteLine("All animals have ears");

    }

    public abstract void Talk()

    {

        Console.WriteLine("All animals can talk");

    }

    public void See()

    {

        Console.WriteLine("All animals can see");

    }

}

We removed the private modifier from the Hear method, but notice that we replaced virtual with the abstract modifier. Since abstract is also virtual, your child classes can override an abstract method. You must change any virtual methods to abstract when you create them within abstract classes. You can have abstract and non-abstract methods within your class, so the other methods can be left as-is without causing any compiler errors.

Now, let's look at the Lion class again with the parent Animal class.

abstract class Animal

{

    public Animal()

    {

        Console.WriteLine("Main Animal");

    }

    public void Hear()

    {

        Console.WriteLine("All animals have ears");

    }

    public abstract void Talk()

    {

        Console.WriteLine("All animals can talk");

    }

    public void See()

    {

        Console.WriteLine("All animals can see");

    }

}

public class Lion : Animal

{

    public Lion()

    {

        Console.WriteLine("Lion is an animal");

    }

        public override void Talk()

    {

        Console.WriteLine("A lion roars");

    }

}

Notice that the Lion class derives from its parent Animal class, but nothing has changed from the previous chapter's class. That's because the Lion class can derive from the abstract class in the same way it can derive from a regular public class.

You can also derive from an abstract class and then use the child class as its own abstract class. For instance, you might want to add an extra layer between Animal and Lion. A lion is a breed of cat, and you want to create an abstract class called Cat that all breeds of cats derive from. You want to make the Cat class abstract, because you don't want any external classes to edit it.

Let's take a look at how we can write this code.

abstract class Animal

{

    public Animal()

    {

        Console.WriteLine("Main Animal");

    }

    public void Hear()

    {

        Console.WriteLine("All animals have ears");

    }

    public abstract void Talk()

    {

        Console.WriteLine("All animals can talk");

    }

    public void See()

    {

        Console.WriteLine("All animals can see");

    }

}

public class Cat : Animal

{

    public Cat()

    {

        Console.WriteLine("A cat is an animal");

    }

        public override void Talk()

    {

        Console.WriteLine("A cat purrs");

    }

}

Notice that in our Cat class, we inherit from the Animal class, but we also override the Talk method. When you specify that a method is abstract, your child class must override it. In this example, we've changed the methods in the Cat class to be specific for their class.

Now we can create the Lion class. Instead of deriving directly from the Animal class, we want the Lion class to inherit from the appropriate Cat class. Let's take a look at the code.

abstract class Animal

{

    public Animal()

    {

        Console.WriteLine("Main Animal");

    }

    public void Hear()

    {

        Console.WriteLine("All animals have ears");

    }

    public abstract void Talk()

    {

        Console.WriteLine("All animals can talk");

    }

    public void See()

    {

        Console.WriteLine("All animals can see");

    }

}

public class Cat : Animal

{

    public Cat()

    {

        Console.WriteLine("A cat is an animal");

    }

        public override void Talk()

    {

        Console.WriteLine("A cat purrs");

    }

}

public class Lion : Cat

{

    public Lion()

    {

        Console.WriteLine("A lion is a cat, which is an animal");

    }

        public override void Talk()

    {

        Console.WriteLine("A lion roars");

    }

}

Notice that we've created our new Lion class and used an override Talk method again. Let's take a look at how you would instantiate this code

Lion lion = new Lion();

lion.Hear();

lion.Talk();

lion.See();

The output would be the following text.

Main animal

A lion is an animal

A lion is an cat, which is an animal

All animals can hear

A lion roars

All animals can see

If you recall from the previous chapter, the constructor for each parent class executes when you instantiate a child class. We have three levels of inheritance in the new zoo program. The Lion class inherits from Cat, and Cat inherits from Animal. Therefore, all three constructors trigger their own output. The constructors can be used to automatically assign values to member variables within your class or even execute methods and other class-specific code.

Interfaces

Interfaces are one way to create a polymorphic program. As we know from the previous chapter, polymorphism allows you to use the same methods for various output depending on the returned data type and parameters defined.

Think of interfaces as a hierarchy structure. Several classes connect to an interface, so you just need to inherit from one interface to call multiple classes instead of inheriting from multiple classes.

We mentioned that interfaces are similar to abstract classes, but they have a different purpose and set of rules. An interface can't contain any member variables or definitions of methods. They can only contain a list of method declarations to the various classes connected to each interface.

With interfaces, you don't need to override methods. Instead, you define your own methods within each class. Let's take a look at an example.

Using our zoo application, let's assume we want an interface named Mammals that connect all animals that fit the class description. Our Lion class should be accessible through the interface. When you create an interface for a class, all other classes are forced to call the interface instead of inheriting directly from the classes.

interface Mammal

{

    void Hear();

    void See();

    void Talk();

}

That's all it takes to turn our zoo program into an interface-driven application. The interface is given the name Mammal and all methods are defined within the interface. We only have three methods, and each one is set as void. The return type in the class methods must match the return type in the interface. Notice that we don't define the methods in the interface. We just declare the methods that can be inherited by external classes.

With the interface set up, we can now hook our classes into the interface hierarchy. First, let's set up our Animal class.

interface Mammal

{

    void Hear();

    void See();

    void Talk();

}

public class Animal : Mammal

{

    public Animal()

    {

        Console.WriteLine("Main Animal");

    }

    public void Hear()

    {

        Console.WriteLine("All animals have ears");

    }

    public abstract void Talk()

    {

        Console.WriteLine("All animals can talk");

    }

    public void See()

    {

        Console.WriteLine("All animals can see");

    }

    public void Walk()

    {

        Console.WriteLine("All animals can walk");

    }

}

We set the Animal class to inherit from the Mammal class. Notice that we added the Walk method from the last chapter. The interface does not declare the Walk method, so this method is invisible to classes that inherit from the Mammal class. You can still implement the Walk method from within your Animal class. Any class that inherits directly from the Animal class can also access the Walk method. However, any class that inherits only from Mammal will not be able to use the Walk method. This type of polymorphism allows much more flexibility in your code.

Just remember that you don't need to declare all class functions in an interface, but you must have a definition for each interface member.

Now we can add the Lion class and inherit from Mammal.

interface Mammal

{

    void Hear();

    void See();

    void Talk();

}

public class Animal : Mammal

{

    public Animal()

    {

        Console.WriteLine("Main Animal");

    }

    public void Hear()

    {

        Console.WriteLine("All animals have ears");

    }

    public abstract void Talk()

    {

        Console.WriteLine("All animals can talk");

    }

    public void See()

    {

        Console.WriteLine("All animals can see");

    }

    public void Walk()

    {

        Console.WriteLine("All animals can walk");

    }

}

public class Lion : Mammal, Animal

{

    public Lion()

    {

        Console.WriteLine("Lion is an animal");

    }

        public void Talk()

    {

        Console.WriteLine("A lion roars");

    }

    public void Hear()

    {

        Console.WriteLine("Lions have ears");

    }

    public void See()

    {

        Console.WriteLine("All animals can see");

    }

}

With our new interface programming, we've made some changes to the Lion class. Because the interface declares the Hear, See, and Talk methods, classes that inherit from this interface must have these methods available. Notice that we changed the parent class for Lion to the Mammal interface.

We also removed the override modifier. When you create methods that match an interface declaration, you automatically override the method so there's no reason for the modifier.

We still want to use the Walk method that isn't defined in the interface and isn't defined in the Lion class. C# lets you derive from multiple classes. You just need to separate each class with a comma. Notice that we decided to have the Lion class inherit from both the interface and the Animal class, so we can still instantiate Animal and use its Walk method.

Now let's take a look at how we call this code. Suppose we now want to see the output for each method. We can't instantiate the instance, so we need to create a class that then implements the classes and methods we just created. Since the Main method is the entry point for a C# program, let's use the main function to now implement our main animal classes.

interface Mammal

{

    void Hear();

    void See();

    void Talk();

}

public class Animal : Mammal

{

    public Animal()

    {

        Console.WriteLine("Main Animal");

    }

    public void Hear()

    {

        Console.WriteLine("All animals have ears");

    }

    public abstract void Talk()

    {

        Console.WriteLine("All animals can talk");

    }

    public void See()

    {

        Console.WriteLine("All animals can see");

    }

    public void Walk()

    {

        Console.WriteLine("All animals can walk");

    }

}

public class Lion : Mammal, Animal

{

    public Lion()

    {

        Console.WriteLine("Lion is an animal");

    }

        public void Talk()

    {

        Console.WriteLine("A lion roars");

    }

    public void Hear()

    {

        Console.WriteLine("Lions have ears");

    }

    public void See()

    {

        Console.WriteLine("All animals can see");

    }

}

public class Zoo

{

      public static void Main()

   {

            Lion lion = new Lion();

lion.Hear();

lion.Talk();

lion.See();

lion.Walk();

      }

}

And the output looks like the following.

Main animal

A lion is an animal

All animals can hear

A lion roars

All animals can see

All animals can walk

As you can see, we still get the same output with the interface design model. We still get the same output since we also derive from the Animal class, and we've added a layer of polymorphism to our code.

As you build larger and more complex programs, interfaces and abstract classes will be necessary to organize your code and ensure that the design is scalable. When you create modular code with OOP, it becomes easier to refine and add new components as they're needed.