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

Motivating Open/Closed1:35

when you're hesitant to touch a particular piece of code because you don't want it to break. Or imagine the situations when we keep editing the same code and that leads to code rot. What if we could instead prevent all of that? Modify the behavior, but do it through extension instead. Now the truth is, you maybe have read books and articles on this subject, page after page of theory. But the easiest way is really to dig in to an example. So let me show you the demo that really made things click for me. It's not the most real world, but at the end of the lesson, we'll dig into something a bit more concrete. Now imagine that we have a class called Square. Your boss wants you to prepare a shape. And this should offer public properties for the width as well as the height. Now, naturally, when we instantiate this class, we should be able to set the width and the height. So

Building Area Calculator2:17

this should offer public properties for the width as well as the height. Now, naturally, when we instantiate this class, we should be able to set the width and the height. So let's do that now. Let's set our constructor and add those two. Now at this point, everything is looking great. We just need to add my namespace to fit my directory tree. But next, your boss tells you, I need some way to calculate the area of this square. So you think, okay, let's follow the single responsibility principle and create a class that is dedicated to calculating area. Let's do that now. Within my directory, let's create a new file called areaCalculator.php, set up the namespace, and add our class, areaCalculator. Now we will add our method. We'll call it calculate. Now what this needs to do is accept an array of squares and determine what the total area is. So how exactly might we do that? Well, we're going to accept the squares.

calculate. Now what this needs to do is accept an array of squares and determine what the total area is. So how exactly might we do that? Well, we're going to accept the squares. So to start, we just write it out just like this, squares. And then we do a foreach. So foreach squares as square, all pretty basic stuff, right? And then we need to add to a totalArea. So a couple ways we could do this. One, we could set this to zero. And then for each one, we could say area plus equals square, grab the width, times square height. That is the formula, so to speak, for the area of a square. width times height, or actually for a square, you could do width squared. But this will work. Finally, we just need to return area. Okay, so hopefully still this is really basic stuff. An alternative way if you wanted would be to get rid of this and then say add these to an array. And then

Adding Circle Breaks Design3:51

to return area. Okay, so hopefully still this is really basic stuff. An alternative way if you wanted would be to get rid of this and then say add these to an array. And then finally, you could return array_sum area. That would work too. Now at this point, everything's going perfectly. The code works, no issues whatsoever. But then your boss tells you, we also need circles. So you think, okay, well, let's see how this works. We will add circle.php. And just to save some time, copy and paste some of this in. So we begin with Circle. And now a Circle doesn't have a width and a height, it has a radius, like so. And then we can just update these like so. All right, so that was easy enough. But now, as you're probably expecting, the boss says, okay, well, we also need to allow support for figuring out the area of a circle. So you come back to area calculator and you think,

you're probably expecting, the boss says, okay, well, we also need to allow support for figuring out the area of a circle. So you come back to areaCalculator and you think, oh, crap, I called everything square. I didn't think about the possibility that I would need to calculate area for anything other than a square. So I'm going to have to edit the original code, meaning we've broken the open closed principle. But let's continue down that original path. So you think, okay, well, maybe we should change squares to shapes. That's smarter. And then we can just update square to shape. Okay, so that's a little more flexible, right? But don't forget, the formula for area for a square versus a circle is different. For a circle, we do radius squared times pi. You probably remember that from geometry. So now, how do we do this? And this is a dead ringer that you are breaking the open closed principle. When you do

we have broken that open/closed principle. This class was open for modification when it should have been closed. However, you might be thinking, you know what, Jeff, it's not a big deal. Add a quick if check and you're done. Get on with your day. Don't worry about these principles. But what about down the line when your boss says, well, you probably anticipated this, but now we need a Triangle class and the ability to calculate area of a triangle as well. Well, in these cases, do you really think we should do something like this again? If it is a Shape Triangle, do we really need to do that? Do we need to update this code every single time we make a change? This is the sort of thing that leads to code rot, or at the very least, it's the sort of thing that introduces breakage. Now, though, what exactly is the solution? How can we extend this behavior while keeping the class closed for modification? Well, here's what Uncle Bob says

Introducing Shape Interface6:56

thing that introduces breakage. Now, though, what exactly is the solution? How can we extend this behavior while keeping the class closed for modification? Well, here's what Uncle Bob says on the subject, and I think it's worded beautifully. When you have a module that you want to extend without modifying, and that's what we want to do, separate the extensible behavior behind an interface and then flip the dependencies around. Now, I know that's a little wordy, but write that down because it gets to the core of what we want to do here. So let's tackle that first part. Separate the extensible behavior behind an interface. That means the first step might be to add an interface. Let's call this ShapeInterface. As always, we need to set the namespace, and now I will set up my contract, and this is how I like to think of interfaces. A contract that any implementation or any concrete class or any derivative must adhere to. In our case,

and now I will set up my contract, and this is how I like to think of interfaces. A contract that any implementation or any concrete class or any derivative must adhere to. In our case, we just need some way to determine what the area is. So that's it. A very simple interface. So let's go back to what Uncle Bob says. Separate the extensible behavior behind an interface. Here is the interface, but now we need to implement them for each concrete class. Class Square implements Shape. So now if I save, my IDE is going to let me know, hey, you need to implement this method according to the contract. So now how do we determine the area of a Square? Well, we return this width times this height. Next, we can do the exact same thing for the circle. Implements Shape. Now, once again, let's add the method stub, and this time the area of a circle can be calculated with this radius times this radius times pi. Okay, so step one is done.

Refactoring to Polymorphism8:37

Implements Shape. Now, once again, let's add the method stub, and this time the area of a circle can be calculated with this radius times this radius times pi. Okay, so step one is done. Separate the extensible behavior behind an interface. We have done that. Next, we need to flip the dependencies around. So if I go back to AreaCalculator, rather than doing specific checks for concrete classes, we can remove all of this. And now we can simply change it to area equals shape. And because we have coded to an interface, we can trust that there is an area method on the Circle, as well as the Square, as well as any other shape that your boss throws at you. So now you can just say shape->area(), and now we have the exact same thing, at which point we can remove this entirely. Now think about this. Because we've coded our app in this way, when our boss says, hey, we need a Triangle and the ability to calculate the area of a Triangle as well,

can remove this entirely. Now think about this. Because we've coded our app in this way, when our boss says, hey, we need a Triangle and the ability to calculate the area of a Triangle as well, we never have to update this code. We simply create the Triangle class, make sure that it implements the common interface. We set the area method on Triangle, along with the formula for calculating the area of a Triangle, and then we pass that in to AreaCalculator. So notice how these design principles sort of have each other's backs. You'll find a lot of similarities between them. In our case, because we coded to a common interface, we were able to flip the dependencies around. So hopefully that example made some things click for you. It definitely did for me when I first learned about it. Now, if you're thinking about more concrete implementations, let me just give you a basic idea. Imagine that you have a class maybe for Checkout. Maybe you buy some item,

Checkout Payment Example10:13

first learned about it. Now, if you're thinking about more concrete implementations, let me just give you a basic idea. Imagine that you have a class maybe for Checkout. Maybe you buy some item, and you have a class for the checkout process. And maybe you have a method called begin, and that accepts maybe your receipt. How about something like that? So maybe now you need to accept payment in some form. So initially you do something like this, acceptCash, and then you pass through the receipt, right? So you have a method called acceptCash that accepts the receipt, and then you, in whatever way you could possibly accept cash online, you do it right here. Accept the cash. Okay, a little silly, but you get the idea. Now, what about the situations though when you don't want to accept cash? What if you instead want to accept a credit card, or a coupon, or maybe even something like PayPal? Well, the way we structured this code, it was designed to accept cash. And when you hear

cash? What if you instead want to accept a credit card, or a coupon, or maybe even something like PayPal? Well, the way we structured this code, it was designed to accept cash. And when you hear people say, oh, it wasn't really designed that way, they're sort of saying, I didn't account for the open-closed principle. Right now, if we do want to accept credit cards, for example, we would have to modify the original source, and that breaks the open-closed principle. So instead, let's go back to the advice Uncle Bob gives. When you have a class or method that you want to extend without modifying it, separate the extensible behavior behind an interface, and then flip the dependencies. So that would mean we'd create an interface, and let's just do everything in here to keep it all on one page. Maybe we would call this PaymentMethodInterface, and within here we'd simply have a public function called acceptPayment, and that should accept the receipt. Now, rather than

all on one page. Maybe we would call this PaymentMethodInterface, and within here we'd simply have a public function called acceptPayment, and that should accept the receipt. Now, rather than hard-coding methods like acceptCash or doing an if or switch statement to acceptCredit, acceptVoucher, acceptCoupon, all of that stuff doesn't need to be in here. Instead, like we noted, we extract all of that behavior and implement a common interface. So now we have a class called CashPaymentMethod, and remember that would need to implement PaymentMethodInterface. So now we add acceptPayment, that receives the receipt, and once again within here you write whatever logic is required to accept the payment. Now, if we come back to Checkout, when we call the begin method, rather than hard-coding in something like CashPaymentMethod or Bitcoin, we don't reference a concrete class. Instead, once again, flip the dependencies and instead code to an interface.

rather than hard-coding in something like cashPayment method or Bitcoin, we don't reference a concrete class. Instead, once again, flip the dependencies and instead code to an interface. And you'll begin hearing that phrase more and more as we dig into these other design principles, code to an interface. Like I said earlier in this video, all of these various principles have each other's backs. So now we will say payment, and rather than calling an acceptCash or some specific method on checkout, we rely on the common interface and simply call payment->acceptPayment. So now checkout has no clue how acceptPayment is actually going to work, it just knows that an acceptPayment method is called, and that will do its job. And this truth is what we refer to as polymorphism, the ability to have different behavior while still sharing a common interface. All right, so with that, we are finished with this lesson.

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