When we write code, we need to handle different business logic for different situations. The most common ones are if and else. But if there are too many, there can be a lot of “if else”, which is why in many legacy systems a function can have thousands of lines of code. Of course you say that you can do this by extracting methods or classes, handing each case to a method or a corresponding class, but this just looks cleaner, still a lot of “if else”, later when there is new logic, add more” if else”, does not fundamentally solve the problem.

For example, in the realization of short message sending business, companies generally access multiple short message providers, such as Dreamnet, Xuanwu, Aliyun and other short message platforms (we call them short message channels), and may need to switch short message channels according to different types of short message or the stability of short message platforms: 1. For example, Ali Cloud SMS control is very strict, with the words of marketing SMS is not allowed to be sent, marketing SMS needs to use other SMS channels to send; 2. An SMS platform service may be suspended temporarily and needs to be switched to another SMS channel. 3. If some SMS platforms have discounts, you need to switch to this SMS channel temporarily to send SMS messages. 4….

Code implementation

In simple terms, the above service scenarios are as follows: Call corresponding SMS platform interfaces for different SMS channels to realize SMS sending. SMS channels are usually configured in files or databases.

The code is implemented as follows (note that none of the code below can be run directly, just sample code for key logic parts) :

Examples of bad code

We have an SMS sending class: SmsSendService, which has a send method to send SMS smssendService.java

Public class SmsSendService{/** * @param phoneNo Mobile phone number * @param Content SMS content */ public void send(String phoneNo,String String channelType=config.getChannelType(); // If it is SMS channel A, call channel A's API to sendif(Objects.equals(channelType,"CHANNEL_A")){
			System.out.println("Send SMS through SMS channel A"); } // If it is SMS channel B, call channel B's API to sendelse if(Objects.equals(channelType,"CHANNEL_B")){
			System.out.println("Send SMS through SMS channel B"); }}}Copy the code

If one day an SMS channel C is added, then append “else if…”

/ /... Some code omitted here... String channelType=config.getChannelType(); // If it is SMS channel A, call channel A's API to sendif(Objects.equals(channelType,"CHANNEL_A")){
	System.out.println("Send SMS through SMS channel A"); } // If it is SMS channel B, call channel B's API to sendelse if(Objects.equals(channelType,"CHANNEL_B")){
	System.out.println("Send SMS through SMS channel B"); } //ADD: If it is SMS channel C, call channel C's API to sendelse if(Objects.equals(channelType,"CHANNEL_C")){
	System.out.println("Send SMS through SMS channel C"); } / /... Some code omitted here...Copy the code

What if they add another text channel? You write another “else if…” ? Obviously this is not desirable, nor does it follow the “open and closed” principle of SOLID principles — open to extension, closed to change. This way we need to modify the original code every time (there is no closure to changes), adding “if else” over and over again.

Let’s optimize the code:

Optimization Code 1

Define an SMS channel interface SmsChannelService, which is implemented by all SMS channel apis. The SMS channel interface smschannelService.java

Public interface SmsChannelService{// Send SMS void send(String phoneNo,String content); }Copy the code

SMS channel A SmsChannelServiceImplA. Java

public class SmsChannelServiceImplA implements SmsChannelService {
	public void send(String phoneNo, String content) {
		System.out.println("Send SMS through SMS channel A"); }}Copy the code

SMS channel B SmsChannelServiceImplB. Java

public class SmsChannelServiceImplB implements SmsChannelService {
	public void send(String phoneNo, String content) {
		System.out.println("Send SMS through SMS channel B"); }}Copy the code

Initialize all SMS channels with the factory class service smschannelFactory.java

public class SmsChannelFactory { private Map<String,SmsChannelService> serviceMap; // Add all SMS channel services to Map publicSmsChannelFactory(){// The channel type is key and the corresponding service class is value: serviceMap=new HashMap<String, SmsChannelService>(2); serviceMap.put("CHANNEL_A",new SmsChannelServiceImplA());
		serviceMap.put("CHANNEL_B",new SmsChannelServiceImplB()); } public SmsChannelService buildService(String channelType){returnserviceMap.get(channelType); }}Copy the code

The interface to call different SMS channels in the original SmsSendService. The original SmsSendService class is optimized as follows

public class SmsSendService {

	private SmsChannelFactory smsChannelFactory;

	public SmsSendService(){ smsChannelFactory=new SmsChannelFactory(); } public void send(String phoneNo,String content){// Read the SMS channel from the configuration. String channelType=config.getChannelType(); / / type of access to the corresponding service class SmsChannelService channelService = smsChannelFactory. BuildService (channelType); // Send a short message channelservice. send(phoneNo,content); }}Copy the code

So the SmsSendService class is very neat, and I get rid of the “if else”, so if I want to add an SMS channel C, I don’t have to change the SmsSendService class again. Simply add a class SmsChannelServiceImplC that implements the SmsChannelService interface, and add a line of code to the factory class SmsChannelFactory that initializes SmsChannelServiceImplC.

The realization of SMS channel C SmsChannelServiceImplC increase. Java

public class SmsChannelServiceImplC implements SmsChannelService {
	public void send(String phoneNo, String content) {
		System.out.println("Send SMS through SMS channel C"); }}Copy the code

Modify the factory class smschannelFactory.java

public class SmsChannelFactory { private Map<String,SmsChannelService> serviceMap; // Initialize the serviceMap and place all SMS channel services in Map publicSmsChannelFactory(){// The channel type is key and the corresponding service class is value: serviceMap=new HashMap<String, SmsChannelService>(3); serviceMap.put("CHANNEL_A",new SmsChannelServiceImplA());
		serviceMap.put("CHANNEL_B",new SmsChannelServiceImplB()); //ADD ADD a line of SmsChannelServiceImplC initialization code servicemap.put ("CHANNEL_C",new SmsChannelServiceImplC()); } public SmsChannelService buildService(String channelType){returnserviceMap.get(channelType); }}Copy the code

“If else” is dead, but we still have to modify the SmsChannelFactory class, which doesn’t satisfy the” open and close principle “, is there a better way?

We further optimized the code by using Spring’s dependency injection:

Optimization Code 2

Adding the getChannelType() method to the SmsChannelService interface is a key step.

Public interface SmsChannelService {// Send SMS void send(String phoneNo,String content); // Key: add getChannelType() method, subclass implement this method to identify the channel type String getChannelType(); }Copy the code

Subclasses to increase the implementation of this method, and combined with @ Service annotations, make its make the spring container management SmsChannelServiceImplA. Java

@Service
public class SmsChannelServiceImplA implements SmsChannelService {
	public void send(String phoneNo, String content) {
		System.out.println("Send SMS through SMS channel A"); } // Key: add getChannelType() to implement public StringgetChannelType() {
		return "CHANNEL_A"; }}Copy the code

SmsChannelServiceImplB.java

@Service
public class SmsChannelServiceImplB implements SmsChannelService {
	public void send(String phoneNo, String content) {
		System.out.println("Send SMS through SMS channel B"); } // Key: add getChannelType() to implement public StringgetChannelType() {
		return "CHANNEL_B"; }}Copy the code

Modify the SmsChannelFactory class: This step is also critical. SmsChannelFactory.java

@Service public class SmsChannelFactory { private Map<String,SmsChannelService> serviceMap; / * injection: */ @autoWired Private List<SmsChannelService> serviceList */ @autoWired Private List<SmsChannelService> serviceList; /* @postConstruct private void to initialize serviceMap after SmsChannelFactory instantiationinit() {if(CollectionUtils.isEmpty(serviceList)){
			return; } serviceMap=new HashMap<String, SmsChannelService>(serviceList.size()); // Convert serviceList to serviceMapfor(SmsChannelService channelService : serviceList) { String channelType=channelService.getChannelType(); GetChannelType () from different implementation classes returns the same value.if(serviceMap.get(channelType)! =null){ throw new RuntimeException("There can only be one implementation class for the same SMS channel."); } /* If the channel type is key and the corresponding service class is value, it is the same as manually setting CHANNEL_A in Optimization Code 1"、"CHANNEL_B"This method is more automatic than that, we will add" CHANNEL_C" later.No need to change the code * / serviceMap. Put (channelType channelService); }} public SmsChannelService buildService(String channelType){returnserviceMap.get(channelType); }}Copy the code

SmsSendService annotated with @service. Inject SmsChannelFactory smssendService.java via @autoWired

@Service public class SmsSendService { @Autowired private SmsChannelFactory smsChannelFactory; Public void send(String phoneNo,String content){// read the SMS channelType from the configuration. String channelType= config.getchanneltype (); / / build channel type the corresponding service class SmsChannelService channelService = smsChannelFactory. BuildService (channelType); // Send a short message channelservice. send(phoneNo,content); }}Copy the code

In this case, if you need to add a channel C, you really only need to add a SmsChannelServiceImplC, no need to change the original code, completely follow the “open and close principle”.

SmsChannelServiceImplC.java

@Service
public class SmsChannelServiceImplC implements SmsChannelService {
	public void send(String phoneNo, String content) {
		System.out.println("Send SMS through SMS channel C");
	}

	public String getChannelType() {
		return "CHANNEL_C"; }}Copy the code

The above optimization removes the “if else” nicely, no longer has” smelly and long “code like” toilet roll “, and fully follows the “open and close principle”. Spring is a good thing, but it depends on how you use it.

If you don’t understand anything, please leave a comment below and remember to like it!