Recently I had a discussion with someone where I bluntly stated I didn’t like virtual functions. Now that I’m reading more about software design principles I can formulate myself a bit better. After a few quick google searches, I couldn’t find the following argumentation anywhere, so I thought it might be relevant to post about it.
First, to rephrase myself: “Prefer pure virtual functions over non-pure virtual functions where possible.” As a quick refresher, pure virtual functions are required to be implemented by a derived class, while non-pure aren’t. Classes containing pure virtual functions are called abstract classes.
In good tradition I’ll start by quoting a design principle to which my statement relates.
Open/Closed Principle: Software entities should be open for extension, but closed for modification. – Bertrand Meyer
More specifically, the “Polymorphic Open/Closed Principle”. This definition advocates inheritance from abstract base classes. The existing interface is closed to modifications and new implementations must, at a minimum, implement that interface.
Although most people understand abstract classes, and how it relates to the template method pattern, I often see implementations relying on overridable functions instead. I rarely use non-pure virtual methods. Only after considering all other possibilities, I might find an overridable virtual method the cleanest approach.
Consider the following C# example taken from the msdn documentation on override:
public class Square { public double x; public Square( double x ) { this.x = x; } public virtual double Area() { return x * x; } } class Cube : Square { public Cube( double x ) : base( x ) { } public override double Area() { return 6 * base.Area(); } }
I understand that the sole intent of the example is to demonstrate the usage of the override keyword, so I’m not criticizing it. It just serves as a nice example where I find an other approach to be a better implementation. I also added additional functionality to highlight the Open/Closed Principle.
public abstract class AbstractShape { public abstract double Area(); public override string ToString() { return GetType().ToString() + ": area " + Area(); } } public class Square : AbstractShape { private double _sideLength; public Square( double sideLength ) { _sideLength = sideLength; } public override double Area() { return _sideLength * _sideLength; } } public class AbstractCompositeShape : AbstractShape { private List<AbstractShape> _childShapes; protected AbstractCompositeShape( List<AbstractShape> shapes ) { _childShapes = shapes; } public override double Area() { double totalArea = 0; foreach ( AbstractShape shape in _childShapes ) { totalArea += shape.Area(); } return totalArea; } } public class Cube : AbstractCompositeShape { private Cube( List<AbstractShape> sides ) : base( sides ) { } public static Cube FromSideLength( double sideLength ) { List<AbstractShape> sides = new List<AbstractShape>(); for ( int i = 0; i < 6; ++i ) { sides.Add( new Square( sideLength ) ); } return new Cube( sides ); } }
Of course this example shouldn’t be considered as a real world example. An implementation is mainly dependant on its usage. This example could be useful where unique identification of every face is important, and where a lot of other shapes are expected. Just to state a few things that would also be worth considering: generics, localization, performance, …
The second sample prevents misuse and promotes reuse, which are advantages of following the Open/Closed Principle like this. You can’t forget to implement a required function. It also demonstrates there are more alternatives to virtual functions than just abstract functions. Composition was used here to achieve the same goal as the first example.
As a set of guidelines I would specify:
- When a function always needs a custom implementation in an extending class, never use non-pure virtual functions.
- When it is possible to group a certain default implementation in a subclass, do so instead of defining it as a default non-pure virtual function. (e.g. Area() function of AbstractCompositeShape in the example above.)
- Question yourself why the behavior of a function needs to be changed. Can this new behavior be encapsulated? (e.g. Usage of composition in AbstractCompositeShape.)
- Not overriding a certain virtual method shouldn’t have major functional consequences. (e.g. ToString method.)
The lead architect of C#, Anders Hejlsberg, explains why non-virtual is default in C#, as opposed to Java. He also advises caution when making functions virtual.
UPDATE
A more Java oriented argumentation can be found here.