DEV Community

Cover image for Facade Pattern in C#
Kostas Kalafatis
Kostas Kalafatis

Posted on • Originally published at dfordebugging.wordpress.com

Facade Pattern in C#

The Facade is a structural design pattern that provides a simplified interface to a library, framework, or any other complex set of classes.

The main idea behind the facade pattern is to make a set of interfaces for a complex system that is easier to use. A facade makes it easier to use a subsystem by giving it a higher-level interface. The high-level interface isn't meant to support all of the complicated system's use cases, just the most important ones. Even though the facade pattern makes the interface easier to use, the complex system can often still be used directly.

You can find the example code of this post, on GitHub

Conceptualizing the Problem

It's common for applications to use multiple third-party libraries or application programming interfaces (APIs) to do certain things. These libraries and APIs may have been made by different groups or teams, and they may have different interfaces, data structures, and ways of working.

When an app needs to use several libraries or APIs at the same time, it can get very complicated and hard to manage. Each library may have its own set of classes and methods that need to be used in a certain way, and there may be complex interactions between the libraries that need to be managed.

As the subsystem gets more complicated, it gets harder to change and keep up with the code. When making changes, bugs and errors can be introduced, and it can be hard to test and fix the code well. Also, the performance of the code can be hurt by the time it takes to manage how the subsystem parts interact with each other.

Ordinarily, we'd need to initialize all of these objects, keep track of all the dependencies, execute methods in the right order and so on. This will cause our code to be tightly coupled with the library code. Tight coupling happens when two or more parts depend on each other very much. This makes it hard to change or replace one part without changing the others.

By making a facade, we can give the user a simpler way to interact with the subsystem while hiding its complexity. This makes it easier for users to use the system and for maintenance and updates to be made. Also, hiding the subsystem behind a facade makes it easier to control and manage access to its functions, making sure they are used correctly and safely.

Structuring the Facade Pattern

In the basic implementation, the Facade pattern has 4 main participants:

  • Facade: The Facade provides quick access to a specific aspect of the subsystem's functionality. It understands how to route the client's request and how to operate all of the moving parts.
  • Additional Facade: To avoid polluting a single facade with unrelated features that may result in yet another complex structure, an Additional Facade class can be created. Clients and other facades can both use additional facades.
  • Complex Subsystem: The Complex Subsystem is made up of dozens of different objects. To make them all do something useful, you must delve deeply into the implementation details of the subsystem, such as initializing objects in the correct order and supplying them with data in the correct format. The facade's existence is unknown to subsystem classes. They work within the system and directly with one another.
  • Client: Instead of directly calling subsystem objects, the Client employs the facade.

Image description

To demonstrate how the facade pattern works, we are going to create a somewhat complex subsystem. Suppose we have an application with a set of interfaces for accessing a MySql/MS/Mongo database and generating various types of reports, such as HTML reports, PDF reports, and so on. As a result, we will have different interfaces to work with different types of databases. These interfaces can now be used by a client application to obtain the necessary database connection and generate reports. However, as the complexity grows or the names of the interface behaviours become confusing, client applications will struggle to manage it. So, to assist the client application, we can use the Facade design pattern and provide a wrapper interface on top of the existing interface.

First, we are going to create our Complex Subsystem participant. Our subsystem consists of 3 helper methods, each corresponding to a different database provider. A helper function will contain the functionality for connecting to the database and generating reports in PDF and HTML format.

Considering the labyrinthine and prodigious nature of the requisite operations, a full-fledged execution of the same for the purpose of demonstration would amount to a cumbersome and infeasible endeavour. In lieu thereof, I shall resort to employing the most refined and sophisticated of mockeries, thereby circumventing the exigencies of installing an unwieldy array of NuGet packages, establishing and configuring multiple databases, conjuring up plausible data, and engineering a robust export mechanism. To embark on such an undertaking would, in essence, constitute an autonomous and comprehensive project unto itself.

In other words, these operations are a bit beyond the scope of what we're trying to accomplish here. So, instead of going down that rabbit hole, we're going to keep things simple and focus on the core functionality by utilizing some nifty mock-ups.

Let's first create our MySQLHelper class:

public class MySqlHelper
{
    public static SqlConnection GetMySqlDBConnection(string connectionString)
    {
        // Generate a mysql db connection using connection parameters
        Console.WriteLine("Successfully created a MySQL database connection.");
        return new SqlConnection();
    }

    public static void GenerateMySqlPDFReport(SqlConnection connection, string tableName)
    {
        // Get data from the table and generate a pdf report
        Console.WriteLine($"Successfully generated a pdf report with the contents of {tableName}");
    }

    public static void GenerateMySqlHTMLReport(SqlConnection connection, string tableName)
    {
        // Get data from the table and generate an html report
        Console.WriteLine($"Successfully generated a report with the contents of {tableName} and exported them to HTML");
    }
}
Enter fullscreen mode Exit fullscreen mode

As you can see, our helper has three mock methods. The GetMySqlDBConnection creates a connection. Normally this would require the MySql.Data.MySqlClient and use the MySqlConnection class but for the sake of simplicity, we use a console write. The GenerateMySqlPDFReport would create a PDF containing the data from a specified table. Again this would require a NuGet package like iTextSharp, but again for sake of simplicity, we are just logging a message. Finally, the GenerateMySqlHTMLReport method would create an HTML report containing the data from the specified table. Once again this would require something like DataTables, but we are not going to do it.

We also have similar classes for MSSQL and MongoDB connections. As above, all the methods are mocked.

public class MSSqlHelper
{
    public static SqlConnection GetDBConnection(string connectionString)
    {
        // Generate a MSSql db connection using connection parameters
        Console.WriteLine("Successfully created a MSSql database connection.");
        return new SqlConnection();
    }

    public static void GenerateMSSqlPDFReport(SqlConnection connection, string tableName)
    {
        // Get data from the table and generate a pdf report
        Console.WriteLine($"Successfully generated a pdf report with the contents of {tableName}");
    }

    public static void GenerateMSSqlHTMLReport(SqlConnection connection, string tableName)
    {
        // Get data from the table and generate an html report
        Console.WriteLine($"Successfully generated a report with the contents of {tableName} and exported them to HTML");
    }
}
Enter fullscreen mode Exit fullscreen mode

And the Mongo helper:

public class MongoDBHelper
{
    public static SqlConnection GetDBConnection(string connectionString)
    {
        // Generate a mongodb connection using connection parameters
        Console.WriteLine("Successfully created a MySQL database connection.");
        return new SqlConnection();
    }

    public static void GenerateMongoPDFReport(SqlConnection connection, string tableName)
    {
        // Get data from the table and generate a pdf report
        Console.WriteLine($"Successfully generated a pdf report with the contents of {tableName}");
    }

    public static void GenerateMongoHTMLReport(SqlConnection connection, string tableName)
    {
        // Get data from the table and generate an html report
        Console.WriteLine($"Successfully generated a report with the contents of {tableName} and exported them to HTML");
    }
}
Enter fullscreen mode Exit fullscreen mode

Now without our facade, to generate a report, we would need to first obtain a connection, then use the connection as an argument to the method along with the table we want the report of and so on. We might also need to check for file system access and create the file and possibly the directory. The generation of the report requires careful orchestration of many moving parts for everything to work as expected, and worst of all this choreography needs to be performed every time we need a report from the database. And keep in mind that this is the workflow for a single DB provider.

Below is an example of how the report generation looks without the facade:

SqlConnection connection;  

// Generate reports without facade  
connection = MySqlHelper.GetMySqlDBConnection("connectionString");  
MySqlHelper.GenerateMySqlHTMLReport(connection, "sample-MySQL");  
MySqlHelper.GenerateMySqlPDFReport(connection, "sample-MySQL");  

connection = MSSqlHelper.GetDBConnection("connectionString");  
MSSqlHelper.GenerateMSSqlHTMLReport(connection, "sample-MSSql");  
MSSqlHelper.GenerateMSSqlPDFReport(connection, "sample-MSSql");  

connection = MongoDBHelper.GetDBConnection("connectionString");  
MongoDBHelper.GenerateMongoHTMLReport(connection, "sample-Mongo");  
MongoDBHelper.GenerateMongoPDFReport(connection, "sample-Mongo");
Enter fullscreen mode Exit fullscreen mode

Now we are going to implement our Facade participant:

public class HelperFacade
{
    public static void GenerateReport(DBConnector connector, ReportType reportType, string tableName)
    {
        var connectorRegistry = new Dictionary<DBConnector, Func<string, IDbConnection>>()
        {
            { DBConnector.MYSQL, MySqlHelper.GetDBConnection },
            { DBConnector.MSSQL, MSSqlHelper.GetDBConnection },
            { DBConnector.MONGO, MongoDBHelper.GetDBConnection }
        };

        var generatorRegistry = new Dictionary<ReportType, Action<IDbConnection, DBConnector, string>>()
        {
            { ReportType.HTML, GenerateHTMLReport },
            { ReportType.PDF, GeneratePDFReport }
        };

        if (!connectorRegistry.ContainsKey(connector) || !generatorRegistry.ContainsKey(reportType))
        {
            throw new ArgumentException("Invalid connector or report type.");
        }

        var getConnectionMethod = connectorRegistry[connector];
        var generateReportMethod = generatorRegistry[reportType];

        using (var connection = getConnectionMethod("connectionString"))
        {
            generateReportMethod(connection, connector, tableName);
        }
    }

    private static void GenerateHTMLReport(IDbConnection connection, DBConnector connector, string tableName)
    {
        switch (connector)
        {
            case DBConnector.MYSQL:
                MySqlHelper.GenerateMySqlHTMLReport(new SqlConnection(), tableName);
                break;
            case DBConnector.MSSQL:
                MSSqlHelper.GenerateMSSqlHTMLReport(new SqlConnection(), tableName);
                break;
            case DBConnector.MONGO:
                MongoDBHelper.GenerateMongoHTMLReport(new SqlConnection(), tableName);
                break;
            default:
                throw new ArgumentException("Invalid database connection.");
        }
    }

    private static void GeneratePDFReport(IDbConnection connection, DBConnector connector, string tableName)
    {
        switch (connector)
        {
            case DBConnector.MYSQL:
                MySqlHelper.GenerateMySqlPDFReport(new SqlConnection(), tableName);
                break;
            case DBConnector.MSSQL:
                MSSqlHelper.GenerateMSSqlPDFReport(new SqlConnection(), tableName);
                break;
            case DBConnector.MONGO:
                MongoDBHelper.GenerateMongoPDFReport(new SqlConnection(), tableName);
                break;
            default:
                throw new ArgumentException("Invalid database connection.");
        }
    }

    public enum DBConnector
    {
        MYSQL, MSSQL, MONGO
    }

    public enum ReportType
    {
        HTML, PDF
    }
}   
Enter fullscreen mode Exit fullscreen mode

Our HelperFacade class defines a single method called GenerateReport. This method takes three arguments: a DBConnector enum value that represents the type of database connector to be used to create the report, a ReportType enum value that represents the type of report to be made, and a string value that represents the name of the table from which the report will be created.

The GenerateReport contains two dictionaries. The first dictionary, called connectorRegistry, maps each DBConnector enum value to a method that returns a database connection object for that type of connector. The second dictionary, called generatorRegistry, maps each ReportType enum value to a method that makes a report of that type.

If both the connector and report type values are valid, the method gets the matching database connection method from the connectorRegistry dictionary and the matching report generation method from the generatorRegistry dictionary. The method then uses the retrieved connection method to connect to the database. It then passes the connection object, the connector, and the table name to the retrieved report generation method to make the requested report.

There are also two private static methods called GenerateHTMLReport and GeneratePDFReport that are part of the HelperFacade class. These methods need an IDbConnection object, a value from the DBConnector enum, and the name of the table from which the report will be made as a string. The GenerateReport method calls these methods to make the report that was asked for based on the ReportType value.

Lastly, the HelperFacade class has two enum types called DBConnector and ReportType. These are used to specify the type of database connector and the type of report to be made, respectively.

Now let's use our HelperFacade method to generate some reports,

HelperFacade.GenerateReport(HelperFacade.DBConnector.MYSQL, HelperFacade.ReportType.HTML, "sample-MySQL");  
HelperFacade.GenerateReport(HelperFacade.DBConnector.MONGO, HelperFacade.ReportType.PDF, "sample-Mongo");
Enter fullscreen mode Exit fullscreen mode

As you can see using the Facade pattern interface is a lot easier and cleaner way to avoid having a lot of logic on the client side.

Is Facade an anti-pattern?

Some developers think of the facade pattern as an antipattern because it can lead to a codebase that is too complicated and tightly linked. The pattern makes it possible to access a complex subsystem through a single interface. This can lead to the facade taking on too many responsibilities. This can make it harder to maintain and change the codebase, since changes to the subsystem may need changes to the facade interface. Also, the facade pattern can hide important parts of the subsystem, making it hard for developers to figure out how it works. This can lead to bad design decisions and wasted time.

On the other hand, the facade pattern is a useful design pattern that makes it easier to use and understand a complex subsystem by simplifying its interface. It can hide changes to the subsystem from the rest of the application and provide a simple, unified interface that hides the complexity of the subsystem. Also, the facade pattern can help make the client code and the subsystem less dependent on each other, making it easier to test and keep up. When used correctly, the facade pattern can help improve an application's overall design and make it easier to maintain. This makes it a good design pattern to think about.

Pros and Cons of the Facade Pattern

✔ Reduces the complexity of a complex subsystem's interface, making it easier to use and understand. ❌Improves overall design and maintainability of an application.
✔Provides a simple, unified interface that encapsulates the subsystem's complexity. ❌May result in poor design decisions and wasted effort if important details of the subsystem are hidden.
✔Offers a straightforward and unified interface that conceals the complexity of the underlying subsystem. ❌ Can easily become a god object.
✔Improves overall design and maintainability of an application.

Relations with Other Patterns

  • Facade makes a new interface for an object that already exists, while Adapter tries to make the interface that already exists usable. Most of the time, Adapter only wraps one object, while Facade works with a whole group of objects in a subsystem.
  • You can use Abstract Factory instead of Facade when all you want to do is hide from the client code how the subsystem objects are made.
  • Flyweight shows how to make a lot of small objects, while Facade shows how to make a single object that represents an entire subsystem.
  • Facade and Mediator both try to set up ways for a lot of closely related classes to work together.
    • Facade makes the interface to a group of objects in a subsystem simpler, but it doesn't add any new functionality. The subsystem doesn't know that the facade is there. Objects that are part of the subsystem can talk to each other directly.
    • The mediator makes sure that all the parts of the system can talk to each other. The components don't talk to each other directly; they only know about the mediator object.
  • Most of the time, a single facade object is enough for a Facade class to be turned into a Singleton.
  • Both Facade and Proxy buffer a complex entity and start it up on their own. Unlike Facade, Proxy and its service object have the same interface, which means they can be swapped out.

Final Thoughts

In this article, we have discussed what is the Facade pattern, when to use it and what are the pros and cons of using this design pattern. We then examined some use cases for this pattern and how the Facade relates to other classic design patterns.

It's worth noting that the Facade pattern, along with the rest of the design patterns presented by the Gang of Four, is not a panacea or a be-all-end-all solution when designing an application. Once again it's up to the engineers to consider when to use a specific pattern. After all these patterns are useful when used as a precision tool, not a sledgehammer.

Top comments (0)