Project – Greeter – Structural Patterns

Context: We’ve programmed a highly sophisticated greeting system that we want to reuse in a new program. However, its interface does not match the new design, and we cannot modify it because other systems use that greeting system.To fix this problem, we decided to apply the Adapter pattern. Here is the code of the external greeter (ExternalGreeter) and the new interface (IGreeter) used in the new system. This code must not directly modify the ExternalGreeter class to prevent any breaking changes from occurring in other systems:

public interface IGreeter
{
    string Greeting();
}
public class ExternalGreeter
{
    public string GreetByName(string name)
    {
        return $”Adaptee says: hi {name}!”;
    }
}

Next is how the external greeter is adapted to meet the latest requirements:

public class ExternalGreeterAdapter : IGreeter
{
    private readonly ExternalGreeter _adaptee;
    public ExternalGreeterAdapter(ExternalGreeter adaptee)
    {
        _adaptee = adaptee ??
throw new ArgumentNullException(nameof(adaptee));
    }
    public string Greeting()
    {
        return _adaptee.GreetByName(“ExternalGreeterAdapter”);
    }
}

In the preceding code, the actors are as follows:

  • The IGreeter interface represents the Target and is the interface that we must use.
  • The ExternalGreeter class represents the Adaptee and is the external component that already contains all the logic that someone programmed and tested. That code could be in an external assembly or installed from a NuGet package.
  • The ExternalGreeterAdapter class represents the Adapter and is where the adapter does its job. In this case, the Greeting method calls the GreetByName method of the ExternalGreeter class, which implements the greeting logic.

Now, we can call the Greeting method and get the result of the GreetByName call. With this in place, we can reuse the existing logic through the ExternalGreeterAdapter class.

We can also test IGreeter consumers by mocking the IGreeter interface without dealing with the ExternalGreeterAdapter class.

In this case, the “complex logic” is pretty simple, but we are here for the Adapter pattern, not for imaginary business logic. Now, let’s take a look at the consumer:

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddSingleton<ExternalGreeter>();
builder.Services.AddSingleton<IGreeter, ExternalGreeterAdapter>();
var app = builder.Build();
app.MapGet(“/”, (IGreeter greeter) => greeter.Greeting());
app.Run();

In the preceding code, we composed our application by registering the ExternalGreeterAdapter class as a singleton bound to the IGreeter interface. We also informed the container to provide a single instance of ExternalGreeter class whenever requested (in this case, we inject it into the ExternalGreeterAdapter class).Then, the consumer (Client in the class diagrams) is the highlighted endpoint where the IGreeter interface is injected as a parameter. Then, the delegate calls the Greeting method on that injected instance and outputs the greeting message to the response.The following diagram represents what’s happening in this system:

 Figure 11.14: Greeter system sequence diagramFigure 11.14: Greeter system sequence diagram 

And voilà! We’ve adapted the ExternalGreeterAdapter class to the IGreeter interface with little effort.

Conclusion

The Adapter pattern is another simple pattern that offers flexibility. With it, we can use older or non-conforming components without rewriting them. Of course, depending on the Target and Adaptee interfaces, you may need to put more or less effort into writing the code of the Adapter class.Now, let’s learn how the Adapter pattern can help us follow the SOLID principles:

  • S: The Adapter pattern has only one responsibility: make an interface work with another interface.
  • O: The Adapter pattern allows us to modify the Adaptee’s interface without the need to modify its code.
  • L: Inheritance is not much of a concern regarding the Adapter pattern, so this principle does not apply once again. If Adapter inherits from Adaptee, the goal is to change its interface, not its behavior, which should conform to the LSP.
  • I: We can view the Adapter class as a facilitator to the ISP, with the Target interface as the ultimate destination. The Adapter pattern relies on the design of the Target interface but doesn’t directly influence it. Per this principle, our primary focus should be to design the Target interface in a manner that abides by the ISP.
  • D: The Adapter pattern introduces only an implementation of the Target interface. Even if the Adapter depends on a concrete class, it breaks the direct dependency on that external component by adapting it to the Target interface.

Next, we explore the last structural pattern of the chapter that teaches foundational concepts.

Leave a Reply

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



          Copyright © 2015-2024 | About | Terms of Service | Privacy Policy