NET 7 Design Patterns In-Depth 15. Base Design Patterns

Chapter 15
Base Design Patterns
Introduction
The design patterns of the base design patterns category can be divided into the following eleven main sections:

Gateway: It can encapsulate access to an external resource or system.
Mapper: It can communicate between two independent objects.
Layer supertype: A series of classes are considered as the parent of the rest of the classes, and in this way, common features and behaviors are placed in the parent class.
Separated interface: It can put interfaces and their implementations in different packages.
Registry: It can find and work with frequently used objects and services through an object.
Value object: It can put the values in the objects and then compare the objects based on their internal values.
Money: It can facilitate working with monetary values.
Special case: It can add a series of behaviors to the parent class in special situations.
Plugin: It can hold classes during configuration instead of compilation time.
Service stub: It can eliminate dependency on problematic services during testing.
Record set: It can provide a representation in memory of tabular data.
Structure
In this chapter, we will cover the following topics:

Gateway
Mapper
Layer supertype
Separated interface
Registry
Value object
Money
Special case
Plugin
Service stub
Record set
Objectives
In this chapter, you will get acquainted with base design patterns and learn how to implement other design patterns in a more suitable way with the help of these design patterns. You will also learn how to solve frequent problems in the software process with the help of these design patterns.

Base design patterns
Different design patterns can be used in the software development process to solve different problems. Each design pattern has its implementation method and can be used for specific issues.

Among them, some design patterns are the basis of other design patterns or the basis of solving many problems. These types of design patterns are called base design patterns. Today, programmers may deal with many of these design patterns daily.

Gateway
Name:

Gateway

Classification:

Base design patterns

Also known as:

---

Intent:

By using this design pattern, access to an external resource or system can be encapsulated.

Motivation, Structure, Implementation, and Sample code:

Suppose that we need to communicate with a Web API in our program, there are various methods to connect to this Web API, but the best method will be to implement the method of connecting and communicating with this Web API in one place so that the rest of the program can connect to it and use it without needing to know the details of the connection with the Web API.

To implement the above scenario, the following codes can be considered:

public class WebAPIGateway

{

private static readonly HttpClient HttpClient;

static WebAPIGateway() => HttpClient = new HttpClient();

public static async Task GetDataAsync(string url)

{

try

{

var response = await HttpClient.GetAsync(url);

response.EnsureSuccessStatusCode();

return await response.Content.ReadAsStringAsync();

}

catch

{

throw new Exception("Unable to get data");

}

}

}

As seen in the preceding code, in order to connect to WebAPI, the way to receive information from it is encapsulated inside the WebAPIGateway class. The rest of the program can easily use the data of this Web API through WebAPIGateway.

The above code can be used as follows:

var result = await WebAPIGateway.GetDataAsync

("https://jsonplaceholder.typicode.com/posts");

As it is clear in the above code, when using the GetDataAsync method, it is enough to send the service address to this method, and the access details to the external resource are encapsulated.

Notes:

The Gateway should be as simple as possible and designed and implemented in line with business requirements.
This design pattern is very similar to facade and adapter design patterns, but in terms of purpose, they have differences. For example, the facade design pattern presents a different view of what it covers (for example, it presents the system's complexity as a simple view to the client), but the gateway may present the same view. Also, the adapter design pattern tries to connect two classes that are not structurally compatible with each other and work with them. On the other hand, it is possible to use an adapter design pattern in the gateway implementation.
Consequences: Advantages

Code readability improves in the rest of the program.
It becomes easier to test the program.
Using this design pattern, external resources or systems can be changed without changing the code of other program parts.
Consequences: Disadvantages

Due to its similarity with other design patterns, it may not be used in its proper place and damage the design.
Applicability:

In order to connect to external resources or systems, this design pattern can be useful.
Related patterns:

Some of the following design patterns are not related to the gateway design pattern, but in order to implement this design pattern, checking the following design patterns will be useful:

Facade
Adapter
Mapper
Name:

Mapper

Classification:

Base design patterns

Also known as:

---

Intent:

Using this design pattern makes it possible to establish a connection between two independent objects.

Motivation, Structure, Implementation, and Sample code:

Suppose there is a need to communicate between the model and the database. This relationship has already been shown in the data mapper design pattern. As seen in the data mapper design pattern, the model and the database are unaware of each other's existence. Also, both sections are unaware of the mapper's existence. Using the data mapper design pattern is one of the most specific uses of the mapper design pattern.

Notes:

This design pattern is very similar to the gateway design pattern. Mapper is often used when the desired objects do not depend on each other (For example, communicating with a database).
This design pattern can be very similar to the mediator design pattern. The difference is that in this design pattern, the objects that the mapper intends to communicate with are unaware of the existence of the mapper.
In order to be able to use a mapper, there is usually another object that uses a mapper and communicates with other objects. Another way to use a mapper is to combine this design pattern with the observer design pattern. In this case, the mapper can listen to the events in the objects and do something when those events occur.
Consequences: Advantages

Different objects can be connected without creating dependencies on each other.
Consequences: Disadvantages

In terms of its similarity with other design patterns, it may not be used in the right place and cause damage to the design.
Applicability:

It can be useful in order to communicate between objects that are not related to each other.
Related patterns:

Some of the following design patterns are not related to the mapper design pattern, but in order to implement this design pattern, checking the following design patterns will be useful:

Data mapper
Gateway
Mediator
Observer
Layer supertype
Name:

Layer supertype

Classification:

Base design patterns

Also known as:

---

Intent:

Using this design pattern, a series of classes are considered as the parent of the rest of the classes, and in this way, common features and behaviors are placed in the parent class.

Motivation, Structure, Implementation, and Sample code:

Suppose that we are defining domain models. All models must have a property called ID. To implement this requirement, the definition of this feature can be assigned to any of the classes, or the ID property can be placed in a class so that other domain models can inherit from this class, as shown below:

public abstract class BaseModel

{

public int Id { get; set; }

}

public class Author : BaseModel

{

public string FirstName { get; set; }

}

As it is clear in the preceding code, the ID property has been transferred to the BaseModel class, and any class that needs an ID can inherit from this class.

Notes:

This design pattern can be combined with Generics in C# for better usability.
You can have several layer supertypes in each layer of the software.
Using this design pattern to define the identity field in models is very useful.
Consequences: Advantages

Using this design pattern will reduce duplicate codes, making it easier to maintain the code.
Consequences: Disadvantages

If this design pattern is not used correctly, it will confuse the code structure. For example, imagine if the BaseModel class has an ID property with an int data type, and the Book class inherits from BaseModel class. But we do not want the ID data type to be int (but we want it to have access to other behaviors in the BaseModel). Then it will be required to add another property to this class, or it will be required to ignore the provided property of the parent class by using the hiding feature (public new string Id { get; set; }), which will make the code confusing and will reduce the readability and maintainability of the code.
Applicability:

This design pattern can be used when there are common features or behaviors between classes.
Related patterns:

Some of the following design patterns are not related to layer supertype design patterns, but in order to implement this design pattern, checking the following design patterns will be useful:

Identity field
Separated interface
Name:

Separated interface

Classification:

Base design patterns

Also known as:

---

Intent:

By using this design pattern, interfaces, and their implementation are placed in different packages.

Motivation, Structure, Implementation, and Sample code:

Suppose that we are designing a program. In order to have a better design, reducing the dependency between different parts of the program can be a very important factor. One of the ways to reduce dependency is to use interfaces and then provide implementation based on these interfaces.

This separation itself has different states, including:

Interfaces and related implementations are in one package. Refer to the following figure:
Figure%2015.1.png
Figure 15.1: Interface and its implementation in the same package

namespace Package

{

public class Client

{

public IUnitOfWork UnitOfWork { get; set; }

}

public interface IUnitOfWork

{

bool Commit();

}

public class UnitOfWork : IUnitOfWork

{

public bool Commit() => true;

}

}

As it is clear in the above code, classes one and two are all in one package. In the above code, the namespace is assumed as a package.

Interfaces and related implementations are in different packages:
Interfaces and clients are in one package: Using this method can be easier if only one or all clients are in one package. In fact, in this method, the client is responsible for defining the interfaces, and the client will be able to work with any package that implements the defined interfaces.

Figure%2015.2.png
Figure 15.2: The interface and the client are in the same package,
but the implementation is in a different package

In the following code, it can be seen that Client and IUnitOfWork are in Package01 and the implementation of IUnitOfWork is placed in the UnitOfWork class defined in Package02.

namespace Package01

{

public class Client

{

public IUnitOfWork UnitOfWork { get; set; }

}

public interface IUnitOfWork

{

bool Commit();

}

}

namespace Package02

{

public class UnitOfWork : Package01.IUnitOfWork

{

public bool Commit() => true;

}

}

Interfaces and clients are also in different packages: If there are different clients, it is better to separate the package related to interfaces and clients. Refer to the following figure:

Figure%2015.3.png
Figure 15.3: Interface, client, and implementation all are in different packages

Notes:

Using interface keywords or programming language capability to define the interfaces is unnecessary. Sometimes using abstract classes is a much better option; you can put default implementations in them. From version 8 of the C# language, it is possible to provide default implementations for interfaces too.
Initialling the object from implementation is noteworthy in this design pattern. To make this easier, you can use the factory design pattern or assign the object initialization to another package. The appropriate object will be created in that package according to the related interface and implementation.
Consequences: Advantages

Dependencies between different parts of the program are reduced.
Consequences: Disadvantages

Using this design pattern in all parts of the program will only increase the complexity and volume of the code.

Applicability:

This design pattern can be used when there is a need to loosen the dependency between two parts of the system or to provide different implementations for an interface.
When there is a need to communicate with another module, this design pattern can be used so that communication can be established without depending on the implementation of that module.
Related patterns:

Some of the following design patterns are not related to the separated interface design pattern, but in order to implement this design pattern, checking the following design patterns will be useful:

Factory
Registry
Name:

Registry

Classification:

Base design patterns

Also known as:

---

Intent:

By using this design pattern, you can find and work with frequently used objects and services through an object.

Motivation, Structure, Implementation, and Sample code:

Suppose that we want to get all the books of an author. We will probably access their books through the author object to implement this requirement. In fact, through the author object, we can access the books. Now the question is, if we do not have the author object or there is no connection between the author and the book, how can we get an author's books? This is exactly where the registry design pattern comes in handy.

To implement this design pattern, you can do as follows:

public class Registry

{

private static Registry _registry = new();

}

In this implementation, we intend to implement this design pattern using the singleton design pattern. As it is clear in the preceding code, we have defined _registry as static so that we have only one instance of this class. We have also considered its access level private because the user does not need to be involved with the implementation details. Now we need to provide an object to the user so that they can work with the book. For this purpose, the Registry class can be changed as follows:

public class Registry

{

private static Registry _registry = new();

protected BookFinder bookFinder = new();

public static BookFinder BookFinder() => _registry.bookFinder;

}

If you pay attention to the preceding code, we have defined BookFinder as protected. The reason for this is that later we can change the instantiation of this object. Testing is one of the most specific modes in the service stub design pattern. Also, the BookFinder method is defined static, making it easy to work with the Registry class.

Considering the preceding implementation, suppose we also want to design a solution to test these codes. For this, you can proceed as follows:

public class RegistryStub : Registry

{

public RegistryStub() => base.bookFinder = new BookFinderStub();

}

As you can see, the RegistryStub class has inherited from the Registry class. Inside the constructor of this class, we instantiate the bookFinder object in the parent class using the BookFinderStub class. Now, to access the RegistryStub class, you can put the following method in the Registry class:

public static void InitializeStub() => _registry = new RegistryStub();

Or it may even be necessary somewhere in the program to re-instance the _registry. For this purpose, the following method can be defined in the Registry class:

public static void Initialize() => _registry = new Registry();

Notes:

This design pattern can also be implemented as thread safe.
Since we have defined the methods as static in the preceding implementation, there is no reason to define the variables or fields as static. This decision can be different according to the nature of the data and object. One of the applications for defining static variables or fields is the presence of static data, such as a list of cities or provinces and the like.
Paying attention to the scope of data in this design pattern is very important. According to the data type or object, their scope can be variable (at the process, thread, and session levels). Different implementations can be provided according to the scope of data or object. Diversity in the scope can also cause the emergence of a registry class for each scope or one Registry class for all scopes.
Using singleton implementation for mutable data in a multi-thread environment can be inappropriate. Using a singleton for process-level data that cannot be changed (such as a list of cities or provinces) is better.
For data in the scope of one thread (such as a connection to a database), using thread-specific storage resources such as the ThreadLocal class in C# can be very useful. Another solution is to use structures like a dictionary, where the key can be the thread ID.
As in the previous case, you can use a dictionary for data with a session scope. In this case, the key will be the session ID. Also, a thread's session data can still be placed in ThreadLocal.
Passing shared data as a method parameter or adding a reference to shared data as a property to the class are alternative methods of using the registry design pattern. Each of these methods has its problems. For example, if data is passed to a method as a parameter, there may be situations where this method needs to be called through other methods in the Call Tree. In this case, adding a parameter to the method will not be a good approach, and the registry will be better. Also, in the case of adding a reference to shared data as a class property, the preceding problem regarding the class constructor will still exist.
Consequences: Advantages

This design pattern allows you to easily access commonly used or shared data or objects.
Consequences: Disadvantages

Implementing this design pattern can be complicated due to the scope and nature of the data.
The use of this design pattern is often in confirming and tolerating mistakes in the design. You should always try to access the data in the objects through intra-object communication, and using the registry design pattern should be the last option.
It becomes difficult to test the code using this design pattern. Because if the data or objects presented in this design pattern change, the test results can change, and therefore the complexity of writing the test increases.
Applicability:

When providing a series of common data or objects to the rest of the program is necessary, using this design pattern can be useful.
Related patterns:

Some of the following design patterns are not related to the registry design pattern, but in order to implement this design pattern, checking the following design patterns will be useful:

Singleton
Service stub
Value object
Name:

Value object

Classification:

Base design patterns

Also known as:

---

Intent:

Using this design pattern, you can put the values in the objects, then compare them based on their internal values.

Motivation, Structure, Implementation, and Sample code:

Suppose we need to store different addresses for publishers in our program and then compare these with each other. In the desired scenario, an address consists of a city, main street, sub-street, and alley. A simple way is to design our model as follows:

public class Publisher

{

public string Title { get; set; }

public List

Addresses { get; set; }

}

public class Address

{

public string City { get; set; }

public string MainStreet { get; set; }

public string SubStreet { get; set; }

public string Alley { get; set; }

}

According to the preceding structure, suppose we have two different addresses, and we want to compare them:

Address address1 = new()

{

City = "Shahid beheshti",

MainStreet = "Kavoosifar",

SubStreet = "Nakisa",

Alley = "Rahnama"

};

Address address2 = new()

{

City = "Shahid beheshti",

MainStreet = "Kavoosifar",

SubStreet = "Nakisa",

Alley = "Rahnama"

};

As it is known, both address1 and address2 objects have the same content. With this assumption, we want to check that only one is saved if two addresses are identical. With this assumption, we write the following code:

if (address1 == address2)

publisher.Addresses.Add(address1);

else

publisher.Addresses.AddRange(new List

{ address1, address2 });

By executing the previous code, address1 is not equal to address2. This is because for reference types, instead of comparing values, the addresses of objects are compared.

Using the value object design pattern, we have previously transferred the values into the object (we have placed city and street properties into the Address class). Now, we need a mechanism to compare the values of these objects. For this, you can change the Address class as follows and add the following methods to it:

public static bool operator == (Address address1, Address address2)

=> address1.City == address2.City

&& address1.MainStreet == address2.MainStreet

&& address1.SubStreet == address2.SubStreet

&& address1.Alley == address2.Alley;

public static bool operator !=(Address address1, Address address2)

=> address1.City != address2.City

|| address1.MainStreet != address2.MainStreet

|| address1.SubStreet != address2.SubStreet

|| address1.Alley != address2.Alley;

As it is clear in the preceding codes, we have rewritten the == and != operators in order to present our method for comparing two objects. Now, if we run the following code again, we will find that the comparison results of address1==address2 are true:

if (address1 == address2)

publisher.Addresses.Add(address1);

else

publisher.Addresses.AddRange(new List

{ address1, address2 });

Another point is that if we create two objects with equal values, we will face two HashCodes. For example, consider the following commands:

Address address1 = new("Rahnama","Nakisa","Kavoosifar","Shahid beheshti");

Address address2 = new("Rahnama","Nakisa","Kavoosifar","Shahid beheshti");

In the preceding two lines, two different objects of the Address class are created, but their values are equal. Therefore, we will face two different HashCodes. We may want objects with equal values to generate equal HashCodes. In this case, we must rewrite the GetHashCode method in the Address class as follows:

public override int GetHashCode() =>

City.GetHashCode() ^

MainStreet.GetHashCode() ^

SubStreet.GetHashCode() ^

Alley.GetHashCode();

As seen in the preceding code, to generate the HashCode, the HashCode values of different properties have been combined using XOR(^) and produced the final HashCode. The advantage of the XOR combination is that the result of A XOR B always equals B XOR A.

Another solution to solve this problem is to use struct instead of class. In this case, there was no need to rewrite the operators == and != and GetHashCode.

Notes:

One of the most important differences between reference and value types is how they deal with comparison operations. When using value objects, the comparison operation must be based on values.
Value objects should be immutable. Otherwise, for example, two publishers may refer to the same address. Therefore, for any publisher who changes the address, this change should also be applied to the other publisher. For this purpose, the properties of the Address model can be changed as follows:
public string City { get; private set; }

public string MainStreet { get; private set; }

public string SubStreet { get; private set; }

public string Alley { get; private set; }

public Address(string city,string mainStreet,string subStreet,string alley)

{

City = city;

MainStreet = mainStreet;

SubStreet = subStreet;

Alley = alley;

}

In this case, the objects created from the Address class cannot be changed. There are other ways to make it immutable.

Consequences: Advantages

Using this design pattern will increase the readability of the code.
Logics such as validation can be encapsulated.
It provides the possibility of type safety.
Consequences: Disadvantages

This design pattern will increase the number of classes and objects in the program.
Applicability:

This design pattern is useful when comparing multiple properties using the == operation.
This design pattern will be useful when faced with objects that only complete the body and concept of other objects. For example, the Address object helps to complete the Publisher object.
When there are a series of parameters or data that are always used together and produce the same meaning. For example, the position of a point on the page (X position and Y position).
Related patterns:

Value object design pattern is very important and useful, which can be the basis of many design patterns. Also, in domain-driven design, this design pattern is highly useful.

Money
Name:

Money

Classification:

Base design patterns

Also known as:

---

Intent:

Using this design pattern can facilitate working with monetary values.

Motivation, Structure, Implementation, and Sample code:

Suppose we are designing a program in which the currency type Rial and Dollar will be used. To implement this requirement, one method is to store the amount of money and its unit in one of the class properties. But the problem will occur when we want to work with these monetary amounts. For example, add or subtract a value to it. A bigger problem will occur when multiplying or dividing numerically on these monetary values.

A better solution is to use a class to implement all the complexity and related details. Wherever we need to use monetary values, we can instantiate this class and use that instance. In other words, the Money class will be a value object. To implement the preceding requirement, suppose we have the following enum:

public enum Currency

{

Rial,

Dollar

}

In the preceding enum, we have defined different types of monetary units. According to the preceding enum, the Money class can be defined as follows:

public class Money

{

public int Amount { get; private set; }

public int Fraction { get; private set; }

public Currency Currency { get; private set; }

public Money(int amount, int fraction, Currency currency)

{

Currency = currency;

(int NewAmount, int NewFraction) = NormalizeValues(amount, fraction);

Amount = NewAmount;

Fraction = NewFraction;

}

private (int NewAmount, int NewFraction) NormalizeValues(

int amount, int fraction)

=> NormalizeValues((double)amount, (double)fraction);

private (int NewAmount, int NewFraction) NormalizeValues(

double amount, double fraction)

{

if (Currency == Currency.Rial)

fraction = 0;

else if (Currency == Currency.Dollar)

{

double totalCents = amount * 100 + fraction;

amount = totalCents / 100;

fraction = totalCents % 100;

}

return ((int)amount, (int)fraction);

}

}

In the preceding class, the Amount property represents the integer part of the value, and the Fraction represents its decimal part. As we know, the values do not have a decimal part in Rial currency, so it has been checked in the NormalizedValues method so that if the currency unit was Rial, the value of the decimal part is considered zero. The Currency property in this class also represents the monetary unit.

In the following, we will rewrite the Equals method to compare two Money objects.

public override bool Equals(object? other)

=> other is Money otherMoney && Equals(otherMoney);

public bool Equals(Money other)

=> Currency.Equals(other.Currency)

&& Amount == other.Amount && Fraction == other.Fraction;

The condition for two Money objects to be the same is that both objects have the same monetary unit, and the Amount and Fraction values are equal. In the same way, the ( ==, !=, < and > ) operators can also be rewritten.

public static bool operator ==(Money a, Money b) => a.Equals(b);

public static bool operator !=(Money a, Money b) => !a.Equals(b);

public static bool operator >(Money a, Money b)

{

if (a.Currency == b.Currency)

{

if (a.Amount > b.Amount)

return true;

else if (a.Amount == b.Amount && a.Fraction > b.Fraction)

return true;

else

return false;

}

return false;

}

public static bool operator <(Money a, Money b) { if (a.Currency == b.Currency) { if (a.Amount < b.Amount) return true; else if (a.Amount == b.Amount && a.Fraction < b.Fraction) return true; else return false; } return false; } public static bool operator >=(Money a, Money b)

{

if (a.Currency == b.Currency)

{

if (a.Amount > b.Amount)

return true;

else if (a.Amount == b.Amount)

{

if (a.Fraction > b.Fraction || a.Fraction == b.Fraction)

return true;

}

else

return false;

}

return false;

}

public static bool operator <=(Money a, Money b) { if (a.Currency == b.Currency) { if (a.Amount < b.Amount) return true; else if (a.Amount == b.Amount) { if (a.Fraction < b.Fraction || a.Fraction == b.Fraction) return true; } else return false; } return false; } As mentioned before, Money is a type of value object, and therefore the GetHashCode method must be rewritten as follows: public override int GetHashCode() => Amount.GetHashCode() ^ Fraction.GetHashCode() ^ Currency.GetHashCode();

Now we need to use a method to increase or decrease the amount of money in a Money object. For this, the following codes can be considered:

public void Add(Money other)

{

if (Currency == other.Currency)

{

int a = Amount + other.Amount;

int f = Fraction + other.Fraction;

(int NewAmount, int NewFraction) = NormalizeValues(a, f);

Amount = NewAmount;

Fraction = NewFraction;

}

else

throw new Exception("Unequal currencies");

}

public void Subtract(Money other)

{

if (Currency == other.Currency)

{

int a = Amount - other.Amount;

int f = Fraction - other.Fraction;

(int NewAmount, int NewFraction) = NormalizeValues(a, f);

Amount = NewAmount;

Fraction = NewFraction;

}

else

throw new Exception("Unequal currencies");

}

As it is clear in the implementation of the preceding methods, the condition of adding or subtracting is that the monetary units are the same. Also, similar methods can be considered for multiplication and division. The important thing about the multiplication and division methods is to pay attention to the decimal nature of the number, which we want to multiply by the Amount and Fraction values. The following is the simple implementation of the Multiply method:

public void Multiply(double number)

{

number = Math.Round(number, 2);

double a = Amount * number;

double f = Math.Round(Fraction * number);

(int NewAmount, int NewFraction) = NormalizeValues(a, f);

Amount = NewAmount;

Fraction = NewFraction;

}

For example, the input number is rounded to a number with two decimal places in the preceding implementation. Or in the calculation of f, the number of cents is rounded because, for example, in the Dollar currency, we do not have anything called one and a half cents. Also note that in the preceding implementations, in terms of ease of learning, simple examples with the least details are given. More details may be needed in operational tasks, or combining this design pattern with other design patterns is necessary.

Notes:

When using this design pattern, you should consider the decimal values and how to treat these values.
You can convert monetary units to each other using this design pattern and exchange rates.
Consequences: Advantages

Increases readability and facilitates code maintenance.
It causes the implementation of meaningful mechanisms for working with monetary items.
Consequences: Disadvantages

Using this design pattern can have a negative effect on efficiency, and most of the time, this effect will be very small.
Applicability:

You can benefit from this design pattern when using several monetary units in the program.
Related patterns:

Some of the following design patterns are not related to money design pattern, but in order to implement this design pattern, checking the following design patterns will be useful:

Value object
Special case
Name:

Special case

Classification:

Base design patterns

Also known as:

---

Intent:

Using this design pattern, a series of behaviors can be added to the parent class in special situations.

Motivation, Structure, Implementation, and Sample code:

Suppose a code is written inside a method that can be used to get the author's ID, retrieve their information from the database, and return it. The important thing here is what should be returned if no author for the given ID is found. There are usually three ways to answer this question:

Use of exceptions
Return NULL
Returning a specific object
Using method 1 has a negative effect on efficiency. Using an exception in these cases is not a good decision because as soon as the exception occurs, .NET stops everything to process the exception.

Method 2 seems to be suitable and is often used. But by using this method, it will be necessary to write different codes to check the returned response, and, in this way, we will find out whether the response is NULL. Usually, this method will need to inform the user that the author's ID is wrong through method 1.

Using method 3, this scenario is more suitable. Using this method, an object of a specific type is returned. This special type has a series of features, among the most important of which it can be mentioned that this special type inherits from the main type in question.

According to the preceding description, suppose we have the Author model as follows:

public class Author

{

public virtual int AuthorId { get; set; }

public virtual string FirstName { get; set; }

public virtual string LastName { get; set; }

public override string ToString() => $"{FirstName} {LastName}";

}

Note that in the preceding model, we have defined the features as virtual so that we can rewrite them later. Also, suppose the following method returns information related to the author after receiving the author's ID:

public class AuthorRepository

{

private readonly List authorList = new()

{

new Author{AuthorId=1,FirstName="Vahid",LastName="Farahmandian"},

new Author{AuthorId=2,FirstName="Ali",LastName="Mohammadi"}

};

public Author Find(int authorId)

{

Author result = authorList.FirstOrDefault(x => x.AuthorId == authorId);

return result;

}

}

As specified in the Find method, a NULL value is returned if the author ID does not exist in the current implementation. We need to define a special type with the mentioned conditions to correct this problem. Therefore, we define the AuthorNotFound class as follows:

public class AuthorNotFound : Author

{

public override int AuthorId { get => -1; set { } }

public override string FirstName { get => ""; set { } }

public override string LastName { get => ""; set { } }

public override string ToString() => "Author Not Found!";

}

As seen in the preceding code, the AuthorNotFound class has inherited from the Author class, so this class is considered a special type of the Author class. Now, with this class, you can rewrite the Find method in the AuthorRepository class as follows:

public Author Find(int authorId)

{

Author result = authorList.FirstOrDefault(x => x.AuthorId == authorId);

if (result == null)

return new AuthorNotFound();

return result;

}

The preceding code says that if the author ID is unavailable, an object of type AuthorNotFound will be returned. When using this code, you can use it as follows:

Author searchResult = new AuthorRepository().Find(3);

if (searchResult is AuthorNotFound)

{

Console.WriteLine("Author not found!");

}

After receiving the response in the preceding code, it is checked whether the return type is AuthorNotFound or not.

Notes:

You can often use the flyweight design pattern to implement this design pattern. In this case, the amount of memory used will also be saved.
This design pattern is very similar to the null object design pattern. But a null object can be considered a special case of this design pattern. There are no behaviors in the null object design pattern, or these behaviors do nothing. But in the special case design pattern, behaviors can do certain things. For example, in the preceding scenario, the ToString() method in the AuthorNotFound class returns a meaningful message to the user.
Among the most important applications of this design pattern, we can mention the implementation of infinite positive/negative values or NaN in working with numbers.
Consequences: Advantages

By using this design pattern, complications caused by NULL values can be avoided.
Consequences: Disadvantages

Sometimes returning a NULL value will be a much simpler solution, and using this design pattern will increase the complexity of the code as well.
Using this design pattern when critical events occur can cause the program to face problems. In these cases, using exceptions will be the best option.
Applicability:

In order to return the results of mechanisms such as search, this design pattern can be used.
Related patterns:

Some of the following design patterns are not related to special case design patterns, but in order to implement this design pattern, checking the following design patterns will be useful:

Flyweight
Null object
Plugin
Name:

Plugin

Classification:

Base design patterns

Also known as:

---

Intent:

This design pattern allows classes to be held during configuration instead of at compile time.

Motivation, Structure, Implementation, and Sample code:

Suppose there is a method through which a log can be recorded. The behavior of this method is different for test and operational environments. In the test environment, we want to save the logs in a text file, but in the operational environment, we want to save the logs in the database. To implement this requirement, one method is to identify the environment using condition blocks in the mentioned method and perform the desired work based on the environment. The problem with this method is that if we need to check the environment for various tasks, we will end up with messy and complicated code. In addition, if the settings are changed, the code must be changed, recompiled, and finally deployed in the production environment.

Another method is to use the plugin design pattern. This design pattern helps select and execute the appropriate implementation based on the environment using centralized runtime settings. The Figure 15.4 sequence diagram shows the generality of this design pattern:

Figure%2015.4.png
Figure 15.4: Plugin design pattern sequence design pattern

As you can see in Figure 15.4, the request to receive the plugin is given to the Plugin Factory. Based on the requested type, Plugin Factory searches for plugins in the settings and creates an object of that plugin type if it finds the corresponding plugin.

The first step in implementing the plugin design pattern is to use the separated interface design pattern.

public interface ILog

{

void Execute();

}

The preceding code shows the interface that any class that wants to do logging must implement it:

public class TxtLogger : ILog

{

public void Execute()

=> File.AppendAllText(

$"E:\\log\\{Guid.NewGuid()}.txt",

$"Executed at: {DateTime.Now}");

}

public class SqlServerLogger : ILog

{

public void Execute()

=> new SqlCommand($"" +

$"INSERT INTO log (Content)" +

$"VALUES(N'Executed {DateTime.Now}')",

DB.Connection).ExecuteNonQuery();

}

TxtLogger and SqlServerLogger classes are intended for recording logs in a file and database. Both classes have implemented the ILog interface.

Now that we have the classes, we need a class to select and return the appropriate class by referring to the settings. In this scenario, we have put the settings in the files test.props.json and prod.props.json as follows:

{

"logging":

[

{

"interface": "ILog",

"implementation": " MyModule.Log.Plugin.TxtLogger",

"assembly": "MyModule"

}

]

}

As it is clear in the preceding JSON content, it is said that when working with the logging module, the implementation of the ILog interface is in the TxtLogger class in the MyModule assembly. If the environment changes, it will be enough to load the prod.props.json file instead of the test.props.json file.

Now, we need the PluginFactory class so that through this class, the appropriate class can be identified and instantiated based on the environment. For this purpose, consider the following codes:

public class PluginFactory

{

private static readonly List configs;

static PluginFactory()

{

var jsonConfigs =

JObject.Parse(File.ReadAllText(@$"{Environment.Name}.props.json"));

configs = JsonConvert.DeserializeObject>

(jsonConfigs.SelectToken("logging").ToString());

}

public static ILog GetPlugin(Type @interface)

{

ConfigModel config = configs.FirstOrDefault(

x => x.Interface == @interface.Name);

if(config == null)

throw new Exception("Invalid interface");

return (ILog)Activator.CreateInstance(

config.Assembly, config.Implementation).Unwrap();

}

}

As seen in the preceding code, in the static constructor of the class, the settings are read from the JSON file. Next, the GetPlugin method selects the desired settings from the list of settings, and then the corresponding object is created and returned.

Thanks to the possibility of having a default implementation in interfaces in C# language, the GetPlugin method can be used without making any changes in the subset classes. With this explanation, the ILog interface changes as follows:

public interface Ilog

{

public static Ilog Instance = PluginFactory.GetPlugin(typeof(Ilog));

void Execute();

}

In the preceding code, it has been tried to combine the plugin design pattern with the singleton design pattern and increase the readability of the code. To use these codes, you can do the following:

ILog.Instance.Execute();

As you can see, when referring to Instance in ILog, the appropriate plugin is selected, and its Execute method is executed. Now, with this design, at the time of execution, you can change the settings and see the results without the need to make changes in the code and build and deploy again.

Notes:

Settings can be saved in different formats and presented through different data sources.
The setting of the environment can be done in different ways, including JSON files available in the project configuration, YAML files available for pipelines in CI/CD processes, and sending parameters through the CLI.
The important thing about this design pattern is that the interface and the implementation communication should be done dynamically from execution.
Usually, different plugins are in different library files, and the implementation does not need to be in the same interface assembly.
This design pattern is closely related to the Separated Interface design pattern. In fact, you can implement interfaces in other modules or files using a Separated Interface design pattern.
When presenting the assembly containing the intended implementation, attention should be paid to dependencies and dependent assemblies. Using the dotnet publish command for the class library project, all dependencies will be copied to the output. Another way to copy all the dependencies and the rest of the files is to use EnableDynamicLoading and set it to true in the csproj file.
When using this design pattern in .NET, it should be noted that until the writing of this book, .NET does not allow the introduction of new frameworks to the host program. Therefore, everything that is needed must be pre-loaded by the host program.
Consequences: Advantages

By using this design pattern, it is possible to eliminate the dispersion of settings in the length of the code, and in this case, it will be easier to maintain the code.
This design pattern improves the development capability, and new plugins can be added to the program.
Consequences: Disadvantages

This design pattern makes all the development items presented in the interface format. Therefore, from this point of view, the ability to develop will be limited.
Maintainability using this design pattern will be difficult because plugins will need to be compatible with different versions of the provided interface.
The complexity of testing will increase. Because the plugin may work alone, but it may have problems interacting with the rest of the plugins and the program.
Applicability:

When faced with behaviors requiring different implementations during execution, we can benefit from this design pattern.
Related patterns:

Some of the following design patterns are not related to the plugin design patterns, but in order to implement this design pattern, checking the following design patterns will be useful:

Separated interface
Singleton
Service stub
Name:

Service stub

Classification:

Base design patterns

Also known as:

Mock object

Intent:

By using this design pattern, it is possible to eliminate dependence on problematic services during testing.

Motivation, Structure, Implementation, and Sample code:

Let us assume that to integrate the identity information of people in the country, it is necessary to communicate with the web service of the National Organization for Civil Registration and receive the person's identity information while providing the national code. The problem is that the web service used for querying the person's information is not ready at the time of software development by us. In this case, the production team has to wait for the registration web service to be ready so that they can start their production work.

This method will delay production on our side. By using the service stub design pattern, we try to simulate the production processes on the side of the National Organization for Civil Registration in the simplest possible approach. In that case, there will be no need to stop the production and testing process.

To implement the preceding scenario, the following codes can be considered. Let us assume that it is agreed that the return response from the registry office has the following structure:

public class UserInformation

{

public string NationalCode { get; set; }

public string FirstName { get; set; }

public string LastName { get; set; }

public DateTime DoB { get; set; }

}

And assume that the real-person query service is supposed to take the username and return an appropriate response with the preceding structure. With this assumption, the InquiryService interface, which has the role of the gateway, can be defined as follows:

The InquiryService feature can be set using the plugin design pattern in the above interface. Based on the value of this feature, it is determined that the input request should be directed to the web service of the National Organization for Civil Registration, or the service stub defined in our program should be executed. The classes are responsible for directing the request to the National Organization for Civil Registration web service or service stub and must implement the preceding interface.

To connect to the web service of the National Organization for Civil Registration, the following codes are written:

public class InquiryService : IInquiryService

{

public UserInformation Inquiry(string nationalCode)

=> throw new NotImplementedException();

}

Obviously, in the Inquiry method, the necessary codes to connect to the National Organization for Civil Registration web service must be written. But currently, we do not have information on how to connect to that service.

The codes related to ServiceStub are as follows:

public class InquiryServiceStub : IInquiryService

{

public UserInformation Inquiry(string nationalCode)

{

return new UserInformation()

{

NationalCode = nationalCode,

FirstName = "Vahid",

LastName = "Farahmandian",

DoB = new DateTime(1989,09,07)

};

}

}

Now, to use this structure, it is enough to act as follows:

IInquiryService.InquiryService = new InquiryServiceStub();

var result = IInquiryService.InquiryService.Inquiry("1234567890");

As shown in the preceding code, the InquiryService property is set with a ServiceStub object so that the input request will be directed to the ServiceStub. In the future, whenever the web service of the National Organization for Civil Registration is ready, an object of the InquiryService class can be returned through the plugin so that the incoming requests are directed to the web service of the National Organization for Civil Registration.

Notes:

As much as possible, service stubs should be simple without any complexity.
The important point in using this design pattern is that the design is based on abstractions and interfaces, and the classes should not be directly dependent on each other.
Using the Microsoft Fakes framework, you can automatically create stubs from the interfaces in an assembly. This feature has some limitations in relation to static methods or sealed classes.
Combining this design pattern with gateway and plugin design patterns can provide a better design.
Consequences: Advantages

It is easier to test remote services.
Development does not depend on external services and modules, and the development speed increases.
Consequences: Disadvantages

If the class dependencies are not in the form of dependencies, using this design pattern will double the problem. Therefore, before using this design pattern, it is suggested to make sure that the dependencies between the involved classes are correct so that the Service Stub can be easily replaced with the actual implementation.
Applicability:

When an external service or module causes damage to the development process due to its internal problems, this design pattern can be used until the status of that service or module is stabilized so that the development process does not stop.
Related patterns:

Some of the following design patterns are not related to the service stub design pattern, but in order to implement this design pattern, checking the following design patterns will be useful:

Plugin
Gateway
Record set
Name:

Record set

Classification:

Base design patterns

Also known as:

---

Intent:

Using this design pattern, it is possible to provide a memory display of tabular data.

Motivation, Structure, Implementation, and Sample code:

We must get the author's table's data from the database, perform business logic validation, and deliver the UI. One way to do this is to embed the business logic into the database. The main problem with this method is that the business logic will be spread between the application codes and the database. A better approach is to fetch the data related to the authors from the database and put it in a table inside the DataSet. Then you can disconnect from the database and work with this DataSet instead. The DataSet in ADO.NET is the same as the record set; often, we will not need to implement a record set and can use existing features.

Notes:

One of the most important requirements of the record set is that it should be exactly like the database structure.
If the record set can be serialized, then the record set can also play the role of a data transfer object.
Due to the possibility of disconnecting from the database, you can use UnitOfWork and Optimistic Offline Lock when you need to do something to change the data.
There are two general ways to implement a record set. Using the implicit method and the explicit method. In the implicit method, to access the data of a specific table or column, we give its name in the form of a string to a method and get the result. In the explicit method, a separate class can be defined for each table, and the data can be received from the record set through the internal communication of the classes.
ADO.NET uses explicit methods, and with the help of XSD content, it identifies relationships between classes and produces classes based on these relationships.
By combining the design pattern of the record set and table module, domain logic can be placed in the table module. Therefore, the data can be fetched from the database and placed in the record set. Then through the table module, domain logic was implemented on this data and provided to the UI. When changing the data, the changes are given from the UI to the table module, where the business rules and necessary validations are performed. The record set is delivered, so the changes are applied to the database.
Consequences: Advantages

Using this design pattern, you can retrieve data from the database and then disconnect from the database and start working with the data.
Consequences: Disadvantages

In the explicit method, each table's corresponding class must be generated, increasing the code's volume and maintenance difficulty.
In the Implicit method, it will be difficult to understand what tables and columns the Record Set contains.
If a large amount of data is fetched from the database, memory consumption will increase, negatively affecting performance.
Applicability:

This design pattern can be useful when it is necessary to take the data from the database and work with the data in offline mode.
Related patterns:

Some of the following design patterns are not related to the record set design pattern, but in order to implement this design pattern, checking the following design patterns will be useful:

Data transfer object
Unitofwork
Optimistic offline lock
Table module
Conclusion
In this chapter, you got acquainted with the base design patterns and learned how to complete the implementations of other design patterns with the help of these design patterns. Also, in this chapter, you learned how to use these design patterns to implement things like converting currencies to each other and so on.

Join our book's Discord space

Join the book's Discord Workspace for Latest updates, Offers, Tech happenings around the world, New Release and Sessions with the Authors:

https://discord.bpbonline.com

Leave a Reply

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