© 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_7

7. Add Features Using Wrappers

Vaskaran Sarcar1  
(1)
Kolkata, West Bengal, India
 

An alternative to inheritance is composition. It is quite common in programming and often gives you a better payoff. This chapter shows you a useful case study on this topic using some wrappers.

The first question that may come into your mind is, what is a wrapper? A wrapper is like a topping that surrounds an object. In programming, you often use a wrapper to add some functionalities dynamically. This is a powerful technique because you can add or discard a wrapper as per your needs, and it does not hamper the functionalities of the original object.

Say you need to work on a piece of code and add some new features. Someone coded it before you, and you cannot change the existing code. This scenario is common in software industries when you need to enhance a feature to attract new customers but you cannot alter the existing workflow of the software to support the existing customers. You understand that in this case, since you were not a part of the team that wrote the first version of the software, you do not have exclusive control of it. Wrappers are useful in similar situations. As mentioned, in this case, you can add new functionality on top of the existing functionality to support new customers. In fact, by using different types of wrappers, you can attract different types of customers too. The upcoming example will make the concept clearer to you.

The Problem Statement

Assume that several people want to purchase a property. The budget and mindset of every individual are different. These people visit a home builder to get a cost estimate. For simplicity, let’s assume that the people have the following options:
  • Either they can build a basic home with minimum facilities or they can build an advanced home with more facilities. For illustration purposes, we’ll refer to these homes as the BasicHome and AdvancedHome, respectively.

  • The home builder gives them options; a customer can opt for a playground, a swimming pool, or both. Let’s call these luxuries. Each of these luxuries has additional costs for a buyer.

Based on the budget restrictions, a customer can opt for various options, and the final price will vary. Most important, a customer who opts for a BasicHome today can upgrade their home tomorrow by adding one playground or a swimming pool (or both). Can you write a program for this scenario?

Initial Program (Using Subclassing)

If you try to provide a solution using inheritance, first try to understand the associated problems. Activities like this will give you a better payoff in the long run. Let us start with the following demonstration.

Demonstration 1

Assume that you have started with the following code:
    class Home
    {
        // Some code
    }
    class Playground : Home
    {
        // Some code
    }
    class SwimmingPool : Playground
    {
        // Some code
    }
This is not a recommended approach, because to get a swimming pool, you first get a playground, which a customer may not want. Because of similar logic, the following structure is not a good choice either:
    class Home
    {
        // Some code
    }
    Class SwimmingPool : Home
    {
        // Some code
    }
    class Playground : SwimmingPool
    {
        // Some code
    }

This is because this time to get a playground you first get a swimming pool, which a customer may not want. So, implementing a multilevel inheritance, in this case, is not a good idea!

Now let’s assume that you start with a hierarchical inheritance where SwimmingPool and Playground both inherit from the Home class, as shown in Figure 7-1.
Figure 7-1

A hierarchical inheritance

Now you need a home with a swimming pool and a playground. So, you end up with the design shown in Figure 7-2.
Figure 7-2

A class needs to inherit from multiple base classes. It causes the diamond problem in C#

But you know that you cannot have multiple base classes in C#. So, any construct like the following will raise a compilation error too:
    class Home: SwimmingPool, Playground // Error
    {
    }

Now you understand that simple subclassing, in this case, is not a good idea. What are the alternatives? Let’s continue investigating.

You may proceed with an interface for the luxury items. For example, you can opt for the following interface:
    interface ILuxury
    {
        void AddPlayground();
        void AddSwimmingPool();
    }
Now you can have a class that can implement this interface. For example, here is a Home class that extends the BasicHome class and implements the ILuxury interface:
   class Home : BasicHome, ILuxury
    {
        public void AddPlayground()
        {
            // Some code
        }
        public void AddSwimmingPool()
        {
            // Some code
        }
    }
But again, a customer may opt for a home with one of these luxuries but not both. In that case, if a method is not needed, you write this:
throw new NotImplementedException();

The problem associated with this is discussed in the context of the LSP in Chapter 4. To avoid this, you may follow the ISP and segregate the ILuxury interface. Yes, this time it can work! Since you saw a similar solution in Chapter 2, I won’t repeat it here.

Now we will look at an alternative approach.

Better Program (Using Object Composition)

Let’s see how a wrapper can help you. Using a wrapper, you surround an object with another object. The enclosing object is often called a decorator, which conforms to the interface of the component that it decorates. It forwards the requests to the original component and can perform additional operations before or after those requests. You can add an unlimited number of responsibilities with this concept. The following figures help you understand this.

Figure 7-3 shows that the home (basic or advanced) is surrounded by a playground.
Figure 7-3

The home is surrounded by a playground

Figure 7-4 shows the home that is surrounded by a swimming pool.
Figure 7-4

The home is surrounded by a swimming pool

Figure 7-5 shows the home that is surrounded by a playground and a swimming pool. Here you first surround the home with a playground, and then you surround the structure with a swimming pool.
Figure 7-5

The home is surrounded by a playground and a swimming pool

Figure 7-6 shows the home that is surrounded by a swimming pool and a playground again. But this time you change the order; you first surround the home with a swimming pool, and then you surround it with a playground.
Figure 7-6

The home is surrounded by a swimming pool. Later you surround the structure with a playground

Note

Following this same technique, you can add two more playgrounds or swimming pools.

Let’s try to implement this concept following the requirements we have.

In the upcoming demonstration, six players are involved. Let’s call them Home, BasicHome, AdvancedHome, Luxury, Playground, and SwimmingPool.

The Home is defined as follows:
abstract class Home
    {
        public double basePrice = 100000;
        public double AdditionalCost { get; set; }
        public abstract void BuildHome();
        public virtual double GetPrice()
        {
            return basePrice + AdditionalCost;
        }
    }
Here are some important points before you read further:
  • You can see that a concrete implementor of Home must implement the BuildHome() method. If needed, it can override the GetPrice() method too. In this example, BasicHome and AdvancedHome inherit from Home.

  • I assume that the base price of a home is $100,000. Using the AdditionalCost property, one can set some extra prices. I use this property to set an additional cost for an advanced home. Currently, for a basic home, this cost is 0, and for an advanced home, this cost is $25000.

  • I assume that once the home is built, there is no need for an immediate modification. One can add the luxuries later.

  • Once a home is built, you can opt for a playground or a swimming pool for an existing home, or you may want both. So, the Playground and SwimmingPool classes appear in this example.

  • Though it was not strictly needed, to share the common code, both the Playground class and the SwimmingPool class inherit from the abstract class Luxury, which has the following structure:

       abstract class Luxury : Home
       {
        protected Home home;
        public double LuxuryCost { get; set; }
         public Luxury(Home home)
         {
            this.home = home;
         }
         public override void BuildHome()
         {
            home.BuildHome();
         }
       }
  • Like the AdditionalCost property, one can set (or update) a luxury cost using the LuxuryCost property.

  • Notice that Luxury holds a reference for Home and the concrete decorators (Playground or SwimmingPool in this example) are decorating an instance of Home.

  • Now let’s look into the structure of a concrete decorator, say, Playground, which is as follows:

class Playground : Luxury
{
    public Playground(Home home) : base(home)
    {
        this.LuxuryCost = 20000;
    }
    public override void BuildHome()
    {
        base.BuildHome();
        AddPlayground();
    }
    private void AddPlayground()
    {
      Console.WriteLine($"""
        For a playground, you pay an extra
         ${this.LuxuryCost}.
        Now the total cost is ${GetPrice()}.
        """);
    }
    public override double GetPrice()
    {
        return home.GetPrice() + LuxuryCost;
    }
}
  • You can see that using the AddPlayground() method, you can add a playground. When you avail of this facility, you have to pay an additional $20,000. I initialize this value inside the constructor. Most important, notice that before adding a playground, it calls BuildHome() from the base class Luxury. This method in turn calls the BuildHome() from a concrete implementation of Home.

  • The SwimmingPool class works similarly, but you have to pay more for this. (Yes, I assume that a swimming pool costs more than a playground in this case.)

Class Diagram

Figure 7-7 shows the most important parts of the class diagram.
Figure 7-7

The class diagram shows the key participants in demonstration 1

Demonstration 2

Here is the complete demonstration for you. In the client code, you can see many different scenarios to show the effectiveness of this application.
Console.WriteLine("***Using wrappers.***");
Console.WriteLine("Scenario-1: A basic home with basic facilities.");
Home home = new BasicHome();
home.BuildHome();
Console.WriteLine(" Scenario-2: A basic home with an
 additional playground.");
Luxury homeWithOnePlayground = new Playground(home);
homeWithOnePlayground.BuildHome();
Console.WriteLine(" Scenario-3: A basic home with two
 additional playgrounds.");
Luxury homeWithDoublePlaygrounds = new Playground(homeWithOnePlayground);
homeWithDoublePlaygrounds.BuildHome();
Console.WriteLine(" Scenario-4: A basic home with one
 additional playground and swimming pool.");
Luxury homeWithOnePlaygroundAndOneSwimmingPool = new SwimmingPool(homeWith OnePlayground);
homeWithOnePlaygroundAndOneSwimmingPool.BuildHome();
Console.WriteLine(" Scenario-5: Adding a swimming
 pool and then a playground to a basic home.");
Luxury homeWithOneSwimmingPool = new SwimmingPool(home);
Luxury homeWithSwimmingPoolAndPlayground = new Playground(homeWithOne SwimmingPool);
homeWithSwimmingPoolAndPlayground.BuildHome();
Console.WriteLine(" Scenario-6: An advanced home with
 some more facilities.");
home = new AdvancedHome();
home.BuildHome();
Console.WriteLine(" Scenario-7: An advanced home with
 an additional playground.");
homeWithOnePlayground = new Playground(home);
homeWithOnePlayground.BuildHome();
abstract class Home
{
    public double basePrice = 100000;
    public double AdditionalCost { get; set; }
    public abstract void BuildHome();
    public virtual double GetPrice()
    {
        return basePrice + AdditionalCost;
    }
}
class BasicHome : Home
{
    public BasicHome()
    {
        AdditionalCost = 0;
    }
    public override void BuildHome()
    {
        Console.WriteLine($"""
          A home with basic facilities is made.
          It costs ${GetPrice()}.
          """);
    }
}
class AdvancedHome : Home
{
    public AdvancedHome()
    {
        AdditionalCost = 25000;
    }
    public override void BuildHome()
    {
        Console.WriteLine($"""
          A home with advanced facilities is made.
          It costs ${GetPrice()}.
          """);
    }
}
abstract class Luxury : Home
{
    protected Home home;
    public double LuxuryCost { get; set; }
    public Luxury(Home home)
    {
        this.home = home;
    }
    public override void BuildHome()
    {
        home.BuildHome();
    }
}
class Playground : Luxury
{
    public Playground(Home home) : base(home)
    {
        this.LuxuryCost = 20000;
    }
    public override void BuildHome()
    {
        base.BuildHome();
        AddPlayground();
    }
    private void AddPlayground()
    {
        Console.WriteLine($"""
          For a playground, you pay an extra
           ${this.LuxuryCost}.
          Now the total cost is ${GetPrice()}.
          """);
    }
    public override double GetPrice()
    {
        return home.GetPrice() + LuxuryCost;
    }
}
class SwimmingPool : Luxury
{
    public SwimmingPool(Home home) : base(home)
    {
        this.LuxuryCost = 55000;
    }
    public override void BuildHome()
    {
        base.BuildHome();
        AddSwimmingPool();
    }
    private void AddSwimmingPool()
    {
        Console.WriteLine($"""
          For a swimming pool, you pay an extra
           ${this.LuxuryCost}.
          Now the total cost is ${GetPrice()}.
          """);
    }
    public override double GetPrice()
    {
        return home.GetPrice() + LuxuryCost;
    }
}

Output

Here is the output. I have highlighted different scenarios in bold font for your reference.
***Using wrappers.***
Scenario-1: A basic home with basic facilities.
A home with basic facilities is made.
It costs $100000.
Scenario-2: A basic home with an additional playground.
A home with basic facilities is made.
It costs $100000.
For a playground, you pay an extra $20000.
Now the total cost is $120000.
Scenario-3: A basic home with two additional playgrounds.
A home with basic facilities is made.
It costs $100000.
For a playground, you pay an extra $20000.
Now the total cost is $120000.
For a playground, you pay an extra $20000.
Now the total cost is $140000.
Scenario-4: A basic home with one additional playground and swimming pool.
A home with basic facilities is made.
It costs $100000.
For a playground, you pay an extra $20000.
Now the total cost is $120000.
For a swimming pool, you pay an extra $55000.
Now the total cost is $175000.
Scenario-5: Adding a swimming pool and then a playground to the basic home.
A home with basic facilities is made.
It costs $100000.
For a swimming pool, you pay an extra $55000.
Now the total cost is $155000.
For a playground, you pay an extra $20000.
Now the total cost is $175000.
Scenario-6: An advanced home with some more facilities.
A home with advanced facilities is made.
It costs $125000.
Scenario-7: An advanced home with an additional playground.
A home with advanced facilities is made.
It costs $125000.
For a playground, you pay an extra $20000.
Now the total cost is $145000.

Analysis

Notice that this implementation follows the OCP principle. So, when you make a different type of home, you do not need to open the existing code; instead, you can make a new class that inherits from the abstract class Home.

I want you to note that I slightly violated the SRP in this example. This is because I wanted to show you the final price after uniformly adding a luxury. In actuality, when I add a wrapper, I do not need to calculate the total cost; instead, it is sufficient to show the increased price for this addition. But I assume that a customer would like to see the total estimated price. This is the reason, after each addition of a luxury item, I showed the total cost. In addition, the price depends on the type of home you choose. So, it makes sense to put the GetPrice() method and the BuildHome() method in the Home class.

This example pattern is more effective when you use a wrapper that does a single job. So, when you follow the SRP, you can add or remove a behavior easily using this kind of wrapper.

I am about to finish this chapter. Before that, I want to inform you that when you analyze I/O stream implementations in the .NET Framework and Java, you will find many similar implementations. For example, the BufferedStream class inherits from the abstract base class Stream. I am taking a snapshot from the Visual Studio IDE to show you the constructors of this class (see Figure 7-8).
Figure 7-8

Partial snapshot of BufferedStream class from the Visual Studio IDE

You can see that the BufferedStream class constructors accept a Steam class object as a parameter. Notice that the Luxury constructor also accepts its base class object (Home) as a parameter. So, you can say that the Luxury and BufferedStream classes follow the wrapper/decorator pattern.

But now notice the partial snapshot of the FileStream class from the Visual Studio IDE (see Figure 7-9).
Figure 7-9

Partial snapshot of the FileStream class from the Visual Studio 2022 IDE

You can see that there is no constructor of the FileStream class that can accept a Steam class object as a parameter. So, this class does not follow the wrapper/decorator pattern.

Summary

The pattern that is shown in this chapter is referred to as a wrapper or a decorator pattern. This chapter shows you an alternative way to implement the subclassing technique. You have seen the reason why neither the multilevel inheritance nor the multiple inheritance can solve the problem that we mentioned at the beginning of the chapter. Later you saw an implementation using object composition. You used different types of wrappers to add a behavior dynamically in this application. In this context, you can remember that a simple inheritance promotes only a compile-time binding, not a dynamic binding.

In short, here are the key points that you learned in this chapter:
  • You can add a state and/or behavior without modifying an existing inheritance hierarchy.

  • In demonstration 2, you defined a new hierarchy that itself extends the root of the original/existing hierarchy.

  • To use the decorators, you first instantiate a home and then wrap it inside a decorator.

  • This example pattern has a name. We call it the decorator pattern. It shows an example of when object composition can perform better than plain inheritance.

  • You also saw some built-in examples in .NET. There you learned that the BufferedStream class follows a similar pattern, but the FileStream class does not follow this pattern.

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

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