C# Coding Solutions—Functors in Practice

Microsoft .NET Framework, ASP.NET, Visual C# (CSharp, C Sharp, C-Sharp) Developer Training, Visual Studio


Jump to: navigation, search
CSharp-Online.NET:Articles
C# Articles

C# Coding Solutions

© 2006 Christian Gross

Functors in Practice

Functors are easy to explain, and providing a reason for using them is not difficult. What is more difficult is actually using them. Using them can be awkward because of the separation of functionality between the functor and the type being managed.

I will use a movie-ticket application to illustrate the details of using a functor, interface, and implementation as a single solution. The heart of the ticketing system is Ticket, which represents a movie ticket and is defined as follows:

public class Ticket {
    private double _price;
    private int _age;
 
    public Ticket( double price, int age) {
        _price = price;
        _age = age;
    }
 
    public virtual double Price {
        get {
            return _price;
        }
    }
    public virtual int Age {
        get {
            return _age;
        }
    }
}

Ticket has only two data members, _age and _price, which represent the age of the moviegoer and the price of the ticket. The age and price are defined when Ticket is instantiated. The properties Age and Price retrieve the values of age and price, respectively.

When selling movie tickets, ticket sales is an important statistic. Ticket sales indicate the popularity and success of a movie. In a traditional programming approach ticket sales are calculated by iterating a collection of Tickets. The total would be counted each time a ticket sales total is asked for. Another approach would be to use a closure functor.

The following source code calculates the total sales for a movie using a closure functor:

public class TicketsBuilder {
    private class StatisticsCounter {
        private double _runningTotal;
        public StatisticsCounter() {
            _runningTotal = 0.0;
        }
        public void ClosureMethod( Ticket ticket) {
            _runningTotal += ticket.Price;
        }
    }
    public static IList<Ticket> CreateCollection() {
        return new ClosureAddProxy< Ticket>(
            new List< Ticket>(),
            new DelegateClosure< Ticket>(
                new StatisticsCounter().ClosureMethod));
    }
}

TicketsBuilder is a class that has a method, CreateCollection, that creates an IList<> instance. The method CreateCollection instantiates the type ClosureAddProxy<>, which implements the Proxy pattern for the closure functor. The parent collection for ClosureAddProxy<> is List<>. The delegate used for the closure functor is StatisticsCounter.ClosureMethod.

Like in the comparer functor example, every time an element is added to the returned IList<> instance DelegateAddClosure<> will call the closure delegate. Each time the closure delegate StatisticsCounter.ClosureMethod method is called, the input price is added to the total ticket sales.

The class StatisticsCounter.ClosureMethod is not entirely accurate, however. Imagine that a person buys a ticket and then asks for her money back or decides to watch a different movie. The ticket would need to be removed from the collection, and the total sales variable _runningTotal would need to be decremented by the price of the removed ticket. Even if it is impossible to get your money back, it is not possible to use such logic for all applications. The problem of the corrupted data needs be solved. Ticket sales can only be incremented because ClosureAddProxy overrides the methods that add elements to the collection. The solution is to use a closure delegate that overrides the remove-element methods.

The following is an example of two closure functors implementing the add-element and remove-element methods:

public class TicketsBuilder {
    private class StatisticsCounter {
        private double _runningTotal;
        public StatisticsCounter() {
            _runningTotal = 0.0;
        }
        public void ClosureAddMethod( Ticket ticket) {
            _runningTotal += ticket.Price;
        }
        public void ClosureRemoveMethod( Ticket ticket) {
            _runningTotal -= ticket.Price;
        }
    }
    public static IList<Ticket> CreateCollection() {
        StatisticsCounter cls = new StatisticsCounter();
        IList<Ticket> parent = new ClosureAddProxy< Ticket>(
            new List< Ticket>(),
            new DelegateClosure< Ticket>( cls.ClosureAddMethod));
        return new ClosureRemoveProxy<Ticket>( parent,
            new DelegateClosure< Ticket>(
                cls.ClosureRemoveMethod));
    }
}

In the modified implementation of TicketsBuilder, StatisticsCounter has two closure methods: ClosureAddMethod and ClosureRemoveMethod. The method ClosureAddMethod is used to increment the ticket sales, and the method ClosureRemoveMethod is used to decrement ticket sales. The method CreateCollection is modified to create two closure proxies: ClosureAddProxy and ClosureRemoveProxy. For each proxy the appropriate closure method is associated.

The calculation of the grand total for the ticket sales works. But there is a very big problem—TicketBuilder creates an instance of StatisticsCounter, but the instance of StatisticsCounter is not saved for reference. In other words, statistics are being generated but no client has access to those values. The solution for retrieving the total ticket-sales revenue is to create a property called RunningTotal.

When working with functors, remember the following points:

  • Using functors does not come naturally because we developers are not wired to write code in such a structure. After you’ve written some functors it will feel natural.
  • For most cases there are four types of functors: Comparer, Closure, Predicate, and Transformer.
  • Functors are realized using delegates. The functors can be called directly in your source code.
  • Often functors are used in conjunction with the Proxy pattern. If a Proxy pattern is used, you will need to define interfaces and/or abstract base classes.
  • Functors make it possible to portably use specific application logic in multiple places without having to write special code to call the functors.


Previous_Page_.gif Next_Page_.gif


Personal tools