Pattern decorator on the example of .NET

Start

In the beginning, let’s make such a digression, I want to start a series of articles about design patterns. I think everyone wants their code to be highly scalable, isn’t it. All patterns are divided into categories, and before considering them, it is better to start studying with the patterns themselves, otherwise you will not understand why the Decorator is a composite pattern, and the Factory is a generative one. After studying, it will already be intuitively clear why a pattern belongs to one category or another.

For the study of patterns itself, I will try not to dive into definitions, but to show examples, ask a problem and solve a problem using a pattern. We will take the company “Sesla Motors” and we will solve the tasks set by it. Don’t let the name make you feel like you’ve heard about it somewhere. This is wrong. After all, the company is engaged only in the production of electric cars. And this is rarely seen in the real world. Should be interesting. Let’s go!.

Task

Our company has a variety of car models. The main ones are “Model A” and “Model B”. The cars themselves are in different price segments. But there are options that can be added to the configuration of both machines. And the company “Cesla Motors” wants to conveniently receive the final price and information from the assembled configuration of the electric car. How can they do it?

Let’s try to solve

Let’s start with the fact that the decorator pattern is actually a convenient “wrapper” for the main class, each wrapper gives the main class new properties. To be able to “wrap” the main class, you need to have a base class for both the wrapper and the main class. Let it be an abstract class car.

public abstract class Car
{
    private string Description;

    public Car()
    {
        Description = "Unknown car";
    }

    public Car(string description)
    {
        Description = description;
    }

    public virtual string GetDescription()
    {
        return Description;
    } 

    public abstract decimal Price();
}

Please note that the properties price And Description have different access IDs. By this I wanted to show the flexibility of this pattern, so it doesn’t matter if we use abstract or virtual. The main thing is to be able to override the property.

From car classes will be inherited Model A And ModelB. Let’s write their implementation. While everything is fine and trivial

public class ModelA : Car
{
    public ModelA() : base("Default ModelA") { }

    public ModelA(string description) : base(description) { }

    public override decimal Price()
    {
        return 40_000.034m;
    }
}

You can write the implementation of ModelB yourself.

In my opinion, I said something else about the wrapper. Hmm… Here it seems to me that the main logic of the decorator will begin. Let’s think about how to wrap class instances Model A or ModelB new option. I would write an additional abstract class that will contain the wrapper logic

public abstract class CarPartsDecorator : Car
{
    public Car _car;

    public CarPartsDecorator(Car car)
    {
        _car = car;
    }
}

In the future, it will be possible to write an abstract method in this class for implementation in subclasses. That is, add additional properties for car options.

Now let’s implement one of the options AutomaticParkingSystem (child class CarPartsDecorator)

public class AutomaticParkingSystem : CarPartsDecorator
{
    private readonly decimal _ownPrice;
    private readonly string _description;

    public AutomaticParkingSystem(Car car,
        string description, decimal ownPrice = 1_500m) : base(car)
    {
        _ownPrice = ownPrice;
        _description = description;
    }

    public override decimal Price()
    {
        return _car.Price() + _ownPrice;
    }

    public override string GetDescription()
    {
        return _car.GetDescription() + _description;
    }
}

You can also write other option classes yourself.

All class! Now let’s see how our service looks like in the end

Car car = new ModelA();

car = new WheelDisk(car, ownPrice: 2_300.4m);
car = new AutomaticParkingSystem(car, description: ", new automatic parking system");

Console.WriteLine($"ModelA price: {car.Price()}");
Console.WriteLine($"ModelB price: {car.GetDescription()}");

The output turned out like this:

ModelA price: 43800,434
ModelB price: Default ModelA, simple wheel disk, new automatic parking system

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *