C# > Object-Oriented Programming (OOP) > Abstraction > Abstract Classes

Abstract Class Example: Shape Hierarchy

This code snippet demonstrates the use of abstract classes to define a shape hierarchy. The Shape class is abstract, providing a common interface for derived classes like Circle and Rectangle. Concrete classes must implement the abstract methods defined in the base class, enforcing a specific behavior while allowing for individual customization.

Concepts Behind the Snippet

This example showcases several key OOP concepts: Abstraction (hiding complex implementation details), Inheritance (creating new classes based on existing ones), and Polymorphism (the ability of objects of different classes to respond to the same method call in their own way). The abstract class Shape acts as a blueprint, defining what all shapes must do, while the concrete classes (Circle, Rectangle) define how they do it. This approach simplifies code management, promotes reusability, and enhances maintainability.

Abstract Class Definition

The Shape class is declared as abstract, meaning it cannot be instantiated directly. It contains two abstract methods: Area() and Perimeter(). These methods have no implementation in the Shape class; they are only signatures. Concrete subclasses must provide implementations for these methods. The Description() method is virtual which allows the child class to override this method

public abstract class Shape
{
    public abstract double Area();
    public abstract double Perimeter();
    public virtual string Description() 
    { 
        return "This is a generic shape."; 
    }
}

Concrete Class: Circle

The Circle class inherits from Shape. It must provide implementations for the Area() and Perimeter() methods. The override keyword indicates that these methods are overriding the abstract methods defined in the base class. It also override the Description method to provide a more specific description of the object.

public class Circle : Shape
{
    public double Radius { get; set; }

    public Circle(double radius)
    {
        Radius = radius;
    }

    public override double Area()
    {
        return Math.PI * Radius * Radius;
    }

    public override double Perimeter()
    {
        return 2 * Math.PI * Radius;
    }

    public override string Description()
    {
        return $"This is a circle with radius {Radius}.";
    }
}

Concrete Class: Rectangle

The Rectangle class also inherits from Shape and provides its own implementations for Area() and Perimeter(), tailored to the geometry of a rectangle. It also override the Description method to provide a more specific description of the object.

public class Rectangle : Shape
{
    public double Width { get; set; }
    public double Height { get; set; }

    public Rectangle(double width, double height)
    {
        Width = width;
        Height = height;
    }

    public override double Area()
    {
        return Width * Height;
    }

    public override double Perimeter()
    {
        return 2 * (Width + Height);
    }
    public override string Description()
    {
        return $"This is a rectangle with width {Width} and height {Height}.";
    }
}

Usage Example

This code demonstrates how to create instances of the concrete classes (Circle and Rectangle) and call their methods through the abstract Shape type. This is an example of polymorphism: the same method call (Area()) behaves differently depending on the actual type of the object.

Shape circle = new Circle(5);
Shape rectangle = new Rectangle(4, 6);

Console.WriteLine($"Circle Area: {circle.Area()}");
Console.WriteLine($"Rectangle Area: {rectangle.Area()}");
Console.WriteLine($"Circle Description: {circle.Description()}");
Console.WriteLine($"Rectangle Description: {rectangle.Description()}");

Real-Life Use Case

Consider a game development scenario. You might have different types of game objects (e.g., enemies, obstacles, power-ups) each requiring a 'Draw' and 'Update' method. Using an abstract class `GameObject` would enforce that all game objects implement these methods, while allowing each object to have its own specific implementation for drawing and updating its state.

Best Practices

Use abstract classes when you want to define a common interface and enforce certain behaviors in derived classes. Avoid using abstract classes if the common functionality is minimal, and interfaces might be a better choice. Design your abstract classes carefully to ensure they are flexible enough to accommodate future changes.

Interview Tip

Be prepared to explain the difference between abstract classes and interfaces. Abstract classes can have both abstract and concrete methods, while interfaces can only define method signatures (until C# 8). Also, a class can inherit from only one abstract class, but it can implement multiple interfaces.

When to use them

Use abstract classes when you have a clear 'is-a' relationship between classes and when you want to provide some default implementation. For example, a `Vehicle` class might be a good candidate for an abstract class, with concrete subclasses like `Car` and `Truck`. Use them when you expect that the child classes will implement and override the parent class methods.

Memory Footprint

Abstract classes themselves don't directly contribute to memory footprint since they cannot be instantiated. However, the concrete classes inheriting from them will have a memory footprint based on their own fields and methods, plus any inherited members. The virtual methods within the abstract class introduce a small overhead due to the virtual method table.

Alternatives

Alternatives to abstract classes include interfaces and concrete classes with virtual methods. Interfaces are generally preferred when you want to define a contract without providing any implementation. Concrete classes with virtual methods offer more flexibility but don't enforce implementation in derived classes.

Pros

  • Enforce a common interface for derived classes.
  • Allow for partial implementation of functionality in the base class.
  • Promote code reusability and maintainability.

Cons

  • A class can only inherit from one abstract class.
  • Can lead to tightly coupled code if not designed carefully.
  • Can be overkill for simple scenarios.

FAQ

  • What is the difference between an abstract class and an interface?

    An abstract class can have both abstract and concrete methods, while an interface (prior to C# 8) can only define method signatures. A class can inherit from only one abstract class but can implement multiple interfaces. Abstract classes can also contain fields, while interfaces cannot (prior to C# 8 with default interface implementations).
  • Can an abstract class be instantiated?

    No, abstract classes cannot be instantiated directly. They are meant to be inherited from.
  • What happens if a derived class doesn't implement all abstract methods?

    If a derived class doesn't implement all abstract methods from its base class, the derived class must also be declared as abstract.