What is the Servant Design Pattern?

Göksu Deniz
9 min readDec 25, 2022

--

Image is created by DALL-E 2 AI

The servant design pattern is a design pattern in which a class (the servant) performs various services for one or more other classes (the clients). The servant class provides a common interface for the clients to access the services, which can include low-level tasks such as input/output operations or higher-level tasks such as business logic. The main advantage of the servant pattern is that it allows clients to access the services they need without having to know the details of how those services are implemented. This promotes a separation of concerns between the clients and the servant, making the system easier to maintain and extend.

Imagine that you have a robot named Robo that can do all sorts of different tasks for you. Robo is really good at many different things, and you can ask him to do anything you need. You might ask him to clean your room, or make you a sandwich, or even help you with your homework.

Now, suppose that you have a lot of friends who also want to use Robo’s help. You don’t want to have to tell each of your friends how to ask Robo to do all of these different tasks. It would be much easier if you had a way to just tell your friends what they want Robo to do, and then Robo takes care of the rest.

That’s where the servant design pattern comes in. The servant design pattern is a way to make it easy for people (like your friends) to ask Robo (the servant) to do things without having to know all the details of how Robo does those things. Instead, your friends can just tell Robo what they want him to do, and Robo will take care of the rest. This way, your friends don’t have to worry about how Robo does the tasks, they can just focus on what they want him to do.

Here is an example of how the servant design pattern might be implemented in C#:

public interface IServant
{
void DoTask1();
void DoTask2();
}

public class Servant : IServant
{
public void DoTask1()
{
// Implementation of task 1
}

public void DoTask2()
{
// Implementation of task 2
}
}

public class Client
{
private readonly IServant _servant;

public Client(IServant servant)
{
_servant = servant;
}

public void UseService1()
{
_servant.DoTask1();
}

public void UseService2()
{
_servant.DoTask2();
}
}

In this example, the Servant class implements the IServant interface, which defines the methods for the tasks that the servant can perform. The Client class depends on the IServant interface and can use it to access the tasks provided by the Servant without knowing the details of how those tasks are implemented. This allows the Client to focus on its own responsibilities, while the Servant handles the details of providing the tasks.

To use the servant pattern in this example, you would create an instance of the Servant class and pass it to the Client constructor when creating a new Client object:

IServant servant = new Servant();
Client client = new Client(servant);

You can then use the UseService1 and UseService2 methods of the Client object to access the tasks provided by the Servant:

client.UseService1();
client.UseService2();

Think about that you are creating an SDK for a cloud storage service. The SDK provides various methods for interacting with the cloud storage service, such as uploading and downloading files, creating and deleting folders, and so on.

To implement the servant design pattern in this scenario, you might create an interface for the servant that defines the methods for the various tasks that the SDK can perform:

public interface ICloudStorageServant
{
void UploadFile(string filePath, string folderPath);
void DownloadFile(string filePath, string folderPath);
void CreateFolder(string folderPath);
void DeleteFolder(string folderPath);
// Other methods for interacting with the cloud storage service
}

You can then create a concrete implementation of the servant that actually performs these tasks by making API calls to the cloud storage service:

public class CloudStorageServant : ICloudStorageServant
{
private readonly string _apiKey;
private readonly string _apiSecret;

public CloudStorageServant(string apiKey, string apiSecret)
{
_apiKey = apiKey;
_apiSecret = apiSecret;
}

public void UploadFile(string filePath, string folderPath)
{
// Make API call to upload file to the cloud storage service
}

public void DownloadFile(string filePath, string folderPath)
{
// Make API call to download file from the cloud storage service
}

public void CreateFolder(string folderPath)
{
// Make API call to create folder in the cloud storage service
}

public void DeleteFolder(string folderPath)
{
// Make API call to delete folder from the cloud storage service
}

// Other methods for interacting with the cloud storage service
}

To use the SDK, developers can depend on the ICloudStorageServant interface and use it to access the various tasks provided by the CloudStorageServant without knowing the details of how those tasks are implemented. This allows the developers to focus on their own responsibilities, while the CloudStorageServant handles the details of interacting with the cloud storage service.

To create an instance of the CloudStorageServant, developers would pass their API key and API secret to the constructor:

ICloudStorageServant servant = new CloudStorageServant("API_KEY", "API_SECRET");

They can then use the methods of the ICloudStorageServant interface to perform tasks with the cloud storage service:

servant.UploadFile("/local/path/to/file.txt", "/cloud/folder/path");
servant.DownloadFile("/cloud/path/to/file.txt", "/local/folder/path");
servant.CreateFolder("/cloud/path/to/new/folder");
servant.DeleteFolder("/cloud/path/to/folder");

Imagine that you are creating a system for managing customer orders in a retail store. The system needs to provide various services for interacting with the orders, such as creating new orders, updating existing orders, and cancelling orders.

To implement the servant design pattern in this scenario, you might create an interface for the servant that defines the methods for the various tasks that the system can perform:

public interface IOrderServant
{
void CreateOrder(Order order);
void UpdateOrder(Order order);
void CancelOrder(int orderId);
}

You can then create a concrete implementation of the servant that actually performs these tasks by interacting with a database or other storage system:

public class OrderServant : IOrderServant
{
private readonly IDbConnection _dbConnection;

public OrderServant(IDbConnection dbConnection)
{
_dbConnection = dbConnection;
}

public void CreateOrder(Order order)
{
// Insert order into database
}

public void UpdateOrder(Order order)
{
// Update order in database
}

public void CancelOrder(int orderId)
{
// Update order status in database to "cancelled"
}
}

To use the system, developers can depend on the IOrderServant interface and use it to access the various tasks provided by the OrderServant without knowing the details of how those tasks are implemented. This allows the developers to focus on their own responsibilities, such as creating user interfaces for interacting with the orders, while the OrderServant handles the details of interacting with the database or other storage system.

To create an instance of the OrderServant, developers would pass a database connection object to the constructor:

IOrderServant servant = new OrderServant(dbConnection);

They can then use the methods of the IOrderServant interface to perform tasks with the orders:

Order newOrder = new Order();
// Set properties of the new order
servant.CreateOrder(newOrder);

Order existingOrder = servant.GetOrderById(123);
// Update properties of the existing order
servant.UpdateOrder(existingOrder);

servant.CancelOrder(456);

This example demonstrates how the servant design pattern can be used to create a system for managing orders that is modular and flexible, allowing the clients and the servant to be developed and maintained independently of each other.

Nuget packages can be considered examples of the servant design pattern. Nuget packages are libraries that provide various tools or services that can be used by other applications. The tools or services provided by a Nuget package are usually accessed through a common interface, such as an API (Application Programming Interface).

The servant design pattern is often used to implement Nuget packages, as it allows the package to provide a common interface for accessing the tools or services it provides, without requiring the users of the package to know the details of how those tools or services are implemented. This makes it easier for developers to use the package in their own applications, as they can focus on their own responsibilities and utilize the tools or services provided by the package without having to worry about how they are implemented.

For example, the Entity Framework Nuget package provides a common interface for interacting with a database, such as reading and writing data, without requiring the users of the package to know the details of how the database is implemented. The Entity Framework uses the servant design pattern to provide this common interface, allowing developers to focus on their own applications and utilize the database functionality provided by the Entity Framework without having to worry about the details of how the database is implemented.

I think you got the main point.

Conclusion

The main purpose of using the servant design pattern is to promote a separation of concerns between the clients and the servant. The servant provides a common interface for the clients to access the services it provides, which can include low-level tasks such as input/output operations or higher-level tasks such as business logic. The clients can access these services without needing to know the details of how they are implemented, which makes it easier for the clients to focus on their own responsibilities.

The servant design pattern can be used to improve the maintainability and extensibility of a system by allowing the clients and the servant to evolve independently of each other. For example, if the implementation of a service provided by the servant needs to be changed, the clients can continue to use the service without being affected, as long as the interface for the service remains the same. This can make it easier to make changes to the system without breaking existing functionality.

In addition, the servant design pattern can be used to improve the testability of a system by allowing the services provided by the servant to be tested independently of the clients. This can make it easier to ensure that the services are working correctly before integrating them with the clients.

Overall, the servant design pattern can help to create a more modular and flexible system by allowing the clients and the servant to be developed and maintained independently of each other.

Here are a few real-world scenarios where the servant design pattern might be useful:

  1. Building an SDK (Software Development Kit) for interacting with a web service: An SDK might use the servant design pattern to provide a common interface for developers to access the various services provided by the web service, such as retrieving data or performing actions, without having to know the details of how those services are implemented. This can make it easier for developers to integrate the web service into their applications.
  2. Implementing a data access layer for a database-backed application: A data access layer might use the servant design pattern to provide a common interface for accessing data stored in a database, such as reading and writing records, without requiring the rest of the application to know the details of how the data is stored and retrieved. This can make it easier to change the database implementation without affecting the rest of the application.
  3. Creating a system for managing customer orders in a retail store: A system for managing orders might use the servant design pattern to provide a common interface for interacting with orders, such as creating, updating, and cancelling orders, without requiring the rest of the application to know the details of how the orders are stored and retrieved. This can make it easier to change the order management implementation without affecting the rest of the application.
  4. Building a library for performing complex mathematical calculations: A library for performing mathematical calculations might use the servant design pattern to provide a common interface for accessing the various calculations it can perform, without requiring the users of the library to know the details of how the calculations are implemented. This can make it easier for users to utilize the library in their own applications.
  5. Creating a tool kit that serves other classes: A library or a class for performing tasks that serves other classes to use it.

These are just a few examples of scenarios where the servant design pattern might be useful. The servant design pattern can be applied in many other contexts where it is desirable to provide a common interface for accessing services without requiring the clients to know the details of how those services are implemented.

Thanks for reading! If you found the article helpful, you can clap and follow. So you will notified of new articles.

# Reference

It was created with the help of ChatGPT AI.

--

--

Göksu Deniz

Software Engineer, passionate about creating efficient solutions. Skilled in mentoring teams to deliver successful projects. Always exploring new tech trends.