introduce
In fact, it is not difficult to introduce the design pattern itself. The difficulty lies in how to use it properly in specific scenarios. After introducing Shiro last week, I originally planned to introduce the Chain of Responsibility pattern while the iron is hot, but the Chain of Responsibility pattern itself is not complicated. , we have also touched a lot, such as interceptor chain, filter chain, each interceptor and filter has the opportunity to process the request.
But in what kind of scenarios can the chain of responsibility mode be introduced? I'm actually looking up the corresponding information, and I've watched a lot of videos of this mode on station B, but the comments below are not completely applauded. On the contrary, it is over-design, not as good as strategy mode, so we still have to recognize one thing, design mode can help us solve some problems, like soy sauce for cooking, proper use can make our dishes more delicious, But if you don't use it properly, put too much soy sauce or some dishes should not use soy sauce, the dishes will not taste good. At this point, we can get a glimpse of the evaluation of the Java encryption library design in Shiro (the security framework in the Java field).
The JDK/JCE's Cipher and Message Digest (Hash) classes are abstract classes and quite confusing, requiring you to use obtuse factory methods with type-unsafe string arguments to acquire instances you want to use.
The JDK cipher and MD5 related classes are very abstract and confusing, and it would be silly to ask the developer to use the type-unsafe String parameter to get the corresponding encryption instance from the simple factory pattern.
PS: I think Shiro's rant is not unreasonable. The following is a comparison between the MD5 algorithm used in JDK and the MD5 algorithm used in Shiro. You can experience it, and maybe you will understand why Shiro's designer complained that JDK's encryption library design is stupid of.
// JDK 中的
private static void testMD5JDK() {
try {
String code = "hello world";
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] targetBytes = md.digest(code.getBytes());
System.out.println(targetBytes);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
}
// 这是Shiro的,我个人觉得Shiro的设计更好
private static void testMD5Shiro() {
String hex = new Md5Hash("hello world").toHex();
System.out.println(hex.getBytes());
}
So when I write design patterns, I basically spend a lot of time introducing the concept and application scenarios of this pattern, and I also drive out UML diagrams. I don’t think UML diagrams are helpful for understanding design patterns, because Understanding the UML diagram itself requires a certain learning cost. Ordinary diagrams can express the concept of design patterns, and do not require more predictive concepts.
In order to introduce the strategy pattern, we will introduce such a scenario (each design pattern has its own scenario), suppose you have a bunch of code if else, each judgment corresponds to an execution method, these methods are already very large, Like the following:
public class StrategyDemo {
public void strategy(){
if (条件A){
doA();
}else if (条件B){
doB();
}else if (条件C){
doC();
}else if (条件D){
doD();
}else if (条件E){
doE();
}
}
}
Here, we also assume that there are already a lot of businesses that meet the conditions. The StrategyDemo files are already considerable, and new demands are still pouring in, which means that else if will continue to be added. How to reduce it? The complexity of the StrategyDemo class is that a file that is too large is not conducive to the maintenance of others, and increases the reading cost of other developers. We can compare it to the microservice architecture. In the process of business evolution, the amount of code continues to increase. The number of requests that the service needs to face is also increasing. In order to speed up the development speed and enhance the request capability of the service, we split the monolithic service into microservices:
Let's briefly introduce Module here. Before JDK 9, the unit of management code that JDK originally provided us was a package, and a class was placed in the package. We used maven's parent-child project to realize the concept of modularity:
In essence, it is also a kind of split. Our nature is to like simple things. How to reduce the complexity of the StrategyDemo class? How to reduce the size of this class? The answer given by the strategy pattern is a further single responsibility. , each class allocates a method, so that the complexity is allocated to several classes, how to do it specifically? Extract judgments and behavior definitions into the parent class, which subclass satisfies the corresponding conditions and which subclass executes. A classic example is to connect to various SMS platforms. Different SMS platforms have different behaviors. The above if else is obviously not conducive to our expansion. This is exactly the scenario where the strategy mode comes into play:
public abstract class AbstractSmsStrategy {
/**
* 不同短信平台的发送短信的方式不一样
* 有的给sdk,有的用HTTP,所以这里用抽象方法
* @return
*/
public abstract boolean sendSmsStrategy(String mobile,String content);
/**
* support 相当于if,哪个子类满足该条件,哪个子类发送短信
* @return
*/
public abstract boolean support(String name);
}
public class AliSmsStrategy extends AbstractSmsStrategy{
/**
* 对应的方法
* @return
*/
@Override
public boolean sendSmsStrategy(String mobile,String content) {
System.out.println("阿里发送短信平台成功.....");
return false;
}
@Override
public boolean support(String name) {
return "ali".equals(name);
}
}
public class TencentSmsStrategy extends AbstractSmsStrategy{
@Override
public boolean sendSmsStrategy(String mobile, String content) {
System.out.println("腾讯发送短信平台成功.....");
return false;
}
@Override
public boolean support(String name) {
return "tencent".equals(name);
}
}
public class SmsStrategyContext {
public AbstractSmsStrategy abstractSmsStrategy;
public SmsStrategyContext(AbstractSmsStrategy abstractSmsStrategy) {
this.abstractSmsStrategy = abstractSmsStrategy;
}
public boolean sendSms(String mobile,String content){
boolean result = abstractSmsStrategy.sendSmsStrategy(mobile, content);
return result;
}
}
public class StrategyDemo {
public static void main(String[] args) {
AbstractSmsStrategy abstractSmsStrategy = new AliSmsStrategy();
SmsStrategyContext smsStrategyContext = new SmsStrategyContext(abstractSmsStrategy);
smsStrategyContext.sendSms("","");
}
}
Most of the strategy patterns are actually like this, define a specific abstract strategy to be implemented by subclasses, and then create a new specific strategy, give the context, and the context will implement the specific texting strategy.
Then why do I need to call the corresponding method through a context, can't I directly use the abstract base class to call the corresponding strategy method? Like the following:
public class StrategyDemo {
public static void main(String[] args) {
AbstractSmsStrategy abstractSmsStrategy = new AliSmsStrategy();
abstractSmsStrategy.sendSms("","");
}
}
That's because this context is still relatively redundant in the current scenario. In this mode, the context is to block the interaction between people and specific strategies. If you have docked with the SMS platform, you will find that the SMS platform usually needs to specify the address. ,account password. Then we can load some configuration or do some preprocessing in the context, and store the sending log of the SMS. The meaning of the context is to shield the details of the loading strategy as much as possible. We can transform the context, and you can experience the meaning of the context in this example.
public class SmsStrategyContext {
public AbstractSmsStrategy abstractSmsStrategy;
public SmsStrategyContext(AbstractSmsStrategy abstractSmsStrategy) {
this.abstractSmsStrategy = abstractSmsStrategy;
}
/**
* 其实对应的操作还有很多。
* 发短信的时候,我们一般是将模板存储在数据库中.
* 业务方在调用的时候,我们从数据库中取出对应的模板。
* 需要发送连接的时候,还需要将长链接处理成短链接。
* 你看上下文的作用体现出来了吧。
* 通常发送短信还有异步处理,所以我们这里还可以再做一个线程池,
* 这样看上下文的作用是不是体现出来了。
* @param mobile
* @param content
* @return
*/
public boolean sendSms(String mobile,String content){
//生成短信日志,这里假装有一个短信日志表.
MessageLog messageLog = new MessageLog();
boolean result = abstractSmsStrategy.sendSmsStrategy(mobile, content);
// 这里假装执行对应的入库操作
return result;
}
}
Note that this context does not have to be strictly in accordance with the design mode. The corresponding strategy must be injected and then called. We must understand that the purpose of setting the context is to shield the details of executing the corresponding strategy as much as possible, and let the caller only provide the mobile phone number and Template ID, you can send SMS. In fact, the template method can be further modified. In this example, we also need to explicitly new out the corresponding strategy. Then we can't do new. I will give you an identifier. You can load the corresponding strategy according to the identifier. ? Let the caller do less work, effectively decoupling. In fact, the above strategy pattern can be transformed into the following:
public class SmsStrategyContextPlus {
private static final List<AbstractSmsStrategy> SMS_STRATEGY_LIST = new ArrayList<>();
/**
* 加载所有的短信策略
*/
static {
AbstractSmsStrategy aliSmsStrategy = new AliSmsStrategy();
AbstractSmsStrategy tencentSmsStrategy = new TencentSmsStrategy();
SMS_STRATEGY_LIST.add(aliSmsStrategy);
SMS_STRATEGY_LIST.add(tencentSmsStrategy);
}
/**
* 略去产生日志, 长链转短链。
* 线程池等操作
* @param mobile
* @param templateKey
* @param platform 哪个平台,如果怕调用方写错,其实这里可以做成枚举值
* platform 也可以从配置中加载,在前台选中启用哪个短信平台。
* 这里其实可以做的很灵活。
* @return
*/
public boolean sendSms(String mobile,String templateKey,String platform){
boolean sendResult = false;
for (AbstractSmsStrategy abstractSmsStrategy : SMS_STRATEGY_LIST) {
if (abstractSmsStrategy.support(platform)){
sendResult = abstractSmsStrategy.sendSmsStrategy(mobile,templateKey);
}
}
return sendResult;
}
}
In Spring, we can actually use this:
@Component
public class SmsStrategyContextSpringDemo {
@Autowired
private List<AbstractSmsStrategy> strategyList ;
public boolean sendSms(String mobile,String templateKey,String platform){
boolean sendResult = false;
for (AbstractSmsStrategy abstractSmsStrategy : strategyList) {
if (abstractSmsStrategy.support(platform)){
sendResult = abstractSmsStrategy.sendSmsStrategy(mobile,templateKey);
}
}
return sendResult;
}
}
Pay attention to the role of this context, and shield the caller from the details of executing the corresponding strategy. You don't have to be too strict about its implementation. My personal opinion is that as long as the details of executing the corresponding strategy are shielded from the caller, it is a Context.
use in shiro
The strategy pattern mentioned above is actually reflected in Shiro, a security framework in the Java field. Shiro helped us write the login code. The so-called login is to compare the login information submitted by the front desk with the user information in the data source. There are actually many kinds of sources, such as JDBC, LDAP and so on. Shiro abstracts the Realm interface to obtain information from data sources uniformly. Shiro supports multiple data sources. How does Shiro load user information according to the corresponding data source? In fact, it is also distributed by strategy. AuthenticatingRealm has two abstract methods:
- doGetAuthenticationInfo is handed over to the corresponding realm to obtain the login information
- supports whether the data source is obtained by the current realm
in conclusion
We usually want the class to be smaller, so that it can be read faster. If you have put a lot of judgment in a class, this class is already very large, you may try to use the strategy pattern to distribute, to reduce the complexity of the code, so It is also easy to maintain, or just pre-judgment. It is like connecting to the SMS platform in the system. There are many SMS platform providers. Even if your system already supports well-known SMS service providers, it is not enough. The customer wants to use his own. SMS service provider, to avoid adding a judgment every time you access an SMS service provider, we can take a strategic approach, access a SMS service provider, inherit the parent class, and implement specific SMS sending operations, such as generating SMS logs, For asynchronous sending, we put it in the context to operate, and we don’t need to write repetitive code. We should pay attention to the idea of understanding the context. The meaning of the context is to reduce the difficulty of client calls, shield the details of loading the corresponding strategy, and keep it as simple as possible. To a certain extent, I understand that design patterns are a way to predict or reduce code complexity. In strategy patterns, it is to distribute code, move behaviors that meet conditions to corresponding subclasses, and minimize changes as much as possible. , the judgment of the strategy mode, there are many if elses at present, even if there are no more judgments, crowding too much code in a single file will also make people who understand the code a headache. In order to reduce the complexity, I will carry out these codes according to a single responsibility distribution. The prediction is that there is currently only one if else, but with the development of the business, more and more SMS platforms will be connected, so here we use the context for general operations, access a SMS platform to implement the corresponding method, and reduce the amount of code. The amount of changes makes the code more maintainable and readable.
References
- The modularity of Java 9--the nirvana of the strong man breaking his "wrist" https://zhuanlan.zhihu.com/p/24800180
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。