The introduction
When we write business code, we must inevitably involve the branch of business logic to write if-else statements. If the current logic has only one if-else, the readability of the code will not be overly affected, but if the if block is bloated, and if the else block is bloated, the overall readability of the code will be very poor, because as the logic expands, the if-else block will continue to have more if-else blocks.
Normally, a beginner will wrap a method, that is, encapsulate the logic of the if block into another method, so that it looks like it was before, with only an if-else. However, this does not address the root problem: poor code readability. Because when you’re reading the code, you’re still going to be inside the encapsulated method, and then there’s going to be more if-else waiting for you inside the method. As the number of branches of logic increases, the human brain is often unable to remember the previous logic at the same time, so the overall readability of the code remains poor.
So how do we solve these problems?
Train of thought
The fundamental reason is that we don’t have the mental capacity to sort through the logic, to generalize the logic, to look at the back and forget the front, so if we sort out the multiple levels of logic into a single level of logic, then the overall readability of the code becomes easier to understand. The original multilevel logic example is shown below:
Similarly, there is a multilevel logical structure, and if you just encapsulate methods, then as a code reader you get caught up in all sorts of if and else method jumps inside of it, rather than understanding the business as a whole.
Based on this common multi-level logic, we should split it into single-level logic, as shown below:
In this way, we can split the code processing into 1 business logic processing interface, 4 business logic processing implementation classes, 1 factory class or context class. It then gets the enclosing business logic processing interface instance through the factory class on the main logical code block and invokes the processing method. In order to achieve business encapsulation and abstraction, that is, to abstract the relevant logic into an interface, different implementation. The next time you add another logic, you simply add an implementation class and a type identifier to the factory method, leaving the main logic code unchanged.
Implementation Scheme 1
Here in the roadside geomagnetic parking space, for example, the specific offline business is in the parking lot will install a geomagnetic hardware, when a car came in at the meeting to a vehicle, the car out of the time at a vehicle out of events, in addition to these two events, when or free parking space occupied, news 2 heartbeat: continue to take up and continue to free
We will create a state handling interface with two methods, one to return the state enumeration and one to handle it, as follows:
public interface StateHandler {
DeviceStatusEnum state();
void handle(String deviceNo, String code);
}
Copy the code
We then create a factory class or context handler class that uses Spring’s constructor to inject all implementation classes that implement the StateHandler interface into the cache map of the current instance, along with a method that returns the handler based on an enumeration as follows:
@Component public class StateContext { public Map<DeviceStatusEnum, StateHandler> stateMap = new HashMap<>(); public StateContext(List<StateHandler> stateHandlerList){ stateHandlerList.forEach(handler -> { stateMap.put(handler.state(), handler); }); } public StateHandler getHandler(DeviceStatusEnum state){ return stateMap.get(state); }}Copy the code
Next is the specific state of the processing implementation class, here only put the vehicle out of the processing class, as follows:
@Component
public class OutStateHandler implements StateHandler{
@Override
public DeviceStatusEnum state() {
return DeviceStatusEnum.OUT;
}
@Override
public void handle(String deviceNo, String code) {
// TODO
}
}
Copy the code
In this case, the main logic code simply gets the handler class from the factory class, based on the state type, and executes the method as follows:
DeviceStatusEnum state = DeviceStatusEnum.get(status.getStatus());
StateHandler handler = stateContext.getHandler(state);
handler.handle(request.getSN(), request.getBerthCode());
Copy the code
So that eliminates the if-else
The problem
With plan 1 above, we’ve eliminated the basic if-else, but let’s think a little bit deeper. What’s wrong with this plan later? Obviously, this is an implementation for a specific business, which requires 1 context class, 1 interface, and N implementation classes. So when our business code has multiple if-else for different businesses, each business needs to create 2+N classes, resulting in class bloat.
Therefore, for this problem, we also need to abstract the above implementation method to make it more practical.
Implementation plan 2 (Final Solution)
By thinking about the implementation of scheme 1, we can optimize the following points
- For an interface class, we can abstract the interfaces of different businesses with only one method that returns an enumerated class for a specific business
- For enumerators, different businesses have different enumerators, whose abstractions are their parent classes:
Enum
- For context classes, the original Map cache key is a specific enumeration class and value is a different implementation class. In order to achieve multiple business extension points, we can design the key as the parent of the enumeration class
Enum
, value is a list of different implementation classes
The specific code is as follows:
- Create an abstract interface that returns an enumerated class for a specific business
public interface IExtensionHandler<Y extends Enum>{
Y extension();
}
Copy the code
The generic Y here is the enumeration class for a particular business
- Create an interface for a context class and implement its default implementation
Public interface IExtensionHandlerFactory {/** * add extensionHandler * @param extensionHandler handler * @param <Y> extension point */ <Y extends Enum<Y>>void addExtensionHandler(IExtensionHandler<Y> extensionHandler); /** * get extension point handler * @param extension point * @param type Processor type * @param <T> Extension point * @param <Y> extension point */ <T extends IExtensionHandler<Y>,Y extends Enum<Y>> T getExtensionHandler(Y extension, Class<T> type); }Copy the code
The default implementation of the context class
@Slf4j public class DefaultExtensionHandlerFactoryImpl implements IExtensionHandlerFactory, ApplicationContextAware { private final Map<Enum, List<IExtensionHandler>> serviceListCache = new ConcurrentHashMap<>(); private final Map<ExtensionCacheKey, IExtensionHandler> serviceCache = new ConcurrentHashMap<>(); @Override public <Y extends Enum<Y>> void addExtensionHandler(IExtensionHandler<Y> extensionHandler) { Assert.notNull(extensionHandler.extension(), "add extension handler failed, bean class " + extensionHandler.getClass().getName() + " extension is null"); serviceListCache.putIfAbsent(extensionHandler.extension(), new LinkedList<>()); serviceListCache.get(extensionHandler.extension()).add(extensionHandler); } @Override public <T extends IExtensionHandler<Y>, Y extends Enum<Y>> T getExtensionHandler(Y extension, Class<T> type) { ExtensionCacheKey<Y> cacheKey = new ExtensionCacheKey(extension, type); IExtensionHandler result = this.serviceCache.get(cacheKey); if (result == null) { List<IExtensionHandler> extensionHandlers = serviceListCache.getOrDefault(extension, Collections.synchronizedList(Collections.emptyList())); for (IExtensionHandler extensionHandler : extensionHandlers) { if (type.isAssignableFrom(extensionHandler.getClass())) { result = extensionHandler; serviceCache.put(cacheKey, result); break; } } if (result == null) { log.warn("No IExtensionHandler found by CacheKey : " + cacheKey + " !" ); } } return (T) result; } @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { Map<String,IExtensionHandler> handlerMap = applicationContext.getBeansOfType(IExtensionHandler.class); Log.info (" Crazy loading extension ing...") ); long start = System.currentTimeMillis(); Handlermap.foreach ((k, v) -> {if (! this.getClass().isAssignableFrom(v.getClass())) { addExtensionHandler(v); }}); long end = System.currentTimeMillis(); Log.info (" loading extension point finished, time: {}, number of extension points: {}", end-start, handlermap.size ()); }}Copy the code
In the default context implementation class, we add a Map that is designed to improve the search for extension points. If we just use serviceListCache, each time the enumeration class for a particular business returns a list of extension processors, we need to iterate to find the corresponding processor. Here we have designed a cache Key: ExtensionCacheKey, which is implemented as follows
@Data
@AllArgsConstructor
@ToString
@EqualsAndHashCode
public class ExtensionCacheKey<T extends Enum> {
private T extension;
private Class<? extends IExtensionHandler<T>> extensionHandlerClass;
}
Copy the code
A business enumeration class, which may have different types of extended handling interfaces, why the value of serviceListCache is a List and why an ExtensionCacheKey is needed.
The last
Although the design of this extension point is simple, it has been widely used in my practice projects, and IT is also recommended that everyone understand and use it, which is very helpful for the processing of complex business.
There is another approach to extension point design, annotation-based approach, which can be found in COLA 4.0
Get the full source code at gitee.com/anyin/shiro…
Add your personal wechat friends to discuss below: DaydayCoupons