I wrote a small project a few days ago, which is simple and complex. I can use it to summarize some principles and methods of object-oriented design.
Because it is the company's businessCodeI will briefly summarize and describe the core requirements here.
Download simplified code (vs2010, C #, no web, no windows Service)
Business Requirements:
1. There are three third-party web services: sourcereaderservice, oasystemservice, and orderservice
2. Monitor the readerresults set obtained from sourcereaderservice. Filter out the list of results that meet the conditions, and create oatask through oasystemservice based on these results
3. Wait for manual confirmation whether the new oatask needs to create an order
4. You can manually create an oatask for order in the OA system.
5. monitor the oatask (including manually created oatask) for which an order needs to be created. Once an oatask needs to be created, create an order through orderservice and write the order ID back to the corresponding oatask; if this oatask is manually created, you need to add itProgramIn the log records
6. Monitor the order status changes in orderservice and update orderstatus back to oatask.
7. If readerresult (obtained from sourcereaderservice) exists in unfinished oatask, no new oatask can be created.
8. The program needs to record operation logs (Reader ID, task id, task create date, order ID, order create date, task completed date)
9. Use Windows service for monitoring. In addition, you need to view logs on the web interface and set the trigger conditions for creating oatask by readerresult, oatask subject and assigned.
Three web services I have mock for a moment.
The following are my ideas for writing this program:
Architecture Design when the business is relatively simple: view the essence through the appearance
When the business logic is relatively simple (for example, this example), you can look at the behavior of the program with all the details aside.Behavior-oriented programming is equivalent to interface-oriented programming, because Interfaces describe behavior!
In this example, the interaction between the three web services is apparent, but the core business of the program should include at least three actions: creating a task, creating an order, and updating a task. Follow these steps to create three corresponding interfaces: itaskcreator, iordercreator, and itaskupdater. At the same time, their behaviors naturally come out: createtask (), createorder (), and updatetask ().
Public InterfaceItaskcreator {VoidCreatetask ();}Public InterfaceIordercreator {VoidCreateorder ();}Public InterfaceItaskupdater {VoidUpdatetask ();}
After writing these three interfaces, I came up with a general idea in my mind: using three timers in a Windows service program to hold the above interfaces, then you can call the corresponding interface method in the elapsed event of timer.
Code that is easy to test
After the interface is written, the write is implemented.
It seems that concretetaskcreator has a direct relationship with sourcereaderservice and oasystemservice. If concretetaskcreator directly holds sourcereaderservice and oasystemservice objects, it is very difficult to perform unit testing. Third-party web services are unfriendly to testing. These Web services are directly coupled to concretetaskcreator, making it difficult to test concretetaskcreator.
At this time, we need to encapsulate a third-party web service. The specific ideas are as follows:
Mocksourcereader is designed for unit testing, while webservicesourcereader actually holds a third-party API class (Web Service), which implements the isourcereader interface.
Similarly, the other two web services must also be encapsulated Using Interfaces named itasksystem and iordersystem.
The above practice is actually the application of the facade mode.
The objects in concretetaskcreator are the isourcereader and itasksystem interfaces. In this way, you can pass in different implementations to test concretetaskcreator or run the product code.
Similarly, concreteordercreator and concretetaskupdater both hold the itasksystem and iordersystem interface objects. For manually created oatask, to obtain the readerresult information to create an order, concreteordercreator also needs to hold the isourcereader interface object.
To record logs, concretetaskcreator, concreteordercreator, and concretetaskupdater must all hold the objects at the data persistence layer. This persistence layer object uses the same idea as encapsulating a third-party web service. It is encapsulated through interfaces, so that the actual database is not used during unit test (Unless the objective of unit testing is to test and operate a real database, using a real database during unit testing may lead to a series of problems, such as slow testing speed and influence of old data on new tests.).
This example is a simple example and cannot be reflected.When the business logic is complex and cannot immediately obtain high-level abstraction, writing code that is easy to test sometimes forces a better design.This feature.
Do not make third-party class libraries excessively pollute your program
Sourcereaderservice returns readerresult. oasystemservice includes oatask and oataskstatus. orderservice includes order and orderstatus. These are all required classes in third-party class libraries.
If these classes are directly used as parameters or return types in isourcereader, itasksystem, and iordersystem, our high-level abstract interfaces will be "kidnapped" by third-party class libraries ". Currently, itasksystem uses the oasystemservice web service. If it becomes another third-party API and requires an instance of the xxtask class as the parameter, the "kidnapped" high-level interface cannot be separated from oasystemservice. So,The scope of third-party class libraries should be limited, especially when the third-party class libraries are unstable (the unstable class libraries are far more common than you think, even Microsoft's. NET FrameworkThere are many outdated methods and members).
According to the above idea, we need to create our own task, task status, order, and readerdata classes (even if their attributes are the same as those in the classes provided by the three web services ), put the initiative in your own hands!
Database is implementation details!
It is so important that I have to repeat it again:Database is implementation details! Do not consider databases before designing a project.Schema!
Too many programs are too cumbersome because they are added to the database details at the business logic layer! Too many programs are designed according to the database first design philosophy, so that the business layer pays too much attention to the details of the persistent layer, leading to business logic chaos and maintenance failure! I have written such a program myself and it has been pitted.
In fact, if any method (Database, XML, or even Memory Object) used by a program on the persistence layer does not affect the running of other parts, it should be a good project, at least in terms of isolating the persistent layer, it is good.
The code in this example is not intended to use any complex persistent method. Only one memory object (idbaccessor) can be used to run the program (or unit test.
Game: balance between code volume, Code complexity, and maintainability
Unit testing will undoubtedly increase the amount of code;
Writing maintainable unit tests is a university question, which also increases the workload;
Tested programs are undoubtedly more complex than untested programs;
The design pattern increases complexity, and is even worse when the design pattern is abused;
Good abstraction, encapsulation, and unit testing will greatly increase the maintainability of the Code;
How to choose?
I personally think that:
1. If a project needs to be maintained for a long period of time (I personally think that a year and a half or more years is enough), good unit testing is essential.
2. The level of good abstraction and encapsulation varies from person to person. In short, work hard (I am not very good at this level)
3. Exercise caution when using the design mode. Whenever you want to apply a design pattern, why? If you clearly know that the application DP code will change in the future (most DP aims to encapsulate change points) or to make the code testable, you should apply DP; for dp (it's cool, it's cool !), Or you are not sure whether the code will change in the future. It may be better to encapsulate the code first.
4. if deadline is very tight, there are too many things to do, and there is no time to write a unit test or consider abstraction. There is a section in the story of code "marching toward death, this is the free mini-book. If you cannot change the project management system, maybe you should consider changing your boss.