Consider a logging tool. Currently, you need to provide a convenient logAPIAllows you to easily record logs. This log must be recorded in a specified text file. The recorded content belongs to the string type and its value is provided by the customer. We can easily define a log object:
Public class log
{
Public void write (string target, string log)
{
//Implementation content;
}
}
When you need to call the log function, you can create a log object to complete the log record:
Log = new log ();
Log. Write ("error. log", "log ");
However, with the frequent use of log records, more and more log-related files make it inconvenient to query and manage logs. In this case, the customer proposes to change the logging method and write the log content to the specified data table. Obviously, if you still follow the previous design, it has great limitations.
Now let's go back to the design stage and think about the logAPIDo you need to consider this change? There are two design concepts: Progressive design and planning design. From the analysis in this example, it is not easy for the designer to consider the possible change of the logging method in the future at the beginning of the design. Furthermore, if a comprehensive design is considered at the very beginning, design redundancy will be generated. Therefore, the design of the plan is certainly forward-looking, but on the one hand, the designers are too demanding, but it will also produce some defects. So should we consider another change in the future when we adopt a progressive design and use the reconstruction method to improve the existing design when there is a change in demand? This is a matter of opinion and wisdom. In this example, we can directly modifyWrite ()To solve this problem. However, such a design is naturally subject to another possible change in the future.CodeThe danger of a large number of modifications, for example, we need to log to the specifiedXMLFile.
Therefore, changes are completely possible. As time and technical capabilities permit, I prefer to minimize the impact of changes on design. In this case, we need to encapsulate the changes.
Before encapsulating changes, we need to figure out what changes have taken place? From the perspective of requirements, the logging method has changed. This concept may lead to two different results. One case is that we regard the log record method as an action. Specifically, it is a user request. In another case, we analyze the log from the perspective of objects. We regard logs in various ways as different objects, and they call the same interface behavior. The difference is that they create different objects. The former requires us to encapsulate "changes in user requests", while the latter requires us to encapsulate "Changes in log object creation ".
Encapsulate "changes in user requests". Here, it encapsulates possible changes in log records. That is to say, we need to abstract the logging behavior into a separate interface, and then define different implementations respectively. 1:
Figure 1: encapsulate changes in log records
If you are familiar with the design pattern, you can see that the structure shown in Figure 1 is exactlyCommandMode. Because we abstract the log record behavior through interfaces, You can freely expand the log record method.IlogInterface. AsLogObject.IlogWeak dependency of interfaces:
Public class log
{
Private ilog log;
Public log (ilog log)
{
This. log = log;
}
Public void write (string target, string logvalue)
{
Log. Execute (target, logvalue );
}
}
We can also encapsulate "Changes in log object creation" to implement logs.APIScalability. In this case, logs are defined as different objects based on different recording methods. When we need to record the log, we create the corresponding log object and then callWrite ()Method To achieve logging. In this case, the log object to be created may change. To encapsulate this change, you can define an abstract factory class to create a log object, as shown in Figure 2:
Figure 2: encapsulate changes in log object Creation
Figure 2:Factory methodThe embodiment of the pattern, composed of abstract classesLogfactoryDedicatedLogObject creation. If you need to record the corresponding logs, such as logging to the database, you must first create a specificLogfactoryObject:
Logfactory factory = new dblogfactory ();
When the applicationProgram, You need to record the log, then passLogfactoryObject To obtain the newLogObject:
Log = factory. Create ();
Log. Write ("errorlog", "log ");
If you want to change the log record mode to a text file, you only need to modifyLogfactoryObject creation:
Logfactory factory = new txtfilelogfactory ();
To better understand the "changes in object creation encapsulation", let's look at an example. Assume that we need to design a database component that can access Microsoft'sSQL ServerDatabase. AccordingAdo. netKnowledge, we need to use the following objects:
Sqlconnection, sqlcommand, sqldataadapter.
If onlySQL ServerWhen accessing the database, we can directly create these objects:
Sqlconnection connection = new sqlconnection (strconnection );
Sqlcommand command = new sqlcommand (connection );
Sqldataadapter adapter = new sqldataadapter ();
If a database component is filled with the above statements, it is obviously unreasonable. It is full of rigidity and bad taste. Once it is required to support other databases, the original design needs to be completely modified, which makes expansion difficult.
So let's think about how to modify the above design? Assuming that the database component requirements or future requirements support multiple databasesConnection,Command,DataadapterObjects, suchSQL Server. That is to say, we need to create an inherited hierarchy for these objects to create abstract parent classes or interfaces for them respectively. Then define different classes for different databases. These classes inherit or implement their parent classes, for exampleConnectionObject:
Figure 3:ConnectionObject hierarchy
ForConnectionThe object abstracts a unifiedIconnectionInterfaces, while supporting various databasesConnectionAll objects are implementedIconnectionInterface. Similarly,CommandObject andDataadapterThe object adopts a similar structure. Now, we can use the principle of polymorphism to create an object:
Iconnection connection = new sqlconnection (strconnection );
From this structure, we can see that object creation may change depending on the database to be accessed. That is to say, the database components we need to design are still unable to cope with changes in object creation in the current structure. With the principle of "encapsulation change", it is necessary to extract the responsibility for creating objects separately for effective encapsulation. For example, the Code for creating an object above should be the responsibility of a special object. We can still create a specialized abstract factory class.DbfactoryAnd is responsible for creatingConnection,Command,DataadapterObject. As for the specific class that implements this abstract class, it has the same structure as the target object. Different factory classes are defined based on different database types, as shown in Figure 4:
Figure 4:DbfactoryClass Diagram
Figure 4 is a typicalAbstract FactoryMode. ClassDbfactoryThe methods in areAbstractMethod, so we can also use interfaces to replace the definition of this class. InheritanceDbfactoryTo create the corresponding database type objects. ToSqldbfactoryClass as an example. The code for creating an object is as follows:
Public class sqldbfactory: dbfactory
{
Public override iconnection createconnection (string strconnection)
{
Return new sqlconnection (strconnection );
}
Public override icommand createcommand (iconnection connection)
{
Return new sqlcommand (connection );
}
Public override idataadapter createdataadapter ()
{
Return new sqldataadapter ();
}
}
Create an AccessSQL ServerThe related objects of the database can be obtained using the factory class. First, we can create a factory object in the initialization part of the program:
Dbfactory factory = new sqldbfactory ();
Then, use the factory object to create the correspondingConnection,CommandAnd other objects:
Iconnection connection = factory. createconnection (strconnection );
Icommand command = factory. createcommand (connection );
Because we use the principle of encapsulation change, we have established a special factory class to encapsulate the changes created by the object. We can see that when we introduce the factory class,Connection,CommandIn the statement for object creation, the dependency between the object and the specific database type has been successfully eliminated. In the above CodeSQLSuchSqlconnection,Sqlcommand. That is to say, the object creation method is completely abstract and irrelevant to the specific implementation. No matter which database you access, these lines of code are irrelevant. The changes to the involved database types are all abstractedDbfactoryAbstract class. To change the database access type, we only need to modify the line of code that creates the factory object. For exampleSQL ServerTypeOracleType:
Dbfactory factory = new oracledbfactory ();
Obviously, this method improves the scalability of database components. We encapsulate the parts that may change into a fixed part of the program, such as the initialization part, or as a global variable. We can also put the parts that may change into the configuration file, create an object by reading the value of the configuration file. In this way, you do not need to modify the code or re-compile the code.XMLFile to change the database type. For example, create the following configuration file:
<Deleetask>
<Add key = "DB" value = "sqldbfactory"/>
</Appsettings>
Modify the code for creating a factory object as follows:
String factoryname = configurationsettings. receivettings ["DB"]. tostring ();
// DblibFor the database component assembly:
Dbfactory factory = (dbfactory) activator. createinstance ("dblib", factoryname). Unwrap ();
modify the database type to Oracle oracledbfactory . This structure is highly scalable and can effectively solve the problems arising from future demand changes.