Abstract classes vs. interfaces in C#

Introduction

Functionality in abstract classes and interfaces in C# is fairly similar, however use of each has supporting and opposing arguments. This article discusses the theoretical aspects of these programming concepts backed up with a working example in C# at the end of this post.

There are three areas to consider when deciding which to implement for a specific purpose within your project:

1. Inheritance

Simply put, a class can only inherit from one other class. If you inherit from an abstract class, more can be achieved. Conversely, a class can implement an unlimited number of interfaces as well as still being able to inherit from a base class.

2. Structure

Interfaces offer a greater degree of design flexibility because they can be used by any class regardless of type.

3. Versioning

As mentioned, an abstract class can contain interfaces and also have the ability to inherit from base classes. This makes version control an awful lot easier.

Changing an interface will result in all classes which depend on it breaking. Once this has been used, its contents and functionality is permanant. Code based on interfaces can only be improved or extended by added new interfaces.


Scenarios where an abstract class is suitable

  • The class you’re developing will be distributed to many other developers or clients or you’re creating an API.
  • You wish to create a common base class for a family of types.
  • You want to provide a default behaviour

Scenarios where an interface is suitable

  • You’re creating a class which will be used in an internal project which won’t be shared with a large number of developers or used in an API
  • You’re looking to provide polymorphic behaviour without subclassing or you want a specific type to display multiple behaviours.

If you are looking for a more detailed answer to this question, what is difference between abstract class and interface, then visit this link for further reading.


Example

Here is an example using code.  I’ve created a Product abstract class and an IProduct interface.  I’ve commented throughout the code and included a test class at the end. 

In the test class, the DisplayPrice method when using the abstract class displays the product price for staff, applying no markup, whereas it shows the full retail price when the customer interface is called. 

The Abstract Class ‘Product’

namespace com.kewney.examples.abintex{    ///     /// Summary for Product    ///     public abstract class Product    {        ///         /// Define Fields        ///         protected int pId;        protected String pName;        protected String pDescription;        protected decimal pCost;        ///         /// properties        ///         public abstract int ID        {            get { }            set { }        }        public abstract String ProductName        {            get { }            set { }        }        public abstract String ProductDescription        {            get { }            set { }        }        public abstract decimal ProductBuyPrice        {            get { }            set { }        }        //Example Method        public String Locate()        {            return String.Format("Product {0}: {1} located", pId, pName);        }        //The price will be different when displayed to the consumer as this will have margin added, so let's delegate the price calculation to each implementation        public abstract decimal DisplayPrice();    }}

The Interface ‘IProduct’

namespace com.kewney.examples.abintex{    ///     /// Summary for IProduct    ///     public interface IProduct    {        int ID        {            get { }            set { }        }        String ProductName        {            get { }            set { }        }        String ProductDescription        {            get { }            set { }        }        decimal ProductBuyPrice        {            get { }            set { }        }        String Locate();        decimal DisplayPrice();    }}

Inherited Objects #1: Product_Staff

namespace com.kewney.examples.abintex{    ///     /// Summary for Product_Staff    /// Note: In this example, the DisplayPrice is calculated using the abstract class    ///         public class Product_Staff : Product // Inheriting from the abstract class Product    {        //It's inheriting from the abstract class therefore no properties or fields should be present here        public Product_Staff() { }        public override int ID        {            get            {                return pId;            }            set            {                pId = value;            }        }                public override String ProductName        {            get            {                return pName;            }            set            {                pName = value;            }        }        public override String ProductDescription        {            get            {                return pDescription;            }            set            {                pDescription = value;            }        }        public override decimal ProductBuyPrice        {            get            {                return pCost;            }            set            {                pCost = value;            }        }        //Locate method implemented in the abstract class        public new String Locate()        {            return base.Locate();        }              //This abstract method that is different for staff than for customers so I'm overriding it here        public override decimal DisplayPrice()        {            return String.Format("Product {0} price for staff is {1}, calculated via abstract class", base.pName, (base.pCost * 1));        }    }}

Inherited Objects #2: Product_Customer

namespace com.kewney.examples.abintex{    ///     /// Summary for Product_Customer    /// Note: In this example, the DisplayPrice is calculated using the abstract class    ///     public class Product_Customer : IProduct // Inheriting from the interface IProduct    {        //Properties and fields defined here        protected int pId;        protected String pName;        protected String pDescription;        protected decimal pCost;        public Product_Customer()        {            //            // TODO: Add constructor here            //        }        public int ID        {            get            {                return pId;            }            set            {                pId = value;            }        }        public String ProductName        {            get            {                return pName;            }            set            {                pName = value;            }        }        public String ProductDescription        {            get            {                return pDescription;            }            set            {                pDescription = value;            }        }        public decimal ProductBuyPrice        {            get            {                return pCost;            }            set            {                pCost = value;            }        }        //N.B Locate is completed within the object this time        public String Locate()        {            return String.Format("Product {0} found", pName);        }        //This abstract method that is different for staff than for customers so I'm overriding it here        public override decimal DisplayPrice()        {            return String.Format("Product {0} price for staff is {1}, calculated via interface", base.pName, (base.pCost * 2));        }    }}

Example to test Interface and Abstract Class

namespace com.kewney.examples.abintex{    public class TestAbstractAndInterface    {        private void TestInterface() // As Customer        {            IProduct prod;            Product_Customer prod = new Product_Customer();            prod = emp1;            prod.ID = 1;            prod.ProductName = "simfree.net Account";            prod.ProductDescription = "the easiest, most cost effective way to use text messaging in business";            prod.ProductBuyPrice = 24.00;            //call the DisplayPrice method            MessageBox.Show(prod.DisplayPrice().ToString());        }        private void TestAbstract() // As Staff        {            Product prod;            prod = new Product_Staff();                        prod.ID = 1;            prod.ProductName = "simfree.net Account";            prod.Product Description = "the easiest, most cost effective way to use text messaging in business";            prod.ProductBuyPrice = 24.00;            //call the DisplayPrice method            MessageBox.Show(prod.DisplayPrice().ToString());        }    }}

If you’re looking for more information or are interested in a view from a developer taking a SOLID perspective, check out Pradeep’s post here.

Leave a Reply