تماشای این درس نیاز به اشتراک حرفه‌ای دارد.

Open-Closed Principle Overview0:00

There's still one more step to take, and that's following the open-close principle, the O in SOLID. The open-close principle is confusing to a lot of people, and I know the name is not doing it any favor. It states that your code should be open for extension, but closed for modification. It means that we should be able to extend our app with additional functionality, without having to constantly change existing code. Think of an open-source package as an example. You install the package, and it's housed in your PandwaRF folder, just like Laravel's framework.

Extending Packages via Inheritance0:35

You install the package, and it's housed in your PandwaRF folder, just like Laravel's framework. You never touch there. Instead, you just reference it in your code, add code on top of it, and you just make sure it works in the context of your own project. I have the AuthenticateMiddleware class opened here, and this is just to understand how the open-close principle would work in practice, with a reference to a package or open-source library. So, if we scroll down a bit here, you'll notice that the rhetoric2 method is not implemented. It's left for us to implement.

So, if we scroll down a bit here, you'll notice that the rhetoric2 method is not implemented. It's left for us to implement. Now, when we want to implement the authentication in our app, we're not coming to this file and editing the method directly over here. Instead, we're implementing the functionality on top of the package that we're using. We have a new class over here, Authenticate, which extends the same middleware, and this is where we implement the rhetoric2 method. This is the open-close principle in practice. To implement new functionality, you don't change existing code, you add code on top of it.

To implement new functionality, you don't change existing code, you add code on top of it. Open-source libraries, like the Authenticate package over here, are used by many people, but changed by few, and changed very rarely. This makes them so well-tested, and over time, they become so stable. It's the case for most of the open-source code. What we want to achieve in our code is the same. We want to make our software resilient, so that we don't change the core of our system every time we add a new functionality. We add new functionality by adding new code, without touching the core of the system.

Applying OCP to Payments2:07

every time we add a new functionality. We add new functionality by adding new code, without touching the core of the system. Back to our example now. Remember how our controller had a bit too much understanding of the specific payment classes that we use, like Wire and Payoneer, and also how to route the execution depending on the input time, what we're doing over here. It's time to address that by making our code open for extension, but closed for modification. The easiest way to do this is to use Laravel's built-in functionality. And to do that, we'll open the service provider that we were working with earlier. So this one.

Binding Interface in Service Provider2:43

And to do that, we'll open the ServiceProvider that we were working with earlier. So this one. And let's quickly just combine this in a single line, so that it doesn't become unreadable. Now, similarly to what we did with the repository classes, we want to bind the concrete implementation with this PaymentOption interface that we have defined for all of the payment options to implement. The difference between this and the previous implementation is that now we're depending on a UserInput. So we need to take that into account when we bind the implementation. Let's have a bind.

So we need to take that into account when we bind the implementation. Let's have a bind. I'll leave my PaymentOption class here, or interface. And then I'll need a closure for handling this. Now in this closure, I will need to include the conditions that I depend on for creating the proper instance. The first one will be if the inputPaymentType equals wire, then I'll need to return an instance of the Wire class. Else, if the request, the inputPaymentType is Payoneer, add makePayoneerClient, otherwise let's just throw an exception.

Refactoring Controller to Interface4:09

Else, if the request, the input payment type is Payoneer, add MakePayoneerClient, otherwise let's just throw an exception. Now we'll need to go back to our controller, find all of the dependencies on our concrete classes and replace them with the abstraction or the payment option interface. Since all of the methods here in the controller depend on this payment option, I'm again going to reference it here or inject it in the constructor. So payment option, payment option. Now instead of this, I will just add the payment option. And now both in the show and store methods, I can remove this part of the logic where the controller is deciding which instance to create.

And now both in the show and store methods, I can remove this part of the logic where the controller is deciding which instance to create. And I can just call the payment option that has been resolved in the background, depending on the user input. I'll do the same here in the store method. And let's see if it works. Okay, let's refresh the page. Transfer options are displayed properly. Wire transfer fields are dynamically pulled and displayed. Seems like it's all working and the functionality hasn't changed, but the structure in the background.

Adding a New Payment Option5:30

Wire transfer fields are dynamically pulled and displayed. Seems like it's all working and the functionality hasn't changed, but the structure in the background has. And now we have a more extensible structure that is easier to scale. Let's now see what it takes to add a new payment option. Let's say I want to add Wwise as a payment option. So that would be a new provider. I'm going to create another service class under the payment option services, name it Wwise. So I added my class, it implements the PaymentOption, it should have a Wwise repository,

Wwise. So I added my class, it implements the payment option, it should have a Wwise repository, so the next step would be to create that. Let's go to the repositories folder. So this is the repository class, assuming that all of the fields are implemented. I'll need a repository for the same class, so going to the contracts now. Let's just copy the Payoneer one. And we have the repository interface. Note what we did. We created a repository interface for the Wwise payment.

Note what we did. We created a repository interface for the Wwise payment. We implemented that interface into our WwiseRepository, so now our WwiseRepository is handling all of the data that is related to Wwise as a payment option. And then we added the service class, Wwise, that implements our payment option interface and has all of the fields for dynamically displaying the details. Now the only thing left to be able to automatically implement this new payment method is to go to our service provider, add another condition for Wwise as a payment option here, resolve it to the Wwise class, include the dependencies here, and let's see if this works. So we see Wwise is another payment option listed here.

it to the Wwise class, include the dependencies here, and let's see if this works. So we see Wwise is another payment option listed here. If I click it, the fields will be dynamically generated. That's it. We implement a new functionality by adding new code instead of changing the existing one. We don't need to touch the controller. We don't need to touch current services, repositories, views. It all works out of the box simply by adding new code.

It all works out of the box simply by adding new code.

دوست دارید گاهی خبرهای Laracasts را ایمیل کنیم؟