© The Author(s), under exclusive license to APress Media, LLC, part of Springer Nature 2023
V. SarcarSimple and Efficient Programming with C# https://doi.org/10.1007/978-1-4842-8737-8_8

8. Make Efficient Templates Using Hooks

Vaskaran Sarcar1  
(1)
Kolkata, West Bengal, India
 

This chapter will show you two important techniques. First, you will learn to use a template method. Why is this important? It is important because template methods are one of the fundamental techniques for code reuse. Suppose you follow a multistep algorithm to achieve a task. Using a template method, you can redefine some of these steps (but not all the steps) of the algorithm without altering the calling sequence of these steps.

This chapter starts with a demonstration that uses a template method. In addition, you’ll use a hook method to enhance this application.

The Problem Statement

A dealer (or seller) can sell various products such as televisions, washing machines, or music players. Assume that you know such a dealer. You can visit a showroom to purchase a television. You can visit the same showroom to purchase a washing machine for your home. In each case, you can summarize the overall activity in the following order:
1. You visit the dealer showroom.
2. You purchase a product.
3. The dealer generates a bill for you.
4. The dealer delivers the product to you.

Can you make an application that mimics this scenario?

Initial Program

In the upcoming program, I assume that you purchase a washing machine and a television from the same dealer. When you purchase a television, you see the following output:
1. The customer visits a dealer showroom.
2. The customer purchases a television.
3. The bill is printed.
4. The product is delivered.
When you purchase a washing machine, you see the following output:
1. The customer visits a dealer showroom.
2. The customer purchases a washing machine.
3. The bill is printed.
4. The product is delivered.

Notice that step 1, step 3, and step 4 are common in both cases. You do not see any difference in these steps when you purchase different products. Also notice that these steps are fixed. For example, once you purchase a product, then the bill is generated, and the product is delivered. It is unlikely the dealer will generate a bill and deliver the product if you do not visit the showroom or choose the product. (I do not consider online shopping in this case.)

Designing a template method is ideal in this scenario where you do not alter the basic steps of an algorithm, but you allow some minor modifications in some steps. In our example, step 2 changes slightly to show the product you select, but the remaining steps are the same when you buy any product.

When you order a pizza, you notice a similar scenario. For example, you can opt for different toppings such as bacon, onions, extra cheese, or mushrooms for a pizza. How does the chef make the pizza? The chef first prepares the pizza following conventional ways. Just before delivery, the chef can include the toppings to make you happy. You can find similar situations in other domains too.

Now the question is, how can you make such an application that has multiple steps but only a few of them can vary? Obviously, you can use different approaches, but the simple answer is that you can use a template method (that consists of many steps) in a parent class. Then you defer some steps to the subclasses (that represent the particular product) and allow derived classes to override those steps based on their needs.

Note

Using simple polymorphism, you can bring a radical change by overriding all, or most, of the methods of a parent class inside a child class. By contrast, when you use a template method, you do not override all the parent class (or, base class) methods in the child class. Instead, you override only a limited number of methods (or, steps). This is the key distinction between this approach and simple polymorphism.

In the upcoming example, you can see the following class that contains a template method called PurchaseProduct. The overall code is simple; still, I have kept the comments in the code to help you understand it.
public abstract class Device
{
  // The following method(step) will NOT vary
  private void VisitShowroom()
  {
   Console.WriteLine("1. The customer visits a dealer showroom.");
  }
 // The following method(step) will NOT vary
 private void GenerateBill()
 {
  Console.WriteLine("3. The bill is printed.");
 }
 private void DeliverProduct()
 {
  Console.WriteLine("4. The product is delivered. ");
 }
 /*
 The following method will vary. It will be
 overridden by derived classes.
 */
 protected abstract void SelectProduct();
 // The template method
 public void PurchaseProduct()
 {
  // Step-1
  VisitShowroom();
  // Step-2: Specialized action
  SelectProduct();
  // Step-3
  GenerateBill();
  // Step-4
  DeliverProduct();
 }
}
Here are the important points:
  • This class is abstract because it contains the abstract method SelectProduct(). A child class of this class overrides this method to show the product you purchased.

  • Inside the template method you see four methods: VisitShowroom(), SelectProduct(), GenerateBill(), and DeliverProduct(). These four methods represent the four steps of the algorithm.

  • SelectProduct() is a protected method. It allows a derived class to redefine/override the method. But the other methods inside the template method are marked with the private keyword/access modifier. So, a client cannot access them directly.

  • When you call the PurchaseProduct() method, a derived class cannot alter the execution order of these methods. Also inside the client code, you cannot access these methods directly. To complete a purchase, you need to invoke the template method. This is why I made this template method public. Clients do not know how the template method accomplishes its task. Since you do not expose the inner logic to a client, this is a better practice.

Note

The participating classes are small. So, similar to other programs in this book, instead of creating separate files (or, namespaces), I have put them together in a single file.

Class Diagram

Figure 8-1 shows the important parts of the class diagram.
Figure 8-1

PurchaseProduct() is the template method in this example.

Demonstration 1

Here is the complete demonstration:
Console.WriteLine("***A demonstration of a template Method.*** ");
Console.WriteLine("---The customer wants a television.---");
Device device = new Television();
device.PurchaseProduct();
Console.WriteLine("---The customer wants a washing machine.---");
device = new WashingMachine();
device.PurchaseProduct();
/// <summary>
/// Basic skeleton of action steps
/// </summary>
public abstract class Device
{
    // The following method(step) will NOT vary
    private void VisitShowroom()
    {
        Console.WriteLine("1. The customer visits a
          dealer showroom.");
    }
    // The following method(step) will NOT vary
    private void GenerateBill()
    {
        Console.WriteLine("3. The bill is printed.");
    }
    private void DeliverProduct()
    {
        Console.WriteLine("4. The product is delivered. ");
    }
    /*
    The following method will vary. It will be
    overridden by derived classes.
    */
    protected abstract void SelectProduct();
    // The template method
    public void PurchaseProduct()
    {
        // Step-1
        VisitShowroom();
        // Step-2: Specialized action
        SelectProduct();
        // Step-3
        GenerateBill();
        // Step-4
        DeliverProduct();
    }
}
// The concrete derived class:Television
public class Television : Device
{
    protected override void SelectProduct()
    {
        Console.WriteLine("2. The customer purchases a
          television.");
    }
}
// The concrete derived class:WashingMachine
public class WashingMachine : Device
{
    protected override void SelectProduct()
    {
        Console.WriteLine("2. The customer purchases a
          washing machine.");
    }
}

Output

Here is the output:
***A demonstration of a template Method.***
---The customer wants a television.---
1. The customer visits a dealer showroom.
2. The customer purchases a television.
3. The bill is printed.
4. The product is delivered.
---The customer wants a washing machine.---
1. The customer visits a dealer showroom.
2. The customer purchases a washing machine.
3. The bill is printed.
4. The product is delivered.

Analysis

In the future if you need to consider a different product, say a DVD, you can easily enhance the application. In that case, you can create a DVD class that inherits from Device and overrides the SelectProduct() method in the same way. Here is a sample for you:
    // The concrete derived class:DVD
    public class DVD : Device
    {
        protected override void SelectProduct()
        {
            Console.WriteLine("2. The customer
              purchases a DVD player.");
        }
    }

Note This implementation obeys the OCP principle. But does this implementation violate the SRP? The answer seems to be yes to some extent. But think from a seller’s perspective: a potential customer visits the showroom and selects the product. Then the seller generates the bill and delivers the product to the customer. All these activities are linked to “one successful sell” from a seller’s point of view. From this perspective, you spread out these steps to accomplish one single task, which is purchasing a product. This is why a customer in this example can access only the template method, and other methods are private to the customer. In addition, you may recall what I said in the preface: sometimes it is OK to bend a rule based on the complexity or nature of a problem.

Enhanced Requirement

Let’s enhance the application and consider another real-world scenario. The dealer can decide to offer a special gift coupon to a customer who purchases a television from him. This offer does not apply to other products. How can you modify this application to serve this new requirement?

One approach is straightforward. You use a method (it can be public or protected) to reflect this offer and place the method inside the parent class Device. Let’s name this method GenerateGiftCoupon(). It is introduced in the following code segment and shown in bold:
/// <summary>
/// Basic skeleton of action steps
/// </summary>
public abstract class Device
{
     // The following method(step) will NOT vary
     private void VisitShowroom()
     {
     Console.WriteLine("1.The customer visits a dealer showroom.");
     }
     // The following method(step) will NOT vary
     private void GenerateBill()
     {
     Console.WriteLine("3.The bill is printed.");
     }
     private void DeliverProduct()
     {
     Console.WriteLine("4.The product is delivered. ");
     }
     /*
     The following method will vary. It will be
     overridden by derived classes.
     */
     protected abstract void SelectProduct();
     // The template method
     public void PurchaseProduct()
     {
          // Step-1
          VisitShowroom();
          // Step-2: Specialized action
          SelectProduct();
          // Step-2.1: Eligible for a gift?
          GenerateGiftCoupon();
          // Step-3
          GenerateBill();
          // Step-4
          DeliverProduct();
     }
     protected virtual void GenerateGiftCoupon()
     {
      Console.WriteLine(" A gift coupon is generated.");
     }
}
Now any subclass of Device can have the GenerateGiftCoupon() method. Each subclass can redefine it as per their own needs. Per our new requirements, you get a special gift coupon from the dealer if you purchase a television, but not for a washing machine. So, inside the WashingMachine class, you override the method and write it in the following way:
protected override void GenerateGiftCoupon()
{
  throw new NotImplementedException();
}

But throwing an exception inside a method body can be risky in certain scenarios. You learned about this when I discussed the LSP in Chapter 4.

To avoid this problem, you can make this method empty as follows:
protected override void GenerateGiftCoupon()
 {
  // Empty body
 }

Now, is it a good idea to use an empty method? I think we can find a better alternative.

You may want to make GenerateGiftCoupon() abstract and override it in its child classes as per their needs. Yes, this will work. But the problem is that when you use an abstract method in the parent class, the derived classes need to provide a concrete implementation for the method (otherwise, it is again abstract, and you cannot instantiate from it). So, if you have too many specialized classes and most of them don’t make you eligible for gift coupons, you are still forced to override them. (Remember the ISP?)

Is there a better solution? Yes, I think so. You can use a hook method. I am about to show this in demonstration 2. Before you proceed, the question is, what is a hook in programming? In very simple words, a hook helps you execute some code before or after an existing code. It can help you extend the behavior of a program at runtime. Hook methods can provide some default behaviors that a subclass can override if necessary. Often, they do nothing by default.

Let me show you the use of a simple hook in this program. Notice the bold lines in the following code segments:
        // The template method
        public void PurchaseProduct()
        {
            // Step-1
            VisitShowroom();
            //Step-2: Specialized action
            SelectProduct();
            // Step-2.1: Eligible for a gift?
            if(IsEligibleForGiftCoupon())
            {
                GenerateGiftCoupon();
            }
            // Step-3
            GenerateBill();
            // Step-4
            DeliverProduct();
        }
The hook method is defined as follows:
// By default, there is no gift coupon.
protected virtual bool IsEligibleForGiftCoupon()
{
  return false;
}
These two code segments tell us that when you invoke the template method, by default GenerateGiftCoupon() will not be executed. This is because IsEligibleForGiftCoupon() returns false, which in turn makes the if condition inside the template method false. But the Television class overrides this method as follows:
protected override bool IsEligibleForGiftCoupon()
{
  return true;
}

So, when you instantiate a Television object and call the template method, you can see that GenerateGiftCoupon() is called just before step 3.

Demonstration 2

Here is the complete demonstration using a hook method. I kept the comments in the code.
Console.WriteLine("***A demonstration of a template Method.*** ");
Console.WriteLine("---The customer wants a television.---");
Device device = new Television();
device.PurchaseProduct();
Console.WriteLine("---The customer wants a washing machine.---");
device = new WashingMachine();
device.PurchaseProduct();
/// <summary>
/// Basic skeleton of action steps
/// </summary>
public abstract class Device
{
    // The following method(step) will NOT vary
    private void VisitShowroom()
    {
        Console.WriteLine("1. The customer visits a
          dealer showroom.");
    }
    // The following method(step) will NOT vary
    private void GenerateBill()
    {
        Console.WriteLine("3. The bill is printed.");
    }
    private void DeliverProduct()
    {
        Console.WriteLine("4. The product is delivered. ");
    }
    /*
    The following method will vary. It will be
    overridden by derived classes.
    */
    protected abstract void SelectProduct();
    // The template method
    public void PurchaseProduct()
    {
        // Step-1
        VisitShowroom();
        // Step-2: Specialized action
        SelectProduct();
        // Step-2.1: Eligible for gift?
        If (IsEligibleForGiftCoupon())
        {
            GenerateGiftCoupon();
        }
        // Step-3
        GenerateBill();
        // Step-4
        DeliverProduct();
    }
    protected void GenerateGiftCoupon()
    {
        Console.WriteLine(" A gift coupon is generated.");
    }
    // Hook:
    // By default, there is no gift coupon.
    protected virtual bool IsEligibleForGiftCoupon()
    {
        return false;
    }
}
// The concrete derived class:Television
public class Television : Device
{
    // If a customer purchases a television
    // he/she can get a gift.
    protected override bool IsEligibleForGiftCoupon()
    {
        return true;
    }
    protected override void SelectProduct()
    {
        Console.WriteLine("2. The customer purchases a
          television.");
    }
}
// The concrete derived class:WashingMachine
public class WashingMachine : Device
{
    protected override void SelectProduct()
    {
        Console.WriteLine("2. The customer purchases a
          washing machine.");
    }
}

Output

Here is the output. Notice the effect of the hook method in bold:
***A demonstration of a template Method.***
---The customer wants a television.---
1. The customer visits a dealer showroom.
2. The customer purchases a television.
        A gift coupon is generated.
3. The bill is printed.
4. The product is delivered.
---The customer wants a washing machine.---
1. The customer visits a dealer showroom.
2. The customer purchases a washing machine.
3. The bill is printed.
4. The product is delivered.

Summary

This chapter showed you how to use a template method to make an efficient application. Then it shows how to use hooks to adapt to a new requirement without altering the core structure of an algorithm.

Hooks can make your program efficient, and they are common in sophisticated programs. For example, they can be used to process or modify system events. They can be useful to insert, remove, process, or modify keyboard and mouse events too.

Microsoft (see https://docs.microsoft.com/en-us/archive/msdn-magazine/2002/october/cutting-edge-windows-hooks-in-the-net-framework) says the following:
  • In Win32, a hook is a mechanism by which a user-defined function can intercept a handful of system events before they reach an application. Typical events that hooks can intercept include messages, mouse and keyboard actions, and opening and closing of windows. The hook can act on events and modify the actual flow of code.

There is a downside too. If you are not careful, using hooks can impact the overall performance of your application. But in our case, using a hook method was beneficial. It helped us to extend the application to accommodate a new requirement.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.144.8.90