In the development of business, most of our business in completing various needs and provide solutions, many scenarios we from CRUD can solve the problem, but the work is not much to the promotion of technical people, how to make yourself free from business to find the pleasure of writing code, I made some try, Using design patterns to improve your business code is one of them. Make your code cleaner and more robust, and find some joy in your code.
preface
Ali good people a lot, they all have a common characteristic, ability to think is to see problems, let the person I admire most is thinking is strong, it is a man of insight and ideas, most people still stay on the surface, many locked in mind to escape not to come out, the ancients said, merit sets for the three immortality, speaking is the thinking and cognition, The difference between people, in a long career or life, apart from luck, is actually the difference in cognition and thinking. Therefore, after removing tedious work, how to find joy from the code in the limited time needs to improve the thinking and planning ability. Compiled a 562-page PDF of design patterns
Chain of responsibility design pattern
Serial Killer Mode Definition
Chain of Responsibility Pattern is one of behavioral design patterns. The model structure is similar to real life chain, made a small iron hoop head-tail chain, if this structure is used in the field of programming, each node can be regard as an object, each object has different processing logic, will be issued a request from the head end of the chain, handed on each node along the path chain object, until we have some objects to deal with this request, We refer to such a pattern as the chain of responsibility pattern.
Serial Killer
It is applicable to the process processing of multiple nodes, where each node completes its own part of responsibility and nodes do not know the existence of each other, such as the approval flow of OA and the Filter mechanism in Java Web development.
-
Multiple objects can handle the same request, but which object handles the request is dynamically determined at run time.
-
Submit a request to one of the objects without the request handler being explicit.
-
A set of objects needs to be processed dynamically to process requests.
Take an example in life, for example, you suddenly think the world is so big and you want to see it, but in reality you can not lose your job, get the application for leave OA, if the days of leave is half a day to one day, may be directly approved by the supervisor; If it is 1 to 3 days vacation, it needs approval from the department manager. If it is 3 to 30 days, the general manager needs to approve; More than 30 days, normal will not be approved. This simple process can be tried out in our current business scenario.
Serial Killer Practice
The business process is simple:
-
Call to cancel your credit card
-
The staff cancels the credit card
The background of cancellations of credit cards is that the cancellation of credit cards is not allowed if the bills are not paid off, there is an overflow, there is a special annual fee is not used, etc. In view of this, we add a set of verification logic whether cancellation is allowed before cancellation.
Here it is:
-
Whether the existence bill is not paid off, for example has not paid the bill, has not paid the bill is not paid, the annual fee management fee is not paid, etc.
-
Whether there is an overflow of excess money.
-
Whether there is a high amount of unused points, the user needs to confirm to give up the points.
For these types of cases, three types of filters have been established, which are:
-
UserLogoutUnpaidBillsLimitFilter: whether there is any outstanding amount.
-
UserLogoutOverflowLimitFilter: whether there is overflow.
-
The amount of UserLogoutGiveUpPointsLimitFilter: whether to give up high.
Judge logic is through UserLogoutUnpaidBillsLimitFilter first to judge whether the current user can cancel a credit card. If allowed to continue by UserLogoutOverflowLimitFilter judge whether there is overflow, whether can you cancel the credit card; If there is no overflow section to continue by UserLogoutGiveUpPointsLimitFilter judge whether the current user is high integral, the front three, as long as there is a not satisfied return in advance.
Public Boolean canLogout(String userId) {// Get user information UserInfo UserInfo = getUserInfo(userId); LogoutLimitFilterChain filterChain = new LogoutLimitFilterChain(); filterChain.addFilter(new UserLogoutUnpaidBillsLimitFilter()); filterChain.addFilter(new UserLogoutOverflowLimitFilter()); filterChain.addFilter(new UserLogoutGiveUpPointsLimitFilter()); boolean checkResult = filterChain.doFilter(filterChain, userInfo); Public Boolean doFilter (LogoutLimitFilterChain filterChain, If (index < filter.size ()) {return filter.get (index++).dofilter (filterChain, userInfo); }}} / / UserLogoutUnpaidBillsLimitFilter doFilter method public Boolean doFilter (LogoutLimitFilterChain filterChain, UserInfo UserInfo) {// Get the current amount owed by the user UserCardBillInfo UserCardBillInfo = findUserCardBillInfo(UserInfo); If (userCardBillInfo! = null) { if ((! CAN_LOGOUT.equals(userCardBillInfo.getEnabledLogout()))) { return false; }} return filterchain.dofilter (filterChain, memberInfo, consumeConfig); } / / UserLogoutOverflowLimitFilter doFilter method public Boolean doFilter (LogoutLimitFilterChain filterChain, UserInfo UserInfo) {// Determine whether the user has overflow payment UserCardDeposit UserCardDeposit = findUserCardDeposit(UserInfo); If (userCardDeposit! = null) { if (userCardDeposit.getDeposit() ! = 0) { return false; }} return filterchain.dofilter (filterChain, memberInfo, consumeConfig); }Copy the code
Summary: The judgment logic of each restriction is encapsulated into a specific Filter. If the logic of some restriction is modified, it will not affect other conditions. If new restriction is needed, it only needs to reconstruct a Filter and knit it into the FilterChain.
A handler object in the chain of responsibility in which there are only two actions, one is to process the request and the other is to forward the request to the next node, not allowing a handler object to process the request and then forward the request to the previous node. For a chain of responsibility, there are only two final situations for a request, one is handled by a processing object, the other is not processed by all objects, the former case is called the chain of responsibility pure chain of responsibility, for the latter case is called impure chain of responsibility, in practical application, most of the impure chain of responsibility. Compiled a 562-page PDF of design patterns
Strategic design pattern
Serial Killer Mode Definition
How do we understand the word strategy? For example, when we go out, we choose different ways to travel, such as by bike, by bus, by train, by plane, and so on. Each of these ways of travel, each of them is a strategy.
Again like we go shopping, shopping mall is now doing activities, there are on sale, full of reduction, rebates, and so on, regardless of the mall for promotional actually, at the end of the day are some algorithms, the algorithm itself is a kind of strategy, and these algorithms is could replace each other at any time, for the same goods, for example, a eighty percent discount, the full 100 minus 30 tomorrow, today These strategies are interchangeable.
A Strategy Pattern defines a set of algorithms, encapsulates each algorithm, and makes them interchangeable.
Serial Killer
In order to eliminate a large number of if else codes, the algorithm logic behind each judgment is extracted into the specific policy object. When the algorithm logic is modified, the user is not aware of it, and only the internal logic of the policy object needs to be modified. These policy objects generally implement a common interface and can be exchanged.
-
Scenarios where multiple classes have only slightly different algorithms or behaviors
-
The algorithm needs to switch scenes freely
-
Scenarios where algorithm rules need to be masked
Serial Killer Practice
The business process is simple:
-
Choose goods
-
Choose different ways to pay the bill
For example, in the upcoming Double 11 event, some offline merchants will hold activities with discounts ranging from 300 to 80, 50% off some products, 30% off at least according to different membership levels, and 20% off for anniversary activities, etc. If these activity discounts are not shared, then the strategy pattern is a good choice for how to implement them and consider extensibility.
/** * public abstract class DiscountStrategy {/** * calculate the price * @param price * @return price */ public abstract CalculationResult getDiscountPrice(Long userId ,BigDecimal price); } public class FullReductionStrategyOne extends DiscountStrategy {/** * Calculate the discount price * @param Price Price * @return */ @override public CalculationResult getDiscountPrice(Long userId,BigDecimal price) {if (price.doubleValue() < 300) { return price; } BigDecimal dealPrice= price.subtract(BigDecimal.valueOf(80)); return getCalculationResult(userId,dealPrice); }} / * * * part goods 5 fold * / public class MerchandiseDiscountStrategy extends DiscountStrategy {/ * * * the price after discount * @ param price, original price * @return */ @Override public CalculationResult getDiscountPrice(BigDecimal price) { BigDecimal dealPrice= Price. Multiply (BigDecimal. The valueOf (0.5)); return getCalculationResult(userId,dealPrice); }} /** * when there is a new requirement, we only need to add a new interface, do not need to change the original policy implementation code. */ public class Price {private DiscountStrategy DiscountStrategy; / / private Price(DiscountStrategy) {this.discountStrategy = DiscountStrategy; } /** * Get discount price ** @param price raw price * @return */ public CalculationResult Discount (Long userId,BigDecimal price) { return discountStrategy.getDiscountPrice(userId ,price); }}Copy the code
The policy pattern is a behavioral pattern that separates the use of an algorithm from the algorithm itself and delegates it to different objects. Policy implementation classes are typically encapsulated lightweight algorithms that can be dynamically replaced as needed when the client (caller) encounters different situations. The choice of policy is completely carried out by the client, which requires the client to understand all the policy implementation classes, which improves the flexibility of the system, but also increases the difficulty of using the client. The policy pattern embodies the open closed principle – “open for extension, closed for modification.” The new policy is added without affecting the modification of other classes. It is expanded and open to extension. It relies only on the abstraction, not the implementation, so it is closed for modification. This reduces coupling while improving code extensibility.
Conclusion: The push logic of each channel is encapsulated into a specific policy. The change of a policy does not affect other policies. Because the common interface is implemented, the policies can be replaced with each other and are user-friendly. For example, the Java ThreadPoolExecutor task rejection policy is executed when the thread pool is saturated. The specific rejection logic is encapsulated in rejectedExecution in the RejectedExecutionHandler.
Template Design pattern
Serial Killer Mode Definition
The value of the template lies in the definition of the skeleton. The process of problem processing has been defined inside the skeleton. The general processing logic is generally realized by the parent class, and the personalized processing logic is realized by the child class.
Such as fried potato and fried mapo tofu, the general logic is:
1, chopping vegetables
2. Drain
3, cooking
4, out of the pot
1,2,4 are the same, but the third step is different, stir-fry potato shreds with a shovel, but stir-fry mapo tofu with a spoon to gently push, otherwise the tofu will be rotten.
Serial Killer Use Scenario
The processing process of different scenarios, part of the logic is generic, can be put into the parent class as a general implementation, part of the logic is personalized, need to subclass personality implementation.
In the Template Pattern, an abstract class exposes ways/templates that define the methods that execute it. Its subclasses can override the method implementation as needed, but calls will be made as defined in the abstract class. This type of design pattern is a behavioral pattern.
Serial Killer Practice
Following the previous example of commodity discount, we added two new requirements:
-
Users get different discounts for adding trace.
-
Whether the user upgrades the membership level after enjoying the discount.
So the process now goes like this:
1. Start trace.
2. Calculate different discounts for users.
3. Whether to allow the upgrade of member level? If the upgrade logic is allowed.
4. The trace is complete.
Where 1 and 4 are generic, and 2 and 3 are personalized, it is possible to add a layer of superclass strategy before the discount strategy, putting the generic logic in the superclass.
The modified code is as follows:
abstract class AbstractDiscountStrategy implements DiscountStrategy{ @Override public CalculationResult getDiscountPrice(Long userId ,BigDecimal price) { //1. Span span = buildSpan(); //2. The specific channel push logic is implemented by subclass CalculationResult CalculationResult =getCalculationResult(userId,price); //3. Whether to allow the member level to be upgraded. calculationResult.isSuccess() && canUpgrade()){ upgradeLevel(userId,calculationResult); } //4. Trace ends the span. Finish (); return calculationResult; Protected Abstract CalculationResult getCalculationResult(Long userId,BigDecimal price); Protected Abstract Boolean canUpgrade(Long userId,CallResult CallResult); } public Class FullReductionStrategyOne extends AbstractDiscountStrategy {@override.} Public class FullReductionStrategyOne extends AbstractDiscountStrategy {@override ProtectedCalculationResult getCalculationResult (Long userId, BigDecimal price) {/ / perform discount logic} @ Override protected Boolean canUpgrade(Long userId,CallResult callResult){ return false } }Copy the code
Observer Design Pattern
Serial Killer Mode Definition
At an auction, the auctioneer watches for the highest bid and notifies other bidders to bid. As the name suggests, this pattern requires an Observer and an Observable. Observers are notified when the Observable state changes. Observers typically implement a generic class of interfaces.
For example, java.util.Observer, when an Observable needs to notify an Observer, it simply calls the update method of the Observer one by one. The success or failure of the Observer processing should not affect the Observable process.
Serial Killer Use Scenario
When there is a one-to-many relationship between objects, the Observer Pattern is used. For example, when an object is modified, the objects that depend on it are automatically notified. The observer pattern is a behavioral pattern.
An Observable’s state change needs to be notified to other objects. The existence of an Observer does not affect the processing result of an Observable. The addition or deletion of an Observer is not aware of an Observable’s state change.
For example, when a Producer sends a message to a Topic, the Producer does not care whether one Consumer or 10 consumers subscribe to the Topic.
Serial Killer Practice
In the section of responsibility chain design mode, I solved the problem of credit card cancellation limit check through three filters, one of which is used to check the user’s points. Here I just read the total amount and times of users’ points, so how to complete the accumulation of points obtained by consumption times?
This is where the observer pattern is used. Specifically, when the transaction system receives a payment success callback, it issues a “payment success event” through Spring’s event mechanism.
In this way, subscribers who are responsible for the number of points spent and the voice broadcast will receive the “payment success event” and do their own business logic.
Let’s draw a simple picture to describe it:
*/ PayCallBackController implements ApplicationContextAware {private ApplicationContext ApplicationContext; // If you need to implement the ApplicationContextAware interface to get the applicationContext, @override public void setApplicationContext(applicationContext) applicationContext) throws BeansException { this.applicationContext = applicationContext; } @RequestMapping(value = "/pay/callback.do") public View callback(HttpServletRequest request){ if(paySuccess(request){ PaySuccessEvent event = buildPaySuccessEvent(...) ; / / by applicationContext publishing events, so as to achieve the aim of informed observer enclosing applicationContext. PublishEvent (event); Public class Implements ApplicationListener<PaySuccessEvent>{@override} /** ** Public class Implements ApplicationListener<PaySuccessEvent>{@override Public void onApplicationEvent(PaySuccessEvent Event) {// Voice broadcast logic}} // Other handlers have similar logicCopy the code
Conclusion: The observer mode decouples the observed from the observer. The presence or absence of the observer does not affect the existing logic of the observed.
Decorator design patterns
Serial Killer Mode Definition
Decorator patterns allow new functionality to be added to an existing object without changing its structure. This type of design pattern belongs to the structural pattern, which acts as a wrapper around an existing class. This pattern creates a decorative class that wraps around the original class and provides additional functionality while preserving the integrity of the class method signature.
Decorators are used to wrap existing classes and make enhancements that are transparent to the user. For example, a BufferedInputStream in Java can add enhancements to its wrapped InputStream to provide caching.
Serial Killer Use Scenario
Add responsibilities to individual objects in a dynamic and transparent manner without affecting other objects. Functions need to be added to an object dynamically, and they can also be dynamically undone. When the system cannot be extended or inherited by inheritance. When you want to enhance the functionality of an existing class without adding too many subclasses, you can use the decorator pattern to achieve the same effect.
Serial Killer Practice
There is a coffee shop, selling all kinds of coffee, latte, cappuccino, blue mountain coffee, etc., before brewing, will ask customers if they want to add sugar, milk, mint and so on. So different coffees with different spices will sell at different prices.
/** * abstract class Coffee */ public abstract class Coffee {/** * public abstract String getName(); Public abstract double getPrice(); } /** * Using a combination of inheritance and composition, we can now consider designing a decorative class that also inherits from coffee, */ Public Abstract class CoffeeDecorator implements coffee {private Coffee delegate; public CoffeeDecorator(Coffee coffee) { this.delegate = coffee; } @Override public String getName() { return delegate.getName(); } @Override public double getPrice() { return delegate.getPrice(); Public class MilkCoffeeDecorator extends CoffeeDecorator {public MilkCoffeeDecorator extends CoffeeDecorator {public MilkCoffeeDecorator(Coffee) {public MilkCoffeeDecorator extends CoffeeDecorator {public MilkCoffeeDecorator(Coffee) {public MilkCoffeeDecorator extends CoffeeDecorator {public MilkCoffeeDecorator(Coffee) coffee) { super(coffee); } @override public String getName() {return "milk," + super.getName(); } @override public double price () {return 1.1 + super.getPrice(); Public class App {public static void main(String[] args) {public static void main(String[] args) {// Coffee blueCoffee = new blueCoffee (); System.out.println(blueCoffee.getName() + ": " + blueCoffee.getPrice()); // Add milk blueCoffee = new MilkCoffeeDecorator(blueCoffee); System.out.println(blueCoffee.getName() + ": " + blueCoffee.getPrice()); // Add mint blueCoffee = new MintCoffeeDecorator(blueCoffee); System.out.println(blueCoffee.getName() + ": " + blueCoffee.getPrice()); // Add sugar to blueCoffee = new SugarCoffeeDecorator(blueCoffee); System.out.println(blueCoffee.getName() + ": " + blueCoffee.getPrice()); }}Copy the code
Bottom line: The use of the decorator pattern enhances functionality so that users can continue to use the original functionality by making simple combinations. The decorator pattern demonstrates the flexibility of composition. Use it to implement extensions. It is also an expression of the open closed principle. If the runtime functionality is dynamically extended relative to a class. This is when you should consider using decorator mode!
Bridge design pattern
Serial Killer Mode Definition
A bridge pattern is a structural design pattern that splits a large class or set of closely related classes into two separate hierarchies, abstraction and implementation, that can be used separately during development.
Bridges are used to decouple abstraction and implementation so that they can vary independently. This type of design pattern is a structural pattern that decoupled abstraction and implementation by providing a bridge structure between the two.
Serial Killer Use Scenario
If a system needs to add more flexibility between abstract and concrete classes to avoid static inheritance relationships between the two levels, bridge patterns can make them establish an association relationship at the abstraction level.
The abstract part and the implementation part can be extended independently in the way of inheritance without affecting each other, the object of an abstract class subclass can be dynamically combined with the object of an implementation class subclass when the program is running, and the system needs to dynamically coupling the abstract class role and the implementation class role.
There are two (or more) dimensions of a class that vary independently, and both (or more) dimensions need to be extended independently.
The bridge pattern is especially useful for systems that do not want to use inheritance or where the number of systems increases dramatically because of multiple layers of inheritance.
Serial Killer Practice
In the performance management system, data needs to be collected, aggregated, and stored before users can query and use it. The collection can be SNMP collection or EMS collection. Storm can be aggregated, spark can be aggregated; The data can be stored in HDFS or MPPDB. According to different scenarios, we can flexibly choose different methods of collection, aggregation and warehousing. This is a function that requires multiple services, each of which has a different type of implementation, and the bridge pattern is ideal.
The bridge mode, as the name suggests, treats each service as a bridge, and we can choose different Bridges depending on the actual situation. The above example shows that data has to cross three Bridges before it can be used: collection bridge -> aggregation bridge -> repository bridge. Each bridge can choose a different construction. Compiled a 562-page PDF of design patterns
/** ** collection bridge CollectionService ** / public abstract class CollectionService {abstract void execute(); public void run() { execute(); }} /** * aggregation bridge AggregationService ** / public abstract class AggregationService {public void run() {if(null! = collectionService) { collectionService.run(); } execute(); } abstract void execute(); CollectionService collectionService; public AggregationService(CollectionService collectionService) { this.collectionService = collectionService; ** / public abstract class StoreService {public void run() {if(null! = aggregationService) { aggregationService.run(); } execute(); } abstract void execute(); AggregationService aggregationService; public StoreService(AggregationService aggregationService) { this.aggregationService = aggregationService; }} /** ** EMS collection bridge ** / public class EMSCollectionService extends CollectionService {@override void execute() { System.out.println("EMS collection."); }} /** ** SNMP collection bridge ** / public class SNMPCollectionService extends CollectionService {@override void execute() { System.out.println("SNMP collection."); }} /** ** Storm aggregation bridge ** / public Class StormAggregationService extends AggregationService {public StormAggregationService(CollectionService collectionService) { super(collectionService); } @Override void execute() { System.out.println("Storm aggregation."); }} /** ** Spark aggregation bridge ** / public Class SparkAggregationService Extends AggregationService {public SparkAggregationService(CollectionService collectionService) { super(collectionService); } @Override void execute() { System.out.println("Spark aggregation."); }} /** ** MPPDB aggregation bridge ** / public Class MPPDBStoreService Extends StoreService {public MPPDBStoreService(AggregationService aggregationService) { super(aggregationService); } @Override void execute() { System.out.println("MPPDB store."); }} /** ** HDFS aggregation bridge ** / public Class HDFSStoreService extends StoreService {public HDFSStoreService(AggregationService) aggregationService) { super(aggregationService); } @Override void execute() { System.out.println("HDFS store."); }} /** ** Bridge Mode test */ public class BridgeTest {public static void main(String[] args) {CollectionService snmpService = new SNMPCollectionService(); AggregationService stormService = new StormAggregationService(snmpService); StoreService hdfsService = new HDFSStoreService(stormService); hdfsService.run(); }}Copy the code
Summary: Bridging mode can decouple the stable part of the system from the extensible part, making the system easier to scale, and meet the OCP principle, which can be modified and closed by callers.
* * the author: alibaba tao department technology official * * my.oschina.net/u/4662964/b…