There are many programmers who write code, but not so many programmers who write good code (may you and I be on the way). Today I’m going to share a case of avoiding a lot of if-else and making progress together.
This article is a problem I encountered with the Flutter project, so the sample code is Dart language. Please don’t burden the reader, language is not the key, the key is the idea.
Origin of the problem
Here are two pieces of actual business code:
-
Before the purchase operation can proceed to the actual purchase process, the necessary conditions need to be verified
_onBuyButtonClick() { /// 1.User block verification final user = getUserInfo(); if (user.isForbidden) { showForbiddenDialog(); return; } /// 2.Unpaid order quantity check final orders = getUserWaitingPayOrders(); if (orders.length >= limit) { showTooMuchOrderWaitingPayDialog(); return; } /// 3. xxx /// 4. xxx /// The buying process } Copy the code
-
The sale operation, followed by the actual sale process, requires the necessary condition verification
_onSellButtonClick() { /// 1.User block verification final user = getUserInfo(); if (user.isForbidden) { showForbiddenDialog(); return; } /// 2.The shop is forbidden to check /// 3. xxx /// 4. xxx /// Selling process } Copy the code
So the process of verification, we all have10
! Items to be verified for each process2 ~ 5
A range. Please experience the requirements document:
Each graph here represents a process, and each yellow square is a checksum item. Of course, the process can’t be repeated, but each check item can be repeated. So we can abstract the question as: How do I add M pre-validations to N operations? Such as:
- operation
N1 (purchase)
Need to checkM1 (whether the user is blocked), M2 (not too many orders waiting for payment)...
- operation
N2 (On sale)
Need to checkM1, M3 (whether the store is banned), M4 (whether the goods being sold have reached the upper limit)...
- operation
N3
Need to verifyM5, M6, M8
.
How can we argue against such a perfectly reasonable demand? 😁 So, let’s kill it!
The solution
Show me the code. Give the current solution in use first, then analyze the details inside.
Our final result is as follows (take the purchase process as an example) :
_onBuyButtonClick() {
/// Use the CheckController to control which conditions need to be checked
final anyChecker = CheckController().check([
Requirement.account,
Requirement.orderBuffer,
]);
/// If there is something to deal with, deal with it
if(anyChecker ! =null) {
anyChecker.handle();
return;
}
/// Previous purchase process
}
Copy the code
As you can see, we’ve reduced what was originally a few dozen lines of validation code (the longest eight validation items, that is, eight if judgments) to a few short lines. In contrast, the scheme has many advantages:
- No duplicate code. before
N2
The validation code in the process is totallyN1
theCopy
. Now even if two processes have the same checksum, it is only the same enumerationcase
On. - Readability is improved and maintainability is greatly improved. In a large number of
if-else
Figuring out what it does isn’t very challenging, but it does take time. Especially after a period of time, without detailed comments. - Maintainability is greatly improved. Check items of a process correspond exactly to the elements of the array, including add, delete, change and check check items. Imagine changing the priorities of two items in a process, which two you need to read before
if
It’s what you care about, and then you can adjust. Now, all you have to do is find the corresponding in the arraycase
You can. And now that they’re absolutely clustered, part of the previous code might be visible on the screen and part of it might not be at all!
How to implement
If you’re interested in the above implementation, here’s how it works.
Stage 1 – Reduce repetition
If we want to reuse M checks, we have to separate the checks from the code. Taking the purchase inspection as an example, we can find that the whole process can be divided into two steps:
- Condition checking
- The result processing
All M checks can be regarded as check XXX condition, if not meet XXX. Here we wrap each check into a separate class, using the example of a check to see if a user is blocked from purchasing:
class AccountForbiddenChecker {
/// Returns whether the user is banned based on the condition
bool match() {
return false;
}
/// Specific actions that a user is banned from doing, such as pop-up warnings
void handle() {}
}
Copy the code
For example, an order waiting for payment should not be inspected too much:
class OrderWaitingPayBufferChecker {
/// Determine whether the customer has too many unpaid orders
bool match() {
return false;
}
/// Specific actions such as pop-up warnings for excessive unpaid orders
void handle() {}
}
Copy the code
Like this, we can encapsulate all of these M checks in concrete classes. Copy-and-paste in multiple process condition checks is avoided. But there is a burden on the user who needs to remember, or at least remember, the name of every Checker. So, we use enumerations to represent each check item:
/// Items to be verified
enum Requirement {
account,
orderBuffer,
// ...
}
Copy the code
We also need to have a conversion process from enumerations to concrete check classes. Switch is used here:
extension Mapper on Requirement {
RequirementChecker toChecker() {
switch(this) {
case Requirement.account: return AccountForbiddenChecker();
case Requirement.orderBuffer: return OrderWaitingPayBufferChecker();
// ...}}}Copy the code
Stage 2 – Increase reproducibility
When you need to coordinate multiple classes, you need a manager.
/// Check the item manager
class CheckController {
/// Based on the enumeration passed in, determine whether a specific item matches and, if so, return the corresponding inspector.
RequirementChecker? check(List<Requirement> items) {
for (final item in items) {
final checker = item.toChecker();
if (checker.match()) {
returnchecker; }}return null; }}Copy the code
RequirementChecker is also required, which is an interface responsible for standardizing each Checker:
abstract class RequirementChecker {
bool match();
void handle();
}
Copy the code
Each specific Checker then implements this interface, so that managers, as well as outsiders, can uniformly use multiple Checkers.
At this point, we have implemented the above solution. For each process, we only need CV method, and then modify the check items slightly to achieve the effect. Bingo!
Related to recommend
Today’s solution is not the author’s startup 🤣. The idea comes from the responsibility chain pattern in the design pattern. Wall crack recommends this site.
For each pattern, there are plenty of diagrams, problems, and solutions. For example,Chain of Responsibility model
Chapter one: It’s not great!
All right, I got all the secrets. I hope you will reach your peak soon!