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

Finding a Seam0:00

When we talk about changing existing legacy code, recommended steps are to first write the characterization tests, then slightly change the existing code to be able to add the functionality following better design practices, and the third one to add new code for the new functionality and just call it from the existing code. Since we already wrote the characterization tests, I will just open my Payment class, and the next step would be to identify a point in the code where we can introduce the new functionality with minimal impact on the existing code. This is what we call a seam, and in short, seams are like soft spots in our code where changes can be made safely and easily. The request we got is to implement a new wise payment option, and what we need to determine

Refactoring to Strategy0:48

changes can be made safely and easily. The request we got is to implement a new wisepay payment option, and what we need to determine right now is where in the Payment class we can introduce this new payment option. A good place to start is the method responsible for processing the payment. So here we have this block where we decide on what method to call depending on the payment type, and we want to follow solid design principles for all of the new code that we'll add. So we'll need to refactor this method slightly to make it easier to add new payment types. One way to do this is by using a strategy pattern, something that I explored a bit while I did that scratch refactoring. So let's follow that approach to restructure this better.

I did that scratch refactoring. So let's follow that approach to restructure this better. What I'm going to do here, I'll add a property, paymentProcessors, and then in the constructor, I will do something like this. Let's just create constants for these numbers because it will make things cleaner. So type PayPal is one. I've added the constants for all types, and now let's reference them instead. So the idea is to follow the solid structure we have established earlier. All of these will be new classes that I'm going to create, but before we go ahead and create the classes, we need a contract to define the functionality that they're going.

Defining Processor Interface2:09

All of these will be new classes that I'm going to create, but before we go ahead and create the classes, we need a contract to define the functionality that they're going to implement. So an interface, PaymentProcessor, that will outline all of the methods that we need to implement so I can easily abstract the functionality later. So an interface with a method processPayment, and now I will create all of my classes, and I'll use the services structure that we have established earlier. So we have PaymentOption, now I'll add here PaymentProcessors, and in PaymentProcessors, I want to add all of the existing WireProcessor, then I'll do Payoneer, then Paypal. All of these will be classes that implement the same interface.

I want to add all of the existing WireProcessor, then I'll do Payoneer, then Paypal. All of these will be classes that implement the same PaymentInterface. I need to reference the interface here. Okay. Now, I need a single method called processPayment. All of these classes should have the same. Let's just plan that and assume that the methods are implemented. We'll focus on one, everything will follow. Back to my Payment class, let's look at the WirePayment because we already have a specific implementation of it.

Extracting Wire Logic4:15

Back to my Payment class, let's look at the WirePayment because we already have a specific implementation of it. So what should happen here, I want to remove this piece of the code and move it to my new class because that's where I want to handle everything, though it should receive paymentDetails. PaymentDetails and amount, that is how we should define our method. Now, after adding all of the classes, I want to reference them here to be able to use them in a Payment class. So services, paymentProcessors, and I'll list all the classes here. And now here, what I want to do is, instead of having this switch block, I will check

So services, payment processors, and I'll list all the classes here. And now here, what I want to do is, instead of having this switch block, I will check if paymentProcessor is not defined for this paymentType, then return an invalid. Otherwise, get that processor, which is already defined, and call processPayment on that object. And I'll remove this piece of the code. Now, we have refactored the processPayment method to work in a better way, which is much more scalable, much easier to maintain, to extend. And what's best, this allows us to add a new functionality now following these best practices.

Running Tests After Refactor5:45

And what's best, this allows us to add a new functionality now following these best practices. So we can create a separate class for the wires payment, we can follow everything that we discussed in terms of the solid design principles and writing code that's easy to extend. Before we go and do that, let's just go back to our tests and see if it all works as expected, if we haven't introduced some problem with all of the refactoring that we made. It can't find our wise class because we haven't created it yet, so let's do that. Okay, now we'll just copy all of this and add it here. Run it again.

Okay, now we'll just copy all of this and add it here. Run it again. Okay, we have two passed, and the past ones are for validating the transaction, validating the processing payment, and we have three failed. These are specific to the wire transfer, and I guess the reason for this is because we left this method empty. When working with legacy code, oftentimes you see methods like this referenced throughout the code, outside of the Payment class that you think is the only one that handles this. So just to play it safe, I don't want to remove this immediately, not until I have absolutely validated that it is not referenced elsewhere and it won't introduce any potential

So just to play it safe, I don't want to remove this immediately, not until I have absolutely validated that it is not referenced elsewhere and it won't introduce any potential bugs or missing features later on. So what I want to do here is just call my WireProcessor, PaymentProcessor, wire, and then process the payment, and just add a return over here so that my code will still behave in the same way. I'm not losing functionality within this method, and I can safely refactor only this part until I'm ready to touch all of the other occurrences outside of this class. Okay, they all passed. By refactoring the Payment class to use the strategy pattern, which acts as a seam, we've

Explaining OCP Benefits7:50

Okay, they all passed. By refactoring the Payment class to use the strategy pattern, which acts as a seam, we've made it easier to extend the class with new payment types. This approach adheres to the open-close principle, where the class is open for extension but closed for modification. Adding a new payment option likewise becomes a matter of implementing a new Processor class and adding it to the list of processors without the need to alter the existing processPayments method or any other parts of the Payment class. This makes our code much more resilient and much easier to maintain and extend on the long run.

This makes our code much more resilient and much easier to maintain and extend on the long run.

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