In addition to Weibo, there is also WeChat
Please pay attention
WeChat public account
Shulou
2025-01-16 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Servers >
Share
Shulou(Shulou.com)06/01 Report--
This article shows you how to use decorator mode and extension methods to achieve Fluent Interface in C#. The content is concise and easy to understand, which will definitely brighten your eyes. I hope you can get something through the detailed introduction of this article.
Using decorator pattern and extension method to realize Fluent Interface background knowledge in C #
Fluent Interface is an API implementation of specific logic processing through continuous method calls. Introducing Fluent Interface into the code can not only improve the development efficiency, but also improve the readability of the code. Since C # 3.0, with the introduction of extension methods, Fluent Interface has become more familiar and used by developers. For example, when we want to find all the even numbers from one list of integers and add them to another list in descending order, we can use the following code:
one
two
three
four
I.Where (p = > p% 2 = = 0)
.OrderByDescending (Q = > Q)
.ToList ()
.ForEach (r = > result.Add (r))
This code not only looks very clear, but also more in line with the way the human brain thinks when it is written. Through these continuous method calls, we first find all the even numbers in list I, then sort these even numbers and add the sorted values to the result list one by one.
In practical application, Fluent Interface is not only used in the query logic similar to the above, but also used by the configuration function of the application development framework, such as Fluent API can be used in Entity Framework Code First to configure the entity (Entity) and model (Model), in addition, there are popular ORM framework NHibernate and enterprise service bus framework NServiceBus, etc., all provide similar Fluent API to simplify the configuration process of the framework. These API are concrete implementations of Fluent Interface. Because the names of the methods in Fluent Interface's method chain are highly descriptive and have a single responsibility, Fluent Interface can also be regarded as a "domain specific language (Domain Specific Language)" to accomplish a domain-specific task. For example, in the above example, Fluent Interface is used to query the domain, while in frameworks such as Entity Framework, NHiberante and NServiceBus, it is used in the configuration domain of the framework.
Next, let's first take a look at the simple implementation of Fluent Interface, briefly discuss the pros and cons of this implementation, and then take a look at an implementation that uses Decorator patterns and extension interfaces.
Simple implementation of Fluent Interface
A simple implementation of Fluent Interface is to process incoming parameters in each method of a type and then return an instance of the type itself, so that when a method of that type is called, other methods can be called continuously without specifying an instance of that type. Now suppose we need to implement a service interface IService, in which we need to use an interface ICache that provides caching and an interface ILogger that provides logging. In order to enable instances of IService to specify instances of the ICache interface and ILogger interface they need in the way of Fluent Interface, we can define the IService interface as follows:
one
two
three
four
five
six
seven
Public interface IService
{
ICache Cache {get;}
ILogger Logger {get;}
IService UseCache (ICache cache); / / return 'this' in implemented classes
IService UseLogger (ILogger logger); / / return 'this' in implemented classes
}
As a result, the configuration of IService instances becomes very simple, such as:
one
two
IService aService = new Service ()
AService.UseCache (new AppfabricCache ()) .UseLogger (new ConsoleLogger ())
This is the simplest way to implement Fluent Interface. For some simple application scenarios, it is indeed a good choice to use this simple and fast way, but while experiencing this convenience, we may need to think further:
The UseCache and UseLogger methods defined directly on the IService interface will destroy the single responsibility of IService itself, which in turn conflicts with the idea of software design. It is not a question for IService to consider which caching service and which logging service to use. Of course, the extension method of C # can easily separate the methods such as UseCache and UseLogger from the IService interface, but it is more reasonable to use the factory to create an instance of IService, and the basis for creating the instance (context) should be provided by other sources of configuration information.
The correctness of the context cannot be guaranteed. In the above example, the problem is not obvious, and whether to call UseCache or UseLogger first will not affect the result in any way. However, in some application scenarios, there are certain dependencies between the set objects. For example, in the Entity Type Configuration of Entity Framework Code First, only if the configured attribute is a string, can you further set the maximum length of the attribute, whether it is Unicode and other options, otherwise Fluent Interface will not provide similar method calls. Obviously, this simple implementation does not meet this requirement.
You need to create an instance of type IService first, and then you can set it using methods such as UseCache and UseLogger. If there is a dependency on ICache or ILogger during the creation of the instance (for example, you want to be able to write some log information using the instance of ILogger in the constructor), then it will be more difficult to implement.
Given the above three points of analysis, when you need to introduce Fluent Interface into an application or development framework more reasonably, the above simple implementation will not meet all the requirements. To this end, I use decorator pattern, and combined with the extension method features of C # to implement Fluent Interface, this way can not only solve the above three problems, but also object-oriented design will make the extension of Fluent Interface easier.
Using decorator pattern and extension method to implement Fluent Interface
Still taking the IService interface in the above article as an example, we can get two revelations through analysis: first, which caching mechanism and which logging mechanism should be used for the instance of IService? this is a process of configuring the instance of IService. Secondly, this configuration process is equivalent to gradually adding new information to the existing configuration information at each configuration stage, such as creating an empty configuration information at the beginning, and when the selected cache mechanism is determined in the first stage, cache-related configuration information will be added based on this empty configuration information, and when the selected logging mechanism is determined in the second stage The configuration information related to logging will be added on the basis of the configuration information obtained in the previous stage, which is just an application scenario of the decorator mode. The last step is very simple, the program only needs to initialize the instance of the IService interface based on the final configuration information. To simplify the implementation process, I chose Microsoft Patterns & Practices Unity Application Block's IoC container to implement this configuration information management mechanism. The advantage of choosing the Unity IoC container is that there are no sequential requirements for the registration of interfaces and their implementation types, and the IoC container automatically analyzes the dependencies between types and registers the types. In fact, in many application development frameworks, Fluent Interface is implemented in the configuration part of the framework in this way.
Introduction of decorator mode
First of all, we introduce the concept of "configurator". The role of the configurator is to configure some aspect of the initialization process of the IService instance (such as cache or log). It returns an instance of the Unity IoC container to the caller so that the caller can perform other configuration operations on the basis of this configuration (for simplicity, the "configuration" described below only means selecting a particular type of implementation. Without other additional configuration content). We can use the following API to define the configurator:
one
two
three
four
Public interface IConfigurator
{
IUnityContainer Configure ()
}
For the convenience of implementation, we will also introduce an abstract class that implements the IConfigurator interface and identifies the Configure method as an abstract method. Therefore, for any kind of configurator, it only needs to inherit from that abstract class and overload the Configure method to implement the configuration logic. The abstract class is defined as follows:
one
two
three
four
five
six
seven
eight
nine
ten
eleven
twelve
thirteen
fourteen
fifteen
sixteen
seventeen
eighteen
nineteen
Public abstract class Configurator: IConfigurator
{
Readonly IConfigurator context
Public Configurator (IConfigurator context)
{
This.context = context
}
Protected IConfigurator Context
{
Get
{
Return this.context
}
}
Public abstract IUnityContainer Configure ()
}
The next step is to implement their own configurators for different configuration links. Let's take the configuration of the cache mechanism as an example to briefly introduce the implementation of the cache configurator.
First define an interface called ICacheConfigurator, which implements the interface of IConfigurator, but it is an empty interface and does not contain any interface definitions for properties, events, or methods. The purpose of introducing this interface is to implement the method extension oriented to the interface in the next extension method definition, so the second problem discussed above can be easily solved. this will be discussed in the next section, "introduction of extension methods". In fact, there are similar designs in many mature applications and frameworks, such as using interfaces as generic constraint types. Therefore, the implementation code for ICacheConfigurator is very simple:
one
two
three
Public interface ICacheConfigurator: IConfigurator
{
}
As a "cache configurator", it only needs to inherit from the Configurator class and implement the ICacheConfigurator interface, as follows:
one
two
three
four
five
six
seven
eight
nine
ten
eleven
twelve
thirteen
fourteen
fifteen
sixteen
seventeen
Public class CacheConfigurator: Configurator
ICacheConfigurator
Where TCache: ICache
{
Public CacheConfigurator (IConfigurator configurator)
: base (configurator)
{
}
Public override IUnityContainer Configure ()
{
Var container = this.Context.Configure ()
Container.RegisterType ()
Return container
}
}
As you can see from the above code, TCache is constrained to the ICache interface type, while in the Configure method, the Configure method of the configuration context (that is, the upper-level configurator instance contained in the configurator itself) is first called, and the configured Unity IoC container instance container is obtained, and then the RegisterType method is called on the container, registering the given cache mechanism implementation type in container, and finally returning the container to the caller.
The implementation of the entire configurator can be summarized with the following class diagram:
The introduction of extension method
As mentioned earlier, extension methods can remove responsibility-independent method definitions from types and implement them centrally in a static class. In the current example, the extension method can also help us "flatten" the hierarchy of type inheritance, making the cohesion logic of the methods in Fluent Interface clearer. Still take the cache configuration part as an example. Suppose we want to configure the cache mechanism after we have obtained the configuration of the service, and only after the configuration of the cache mechanism is completed can we start to configure the logging mechanism. Then we can define the extension method as follows:
one
two
three
four
five
six
seven
eight
Public static ICacheConfigurator WithDictionaryCache (this IServiceConfigurator configurator)
{
Return new CacheConfigurator (configurator)
}
Public static ILoggerConfigurator WithConsoleLogger (this ICacheConfigurator configurator)
{
Return new LoggerConfigurator (configurator)
}
The above WithDictionaryCache method indicates that you need to use a dictionary-based caching mechanism on the configuration of Service, while WithConsoleLogger indicates that you need to choose the console as the logging mechanism on the basis of caching configuration.
We can also learn from the above code that the extension method can also intuitively define the order between various configurations, which is also very convenient to change. For example, if there is no relationship between the caching mechanism and the logging mechanism configuration, then we can use IServiceConfigurator as the first parameter type of WithConsoleLogger without having to modify any other part of the code.
The next thing to do is to design a factory class that can create a new IService instance based on our configuration information.
Implementation of factory class
The implementation of the factory class is very simple, and the extension method is also used to extend the IConfigurator type. after obtaining an instance of the Unity IoC container, you only need to call the Resolve method to return the implementation type of the IService type directly. The use of the Resolve method directly solves the third problem mentioned above. The code for the factory class is as follows:
one
two
three
four
five
six
seven
eight
nine
ten
eleven
twelve
thirteen
fourteen
fifteen
sixteen
seventeen
eighteen
nineteen
twenty
twenty-one
twenty-two
twenty-three
twenty-four
Public static class ServiceFactory
{
Public static IToConfigConfigurator ToConfig ()
{
Return new ToConfigConfigurator ()
}
Public static IService Create ()
{
Return ToConfig (). Service (). Create ()
}
Public static IService Create (this IConfigurator configurator)
{
Var container = configurator.Configure ()
If (! container.IsRegistered ()
Container.RegisterType ()
If (! container.IsRegistered ()
Container.RegisterType ()
If (! container.IsRegistered ()
Container.RegisterType ()
Return container.Resolve ()
}
}
test
Create a test project to test our work. For example, the following test method will test the type of caching mechanism and logging mechanism used by the implementation of IService:
one
two
three
four
five
six
seven
eight
nine
ten
eleven
twelve
[TestMethod]
Public void UseAppfabricCacheAndDatabaseLoggerTest ()
{
Var service = ServiceFactory
.ToConfig ()
.Service ()
.WithAppfabricCache ()
.WithDatabaseLogger ()
.Create ()
Assert.IsInstanceOfType (service.Cache, typeof (AppfabricCache))
Assert.IsInstanceOfType (service.Logger, typeof (DatabaseLogger))
}
Now we can use Fluent Interface to configure the initialization process of the IService instance. The introduction of Fluent Interface is more like using a natural language to describe the configuration process: Service factory, to config (the) service with Appfabric Cache (mechanism) (and) with Database Logger (mechanism).
Summary
Through the discussion of the simple implementation method, this paper leads to the possible design problems, and then chooses a more reasonable implementation method, that is, to implement Fluent Interface by using the decorator pattern and the extended method feature of C #. This new implementation method can not only solve the design problems discussed, but also bring some expansibility to the implementation of Fluent Interface. Finally, the paper makes a simple test of this implementation, and also shows the application of Fluent Interface in practice.
The above content is how to use decorator pattern and extension method to implement Fluent Interface in C #. Have you learned any knowledge or skills? If you want to learn more skills or enrich your knowledge reserve, you are welcome to follow the industry information channel.
Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.
Views: 0
*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.
Continue with the installation of the previous hadoop.First, install zookooper1. Decompress zookoope
"Every 5-10 years, there's a rare product, a really special, very unusual product that's the most un
© 2024 shulou.com SLNews company. All rights reserved.