Blog from Saravanan Arumugam

Let us talk about Technologies

Monthly Archives: September 2010

Rating Engine Implementation using Abstract Factory Pattern




I recently attended a code challenge contest in our company and won the contest. 

As a prize I was given the “WCF 4 Unleashed” book worth $40. 

 

The challenge in the contest was to implement a Rating engine with the Abstract Factory Pattern.  

I would like to present the Abstract Factory Implementation for the Rating Engine, that Ii wrote to win the prize over here. (This is supposed to rate the health care products – Medical/Dental) 

 

The underlying idea of Abstract factory pattern is that the implementations would be on the generic family of classes (Product, Rating Algorithm). 

The real classes being used would not be disclosed to the client. There would be a set of Factory classes to generate the specific concrete classes (which will be substituted on the places of Generic class declaration), based on the program scenario.

[http://www.dofactory.com/Patterns/PatternAbstract.aspx]

 

Following is the class diagram of the EightFingerGenie.ProductRating.RatingEngine namespace.

[Note: EightFingerGenie is the Organization name and ProductRating is the application that uses the RatingEngine]

 

image

 

Brief explanation on the diagram

Here the product factory is an abstract class which is capable of creating a product (Either Medical/Dental Product) when CreateProduct() method is called.

 

ProductFactory class is also responsible for deciding the rating algorithm to be followed for rating the products.

 

Product is the base class to represent either a medical or dental product.

 

It holds most of the definitions of the Product.

Medical and Dental product classes implement the Product Specific implementations. (In my implementation there are no such specific implementations)

 

Implementation of Abstract Factory Pattern

Before going in for explanation of different classes, let’s have a look at the implementation/use of Abstract Factory pattern assuming all the supporting classes (Shown in the diagram above) are available.

 

namespace EightFingerGenie.ProductRating
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine();
            Console.WriteLine(“Processing quote with both medical and dental products…”);
            Quote comboQuote = GetComboQuote();
            comboQuote.RateThisQuote();

            Console.ReadLine();
        }

        private static Quote GetComboQuote()
        {
            Quote comboQuote = new Quote();

            MedicalProductFactory medicalFactory = new MedicalProductFactory();
            DentalProductFactory dentalFactory = new DentalProductFactory();

            Product[] products = {
                medicalFactory.CreateProduct(“Medical Product 1″),
                dentalFactory.CreateProduct(“Dental Product 2″),
                medicalFactory.CreateProduct(“Medical Product 3″),
                dentalFactory.CreateProduct(“Dental Product 4″)
            };

            //Rating Algorithm is made visible upto the consumer level in order to provide the
            //consumer a flexibility to chose the algorithm.
            Array.ForEach<Product>(products, delegate(Product p)
            {
                if (p.ProductType == AvailableProductType.Medical)
                    p.RatingAlgorithm = medicalFactory.GetRatingAlgorithm();
                else
                    p.RatingAlgorithm = dentalFactory.GetRatingAlgorithm();
            }
                    );

            comboQuote.Products.AddRange(products);

            return comboQuote;
        }

    }
}

 

In the code above, a Quote object can be imagined like a container to multiple Products (Quote object’s definition can be found under this paragraph).

At a high level, we can notice that in the implementation, the code is written to use Product class alone to contain both Medical and Dental products.

The code will use RatingAlgorithm class to represent any kind of RatingAlgorithm.

 

But the type of product and the type of algorithm to rate the product are all provided by the ProductFactory Classes.

 

Quote Class

The quote class holds a list of products, and some quote specific properties.

RateThisQuote() method rates every products present in the Quote class.

 

namespace EightFingerGenie.ProductRating
{
    public class Quote
    {
        #region Constructors

        public Quote()
        {
        }

        #endregion

        #region Properties

        /// <summary>
        /// Gets the list of products in the Quote
        /// </summary>
        public List<Product> Products
        {
            get { return products; }
        } private List<Product> products = new List<Product>();

        /// <summary>
        /// Gets the presence of Medical product in the Quote
        /// </summary>
        public bool HasMedical
        {
            get
            {
                if (this.products != null)
                    return this.products.Exists(delegate(Product p) { return p.ProductType == AvailableProductType.Medical; });
                else
                    return false;
            }
        }

        /// <summary>
        /// Gets the presence of Dental product in the Quote
        /// </summary>
        public bool HasDental
        {
            get
            {
                if (this.products != null)
                    return this.products.Exists(delegate(Product p) { return p.ProductType == AvailableProductType.Dental; });
                else
                    return false;
            }
        }

        #endregion

        #region Public Methods
        /// <summary>
        /// Rates all the products associated in the quote
        /// </summary>
        public void RateThisQuote()
        {
            if (this.products != null)
            {
                foreach (Product product in this.products)
                    Console.WriteLine(“Product: {0}\n\tResult: {1}\n”, product.ProductName, product.RateProduct());
            }
        }

        #endregion
    }
}

 

Product Class

This class represents the base product which is expected to be emitted by the ProductFactory.

This holds some Product Specific properties and RateProdut() method.

RateProduct() method uses the appropriate rating algorithm to rate the product, based on the category of the product (Medical / Dental).

using System;
using System.Collections.Generic;
using System.Text;
using EightFingerGenie.ProductRating.RatingEngine.Common;
using EightFingerGenie.ProductRating.RatingEngine.RatingAlgorithms;

namespace EightFingerGenie.ProductRating.RatingEngine.Products
{
    public abstract class Product
    {
        #region Constructors

        /// <summary>
        /// Creates an instance of a product
        /// </summary>
        /// <param name=”productName”>Name of the product</param>
        /// <remarks>
        /// Product instance cannot be constructed directly.
        /// This class can be instantiated only by its derived classes.
        /// Use the product factory classes to create a Product.
        /// </remarks>
        /// <exception cref=”ArgumentException”></exception>
        protected Product(string productName)
        {
            this.productName = productName ?? string.Empty;
        }

        #endregion

        #region Properties

        /// <summary>
        /// Gets or sets name of the product
        /// </summary>
        public string ProductName
        {
            get { return productName; }
            set { productName = value; }
        } private string productName;

        /// <summary>
        /// Gets the type of the product
        /// </summary>
        public AvailableProductType ProductType
        {
            get { return productType; }

            //Only the derived classes should be able to set the property.
            protected set { productType = value; }
        } private AvailableProductType productType;

        public RatingAlgorithm RatingAlgorithm
        {
            get { return ratingAlgorithm; }
            set { ratingAlgorithm = value; }
        } private RatingAlgorithm ratingAlgorithm;

        #endregion

        #region Public Methods

        /// <summary>
        /// Rates the current product
        /// </summary>
        /// <returns>Returns the result in the text form</returns>
        public string RateProduct()
        {
            return this.ratingAlgorithm.Rate();
        }

        #endregion
    }
}

 

MedicalProdcut and DentalProduct Classes 

With the general product specific properties specified in the base Product (abstract) class, MedicalProduct and DentalProduct class define the category specific properties (In the example, I have not implemented any special category specific property except the type of the Product). In the constructor of these classes, product type is set.

 

using System;
using System.Collections.Generic;
using System.Text;
using EightFingerGenie.ProductRating.RatingEngine.Common;

namespace EightFingerGenie.ProductRating.RatingEngine.Products
{
    public class MedicalProduct : Product
    {
        #region Constructors

        public MedicalProduct(string productName)
            : base(productName)
        {
            this.ProductType = AvailableProductType.Medical;
        }

        #endregion
    }
}

 

namespace EightFingerGenie.ProductRating.RatingEngine.Products
{
    public class DentalProduct : Product
    {
        #region Constructors

        public DentalProduct(string productName)
            : base(productName)
        {
            this.ProductType = AvailableProductType.Dental;
        }

        #endregion
    }
}

 

ProductFactory Class

This is an abstract class and it exposes two abstract methods.

CreateProduct() method is used for creation of a specific type of product. For example, MedicalProductFactory.CreateProduct() would automatically create a MedicalProduct.

 

namespace EightFingerGenie.ProductRating.RatingEngine.Products.Factories
{
    public abstract class ProductFactory
    {
        /// <summary>
        /// Creates a product
        /// </summary>
        /// <param name=”productName”>Name of the product.</param>
        /// <returns>Returns a product instance.</returns>
        public abstract Product CreateProduct(string productName);

        /// <summary>
        /// Produces a rating algorithm
        /// </summary>
        /// <returns>Returns a RatingAlgorithm instance</returns>
        public abstract RatingAlgorithm GetRatingAlgorithm();
    }
}

 

MedicalProductFactory, DentalProductFactory Classes

These are the classes that substitute the concrete family of classes on the generic implementation. MedicalProduct/DentalProduct is overlaid on the places of Product and MedicalRatingAlgorithm/DentalRatingAlgorithm is overlaid on the places of RatingAlgorithm.

namespace EightFingerGenie.ProductRating.RatingEngine.Products.Factories
{
    public class MedicalProductFactory : ProductFactory
    {
        public override Product CreateProduct(string productName)
        {
            return new MedicalProduct(productName);
        }

        public override RatingAlgorithm GetRatingAlgorithm()
        {
            return new MedicalRatingAlgorithm();
        }
    }
}

namespace EightFingerGenie.ProductRating.RatingEngine.Products.Factories
{
    public class DentalProductFactory : ProductFactory
    {
        public override Product CreateProduct(string productName)
        {
            return new DentalProduct(productName);
        }

        public override RatingAlgorithm GetRatingAlgorithm()
        {
            return new DentalRatingAlgorithm();
        }
    }
}

 

RatingAlgorithm Class

Finally, the RatingAlgorithm Family. For simplicity I have implemented just a Rate() method in the class.

 

namespace EightFingerGenie.ProductRating.RatingEngine.RatingAlgorithms
{
    public abstract class RatingAlgorithm
    {
        /// <summary>
        /// Rates the associated product
        /// </summary>
        /// <returns>Returns the result in the text form.</returns>
        public abstract string Rate();
    }
}

The derived classes of this class have to implement Rate() method on specific product types.

 

MedicalRatingAlgorithm/DentalRatingAlgorithm Classes

Again for simplicity they just output a string specifying the name of the algorithm.

 

namespace EightFingerGenie.ProductRating.RatingEngine.RatingAlgorithms
{
    public class MedicalRatingAlgorithm : RatingAlgorithm
    {
        public override string Rate()
        {
            return “Rated according to the medical rating algorithm”;
        }
    }
}

namespace EightFingerGenie.ProductRating.RatingEngine.RatingAlgorithms
{
    public class DentalRatingAlgorithm : RatingAlgorithm
    {
        public override string Rate()
        {
            return “Rated according to the dental rating algorithm”;
        }
    }
}

 

Output

The implementation of the abstract factory on Rating Engine outputs the following.

 image