About interface-oriented programming, do you really understand it?

What is the interface (What)

Baidu said: An interface generally refers to an abstraction that an entity provides to the outside world to separate external communication methods from internal operations so that it can be modified internally without affecting the way other external entities interact with it.

牛津字典说:Interface: A point where two systems, subjects, organizations, etc. meet and interact.

维基百科说:In computing, an interface is a shared boundary across which two or more separate components of a computer system exchange information.

Frank said: All the above is correct, but I have more to say about the interface.

What are the benefits of interfaces (Why)

In my opinion, the interface has two main benefits in software design:

1. Setting standards

The formulation of standards is inseparable from interfaces . The purpose of standards is to separate definition and implementation. As a complete abstraction, interfaces are the best choice for standard formulation.

The operation of this world is inseparable from the division of labor and cooperation, and the premise of division of labor and cooperation is standardization. Imagine that your computer can allow you to change the graphics card from NVIDIA to Colorful; your light bulb is broken, you can find a supermarket and buy a new one and you can replace it; you change the data from Oracle to MySQL, But you don't need to use any code you write based on JDBC. And so on, behind these things are all because of interfaces and standardization based on interface customization.

In the Java world, there is a very NB community called JCP (Java Community Process), which sets standards through JSR (Java Specification Request). It is with JSR-315 (Java Servlet) that our server code can switch freely between Tomcat and Jetty.
image.png

Finally, I would like to summarize the importance of standards in one sentence, which is: "First-rate companies make standards, second-rate companies make brands, and third-rate companies make products.

2. Provide abstraction

In addition to standards, another feature of interfaces is abstraction. It is this abstraction that allows the caller and implementer of the interface to be completely decoupled.

The advantage of decoupling is that the caller does not need to rely on specific implementations, so they don't need to care about implementation details. In this way, whether it is a change in implementation details or a replacement of a new implementation, it is transparent to the caller.

This scalability and flexibility is one of the most beautiful design art in software design. Once you have tasted the beauty of this "rely on interface" design, you will not be willing to return to the simple and crude "rely on implementation". The "interface-oriented programming principle" and the "dependency inversion principle" we usually talk about are both of this design .

In addition, once you have mastered this powerful technique-abstraction-oriented and interface-oriented, you will find that although there is little difference between implementation-oriented and interface-oriented at the code level, the design ideas and design concepts behind it The difference is no less than the difference between my basketball level and James’ basketball level!

    //面向接口
    Animal dog = new Dog();
    
    //面向实现
    Dog dog = new Dog();

As a senior workplace veteran, I suggest that you are doing system design, module design, and even object design. More consideration should be given to the higher level of abstraction-that is, the interface , instead of getting caught up in the implementation details. We must clearly realize that interface design is the main content of our system design. And this kind of person who can jump out of the details and stand at a higher level of abstraction to look at the module design, module division, and module interaction of the entire system is a very scarce talent in our software design team. Sometimes, we also call these people architects .

When to use the interface (When)

When there is a need for scalability

Extensible design mainly uses object-oriented polymorphism, so the interface here is a broad concept. If you use programming language terms, it can be either Interface or Abstract Class.

This demand for scalability can be said to be ubiquitous in software work, as small as a tool. For example, I need a switch function in my current system. The switch configuration is currently configured with a database, but it may be migrated to the Diamond Configuration Center or SwitchCenter in the future.

The simple way is, I directly use the configuration of the database to realize the switch function, as shown in the following figure:
image.png

But the problem with this is obvious. When you need to switch to a new configuration implementation, you have to strip the original application code and make changes. A more appropriate approach should be to provide a Switch interface, allowing different implementations to implement this interface, so that when switching configuration implementations, the application code no longer needs to be changed.
image.png

If we say that the above refactoring is only a partial optimization of the code using the strategy mode, of course it is better to do it, if you don’t do it, the impact is good, you can just pass it.

So the next scenario I want to introduce to you is not just a question of "whether or not", but a question of "have to".

For example, your boss assigns you a task to implement a pluggable product similar to eclipse. At this time, using an interface is not just a matter of choice, but an architectural method you have to use. Because the essence of pluggability is that you make a standard interface (API), and then there are different implementers to implement the plug-in, and finally the PluginManager will string the plug-in mechanism.

The picture below is the Pluggable architecture of an enterprise collaborative cloud I designed for ICBU at the time. In essence, it is a standard and extended design based on interfaces.
image.png

When decoupling is needed

The example of Switch introduced above, on the surface, is a demand for scalability. But the essential reason for not being scalable is precisely because of coupling . When we uncouple the coupling through the Switch Interface, the demand for scalability is easily solved.

It is found that this coupling is essential to the maintainability of the system. Some couplings are obvious (such as the Switch example). But more coupling is implicit, not so obvious, and for a long time, it is not a problem, but once it becomes a problem, it will be a very headache.

A real typical case is the java logger. In the early years, everyone used commons-logging and log4j without any problems. However, a hidden danger is growing here-that is the strong coupling to the logger implementation.

When logback came out, things started to become complicated. When we wanted to replace a new logger vendor, in order to minimize code changes, we had to use various Bridges. In the end, no one could see the log code. Understand the code maze. The following picture shows the log framework dependency situation of an old business system that I spent a lot of effort to sort out.
image.png

Imagine if we could encounter the problems caused by this tight coupling at the beginning. Add a layer of abstraction and decoupling between the application and the logging framework. So many subsequent bridges and so many backward compatibility can save trouble. And what we have to do is actually very simple-just add an interface for decoupling (as shown in the figure below) :
image.png

To provide API to the outside world

JCP and JSR have been introduced above, you can read some JSR documents when you have time. Whether it is the successful JSR-221 (JDBC specification), JSR-315 (Servlet specification), or the failed JSR-94 (rule engine specification), etc. In essence, they are defining standards and formulating APIs. The content of the specification is abstract, and the form of its external release is all interfaces. It does not provide implementation, at most it will guide the implementation .

There is also the SDK of various open platforms that we usually use, or the two-party library of RPC in distributed services. The main component contained in it is also the interface. The implementation is not locally, but in the remote service provider.

The situation similar to this API is forcing developers to think clearly about the interface. I think this is a beautiful "side effect" of the microservice architecture. Once the various coupled business modules in the original monolithic application are service-oriented, they naturally become "interface-oriented".

Implement interface-oriented through dependency inversion (How)

Regarding dependency inversion, I have written many articles before to explain its importance. In fact, the Switch case I gave above about the expansion requirements, the logger case about decoupling. The methodology behind it to solve the problem is dependent on inversion .

image.png
As shown in the figure above, the principle of dependency inversion mainly stipulates two things:

  1. High-level modules should not rely on low-level modules, both should rely on abstraction (as shown in Figure 2 above)
  2. Abstraction should not depend on details, and details should depend on abstraction.

Let's look back, whether it is the design of the Switch or the design of the abstract Logger, are they all following the two definitions above?

In fact, DIP (dependency inversion principle) is not only useful in object design, but also in module design. It is also very useful in architecture design. For example, when I was doing COLA 1.0, like most application architecture hierarchical design, the Domain layer can rely on the Infrastructure layer by default.
image.png

This seemingly "innocent and elegant" design actually still has a lot of hidden dangers. It also violates my original intention to separate business complexity from technical complexity. When the business becomes more complex, this "lazy" "The behavior is likely to cause the Domain layer to degenerate into a Big mud ball. Therefore, in COLA 2.0 , I decided to use DIP to reverse the relationship between the Domain layer and the Infrastructure layer, and finally formed the following structure:
image.png

The advantage of this is that the Domain layer will become more pure, and its benefits are reflected in the following three points:

1. Decoupling: The Domain layer completely gets rid of the dependence on technical details (and the complexity brought by technical details), and only needs to handle business logic with peace of mind.

2. Parallel development: As long as the interface is agreed upon between Domain and Infrastructure, two students can write code for Domain and Infrastructure in parallel.

3. Testability: The domains without any dependencies are all POJO classes. Unit testing will become very convenient and very suitable for TDD development.

When is the interface not needed

"Although strong wine is good, don't be greedy!"

Like many other software principles, interface-oriented is good, but it shouldn't be a killer weapon that is used indiscriminately regardless of background or occasion. Because of the excessive use of interfaces, the introduction of too many indirect layers will also bring some unnecessary complexity.

For example, I have seen that the internal module design of some applications is too "flexible", adding a layer of Interface to all DAOs and Converters, but the actual situation is that the possibility of replacing the implementation of DAO and Converter in the application is extremely low. . Similar to this, the pretending interface is a dispensable chicken bone (a lower grade than chicken ribs).

As Joshua Bloch, author of "Effective Java", said:

"Like most disciplines, learning the art of programming must first learn the basic rules, and then you can know when to break these rules."

Guess you like

Origin blog.csdn.net/significantfrank/article/details/102877680