Interface-oriented programming is a design philosophy in coding that builds applications based on interfaces rather than fixed classes.
If you are a programmer, you may have heard of such statements, such as: interface-oriented programming, using abstract classes instead of fixed classes, etc.
These are all saying the same thing, write your application code so that it relies on abstract interfaces rather than concrete classes.
Why?
This was my exact reaction when I first heard this sentence. Why use interfaces instead of classes? Even if the interface is created, I need to create a class that implements the interface. Isn't this a waste of time?
of course not! !
The only constant in this world is change itself, that is to say, change is eternal.
As far as programming is concerned, there are no exceptions to this. As business needs change over time, our code must also change.
So the code must remain flexible.
Interface-oriented programming can make the code loosely coupled and flexible.
How to do it?
Observe the code below.
class Logger { public function log($content) { //日志保存到文件中. echo "Log to file"; } }
This is a simple class that logs to a file. We can call it in the controller.
class LogController extends Controller { public function log() { $logger = new Logger; $logger->log('Log this'); } }
But what should we do if we need to record logs to multiple places (such as databases, files, clouds, etc.).
We can then change the LogController and Logger classes to accommodate these changes.
class Logger { public function logToDb($content) { //将日志记录到 db. } public function logToFile($content) { //将日志保存到 file. } public function logToCloud($content) { //将日志存储到 cloud. } }
class LogController extends Controller { public function log() { $logger = new Logger; $target = config('log.target'); if ($target == 'db') { $logger->logToDb($content); } elseif ($target == 'file') { $logger->logToFile($content); } else { $logger->logToCloud($content); } } }
Now we can record different goals. But what if we want to add other targets (such as logs) to the redis server? Finally, we will modify both the Logger class and the LogController class.
As you can see, this quickly gets out of our hands and the code becomes messy. The Logger class quickly became a whole. This is a nightmare.
So we need to split things up. Following SOLID principles, we can move the responsibilities to the appropriate classes.
class DBLogger { public function log() { //将日志记录到 db } } class FileLogger { public function log() { //将日志保存到 file } } class CloudLogger { public function log() { //将日志存储到 cloud } }
And the controller is changed to:
class LogController extends Controller { public function log() { $target = config('log.target'); if ($target == 'db') { (new DBLogger)->log($content); } elseif ($target == 'file') { (new FileLogger)->log($content); } else { (new CloudLogger)->log($content); } } }
This is much better. Now if we want to add additional logging targets, we can create a new class and add it to the if-else in the Controller.
However, our controller is still responsible for selecting the logger. For the controller, there is no need to know about the different loggers and choose between them. It just requires a logger class with a log() method to log content.
Use interface
This situation is suitable for using interface. So what is an interface?
An interface is a description of the operations that an object can perform.
For our example, the controller only needs the logger class with the log() method. Therefore, our interface must describe that it must have a log() method.
interface Logger { public function log($content); }
As you can see, it only contains the function declaration and not its implementation, that's why it is called abstract.
When implementing an interface, the class that implements the interface must provide the implementation details of the abstract methods defined in the interface.
In our example, any class that implements the Logger interface must provide implementation details for the abstract method log().
We can then inject this interface in the controller.
class LogController extends Controller { public function log(Logger $logger) { $logger->log($content); } }
Now the controller no longer cares about the logger type passed to it. All it needs to know is that it must implement the Logger interface.
Therefore, we need to modify the Logger class to implement this interface.
class DBLogger implements Logger { public function log() { //将日志记录到 db } } class FileLogger implements Logger { public function log() { //将日志存储到 file } } class CloudLogger implements Logger { public function log() { //将日志保存到 cloud } }
Now we can add more loggers without touching existing code. All we have to do is create a new class that implements the Logger interface.
class RedisLogger implements Logger { public function log() { //将日志存储到 redis } }
Our code now seems to be flexible and low-coupled. We can change the implementation method at any time without changing the previous code.
Dependency Injection
When we are using the Laravel framework, we can use the service container to automatically register the implementation of the interface.
Because Laravel provides method injection out of the box, we only need to bind the interface and implementation.
First we need to create a logger configuration file. Just like this
<?php return [ 'default' => env('LOG_TARGET', 'file'), 'file' => [ 'class' => App\Log\FileLogger::class, ], 'db' => [ 'class' => App\Log\DBLogger::class, ], 'redis' => [ 'class' => App\Log\RedisLogger::class, ] ];
Then add the following code to the AppServiceProvider.php file under the app/Providers path
class AppServiceProvider extends ServiceProvider { public function register() { $default = config('log.default'); $logger = config("log.{$default}.class"); $this->app->bind( App\Contracts\Logger::class, // the logger interface $logger ); } }
The effect of this is to read the default logger from the logger.php configuration file, Then bind to the Logger interface. In this way, when we use the Logger interface, the container will parse and return the default Logger instance for us.
Because the default logger is specified using the env() assistant, we can use different loggers in different environments, such as file in the local environment and db in the production environment.
Summary
Using interfaces allows us to write low-coupling code and provide an abstraction layer. It allows us to change the implementation at any time. Therefore, try to implement the variable parts of your application in an interface-oriented manner as much as possible.
Recommended tutorial: "PHP Tutorial"
The above is the detailed content of Laravel interface-oriented programming (practice). For more information, please follow other related articles on the PHP Chinese website!