Builder Pattern (Gang of Four Design Patterns Series)

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
hey everyone wes here welcome back to the gang of four design patterns series if you're just joining me for the first time in this series welcome i am covering every design pattern that is explored in the famous gang of four design patterns book in c-sharp in this video we're going to pick out where we left off among the creational patterns and take a look at the builder pattern so if you're not already familiar with the factory method pattern i would recommend going back and checking out the first video in this series to get a good sense for some of the benefits of the factory method pattern and some of the particular use cases for that pattern as the factory method is really probably the fundamental design pattern in terms of creational design patterns the builder pattern is a bit more complex and the trade-off that we get with that complexity is that we have a really good pattern for cases when we need to construct complex objects and it might be beneficial to do that in a step-by-step fashion so in this video we're going to take a look at two approaches to the builder pattern one the traditional gang of four approach and another a sort of fluent builder pattern approach one that i found useful in practice quite often when trying to create complex objects in unit testing scenarios so i want to demonstrate both of those approaches as usual all the code for this is in the description below and if you get anything out of the series i'd really appreciate it if you liked the video and be sure to subscribe to find out when the next design pattern video is released so with that let's go ahead and dive in and take a look at when and why we might use the builder pattern in c sharp alright so let's dive in and take a look at our second design pattern of the series this will be our builder pattern if you aren't already pretty familiar with the factory method pattern i would recommend checking that out as well as that's really probably i would say the fundamental creational design pattern that's really worth knowing and some of the other creational patterns that we'll look at including the builder pattern are for generally more specific types of use cases so it's going to have all of the same benefits that the factory method pattern has but it'll be a bit more complex and the complexity there will be due to the specific type of use case in which we might want to actually work with this pattern and that's generally going to be the case for the other creational patterns as well so be sure to have at least a pretty good understanding of some of the benefits of the factory method pattern and that will help you make a decision about whether or not something like the builder pattern is also applicable for your particular use case so let's take a look at what the definition of a builder pattern is so it's a creational pattern like the factory method pattern that separates the flexible construction of complex objects from their representation so this already sounds pretty similar to the factory method in that it's a creational pattern and we have some separation of concerns here between the construction or creation of some object and in this case it's representation or perhaps where that object is used one of the key points of the builder pattern and where it really starts to become useful is when we need to work with the construction of complex objects so if you've ever worked with an object that required a lot of state at the time of its creation and you've worked with say some constructor that has a bunch of parameters to set state on that object then you know generally how sort of messy and unwieldy those types of objects can be to construct generally it's kind of a smell to just continue to add parameters to a method including a constructor because it starts to get hard to discern like say even just like the order of the parameters that go into into the constructor generally we want some way to be able to set state without worrying about sort of front loading a whole bunch of state through a constructor for creating a new object and we'll take a look at how the builder pattern helps us with that particular smell but it's also the case that as object construction gets kind of complex we want some way to be able to reason about it without trying to understand every piece of state that goes into that object up front so the builder pattern will help us with that and so by separating the code here that handles the construction of a complex object in different ways and in a step-by-step fashion we can make complex object construction more extensible so as you might imagine when object construction gets really complex because we have some complex object to build we know the number of different states that that object could take could explode and there may be certain states which are useful for us over and over again in which we want to just be able to quickly construct them in that state and so maybe we have different ways of creating different instances of a type that are loaded with different configurations we can take a look at how we might use the builder pattern in that regard as well and then we'll also look at how we can do this in a step-by-step fashion again so that maybe we don't have to just load in all of the state all at once in a constructor and instead do this in a step-by-step way we're also going to look at a slight variation that's not explicitly part of the gang of four patterns called the fluent builder and so i have a second example to that as well which provides us with a fluent step-by-step syntax which can be really useful in a lot of cases particularly for things like creating fake objects to work with in unit tests and in plenty of other scenarios so let's take a look at the sort of standard uml diagram for a builder pattern this is in particular the an adaptation of the gang of four pattern for the builder pattern diagram for the pattern rather so what we have here is a director instance which is going to have a an object which implements ibuilds products in this case so this particular syntax here is what the gengafor would call an aggregate and today in our sort of modern lingo we would generally just call this composition the gang of four makes a distinction between two types of composition it calls this particular relationship an aggregation there's another type of composition they call acquaintance the difference there is really what they call an aggregate and what i would call composition is that the director has an instance of something in this case and that it is the lifetime scope of the instance that it has is is tied to the lifetime scope of the object that has it so when director goes out of scope and is destructed for instance then concrete builder in this case whatever is implementing this interface goes with it so this is just a standard director has a something that implements this interface whereas the acquaintance concept is a looser relationship in which case some object might only as they say know about another type of object and can in some other way indirectly interact with it but the two lifetime scopes of those objects are independent in this case we just have standard composition where a director has something that implements ibuilds products here okay so that's a long-winded explanation but since we're kind of sticking to the gang of four book i wanted to mention their their terminology for things so we have this director object it has something that implements ibuilds products and that interface is implemented by a concrete builder which ultimately instantiates our product so our product is the complex thing that we want to be able to build but we don't want our director or wherever our director is used in the client to have to say new product with a whole bunch of parameters in the constructor and uh use this big unwieldy object that would get nude up in a particular place either in the director or at some level even higher and so different implementations of ibuilds products will produce different configurations of a particular product and the director can call construct to invoke the concrete builder in different ways to actually make that happen so the concrete builder is going to say like step by step build part a of product build part b of product and then ultimately something like create or make product and it will sort of materialize that new instance of product for us to use in the director so it's important here also to note that the director works with interfaces so that any number of different concrete implementations of ibuilds products could be used to produce different outcomes different types of or different configurations of a product so now we'll look at the diagram in a real world use case as it's used in the code so we're going to be dealing with a purchase order builder and i chose purchase orders because generally they're very common relatively complex objects that could potentially hold a lot of state and the state of which is often known at the time that the object is needed and so it seems like a relevant use case for a builder pattern so our director in this case is actually our purchase order processor and this class can do stuff like generate weekly purchase orders it can save a purchase word to the database maybe it can send an email to a vendor so it has this relationship where it has a implementation of something that builds purchase orders which is our ibuilds purchase orders and this can be implemented in our case by either a bakery po builder or a coffee po builder and so we can imagine that the bakery po builder builds a purchase order by following all of the conventions that our bakery suppliers require of us so maybe they have a particular type of poid that they want to see on their end they certainly have different line items that might need to get constructed on their standard weekly po than our coffee vendors and so we have sort of implemented the single responsibility principle here which is to say that any given class should have one reason to change and our bakery po builder changes based on what our bakery vendors require of their of the pos that we generate and likewise our coffee po builder would change only for the pos that we build for our coffee vendors however both of these implement the same interface and so we could very easily generate new concrete implementations of ibuild purchase orders say for some other type of vendor we might have that we might sell things at our cafe or our bakery so like a t-shirt vendor or something like that and nonetheless the the director in this case could still use the same method to generate a weekly purchase order and save that purchase order to the database and since it's working with interfaces in general the all we would need to do is to create a t-shirt purchase order builder that would implement the same interface and produce another purchase order object which is just configured differently for that particular use case okay so i think that's enough diagramming now we should dive right into some code and take a look at the implementation of this and we'll take a look at actually two implementations one which is this the sort of gang of four design pattern with the director and then we'll also look at another variation that i found really useful in practice called the fluent po builder or the fluent builder so let's take a look okay so here we are in the solution and if you want to take a look along with me here the bakery purchase order system which is in our example programs creational examples namespace has a console app in it and so that's what we'll be looking at here first just to kind of see how things work there's some comments here feel free to check them out if you're interested in the details but basically i'm just going to cover this in more of a natural way of speaking about it so we're going to look at a first approach here which is what we just reviewed a typical sort of gang of four builder pattern in which we're going to use a director and then some particular builder so the main method here this is the uh this is sort of the root of our application it's the entry point and so in terms of dependencies this is where they're going to be created in dependency injection terms this would be like our composition root or our container so we're going to have a logger that's used throughout the app and a database that's used throughout the app and that's going to get injected here manually if you will just through constructors to the various objects so these are our third-party dependencies we have some concrete builders here as we saw we're going to have a bakery po builder and a coffee po builder and then a director class which is our purchase order processor all the po processor takes when instantiated are our third-party dependency so our logger and our database but then the po processor has on it a method generate weekly purchase order that takes as a single argument anything that implements ibuild purchase orders so just to step into one of these methods really quickly our bakery po builder implements ibuilds purchase orders the only thing that we need on anything that implements side builds purchase orders are methods for the various steps of creating some purchase order object so as you can see we can do things like set the id set the company the address the date the supplier and the items and these are all void methods which are actually just changing the state of some purchase order object so if we take a look at the bakery purchase order what we have is we have some backing fields here for the state that's going to be that's going to be contained by the purchase order ultimately and each time we call one of these methods it is modifying or mutating these backing fields which are private to the po builder itself so we can set all of our state using this builder class and then we have this very important method which ultimately calls each of those methods and returns that purchase order instance so this is sort of the helper method here if we wanted to think about this a little bit differently we could of course call these methods from some other place and then create a method here which sets the backing fields later instead of encapsulating everything here in one method but for the simple purposes of this example we build a purchase order what this does is it actually calls these methods for us and then returns us a po and that's really all there is to the purchase order builder itself here we'll talk about what this static implicit operator is a bit later but that'll become relevant with affluent builder so as far as sort of gang of four approaches concerned this is an implementation of ibuild's purchase orders we take a look at another implementation we can head back here we can see our coffee purchase order builder and some of the things that it might do differently is it might set the id differently here for instance so here we have coffee underscore date with this syntax whereas in the case of the bakery purchase order we have a slightly different syntax with bakery underscore and so the coffee builder could do a number of things differently and in fact it does it's still an implementation of i build purchase orders but it sets its backing fields a bit differently according to the particular weekly pos that we generate for coffee likewise it has a build purchase order method which returns that fully constructed purchase order okay so we have these two builders we have our po processor which is our director class and we call generate weekly purchase order once for each of the particular builder instances that we've created or implementations so let's take a look at what happens when we call that this takes in anything that implements ibuild's purchase orders so through this approach using dynamic polymorphism we can of course make this our code extensible because we could implement ibuild's purchase orders into the future in many different ways without actually changing what the behavior of generate weekly purchase order is or changing the code because its behavior is just to generate a weekly purchase order for some type of thing that implements this interface so it calls po builder dot build purchase order and we know that that's going to return a purchase order uh and in our case we can implement that as either a po for a bakery or po for the coffee and then we call print purchase order and we save it to the database so our director here has a little bit of additional responsibility just to show you that this responsibility exists separately from the construction and use of these objects we can save this purchase order to a database using a dependency that was injected into the po processor we can log some things out here and then ultimately we can print the purchase order and everything is just parameterized here with the order id and the company name and all of the state that complex state that's on our purchase order object okay so what happens when we do this is when we call this generate weekly purchase order as we saw we build it using the particular implementation of ibuild's purchase orders and we print it and then we save it to the database so the benefits that we see here just to recap are that the director class here can take in anything that implements ibuild's purchase orders in our case we've created implementations for bakery pos and coffee pos but into the future as we have new suppliers that require some weekly pos to be generated we could add those classes to our solution and then call the same business logic that generates weekly purchase orders for any implementation for those new suppliers furthermore the purchase orders since they're fairly complex objects themselves we can encapsulate all of that logic that's related to creating these pos and put in in individual methods the things that change state on such an object into some builder class here and keep things separate so in the event that pos even grow in complexity over time and have new fields that we need to set on them for whatever reason or perhaps fields get removed rather than look through everywhere where a constructor is used to build that object we would come to the builders themselves and actually add or remove methods here which keeps code a lot cleaner than trying to find every place a constructor was used and and modifying it there we have one place where the logic lives for creating these purchase orders and then in fact we would still be able to call build purchase order and it would produce whatever whatever the current shape and state of those purchaser objects is in any case okay so we have one more approach here and i wanted to bring this one up because it's actually something that i've used a lot when building automation test framework code and this is called the fluent builder approach and it's not explicitly part of what we just looked at although the general concept of creating an object step by step is still the same here we don't necessarily have a director class but instead what we want is some way to use a fluent syntax which sort of reads very naturally for creating an object in step-by-step fashion and so for that i've created a another interface called i fluent purchase order builder and this is pretty interesting and a little bit tricky if you're looking at it for the first time i would say but once you sort of get the hang of it and see what's happening here i think it's pretty clever and it can be useful for use cases for things like i would say creating fake objects for unit testing for instance so this interface defines the same types of methods that we saw on the interfaces for the other builders except this is actually returning the same interface itself so when we call with id we're actually going to return an eye fluent purchase order builder something that implements that interface so rather than be a void method that changes some state on some object this is going to actually return an instance of something that implements this interface itself and then ultimately still builds a purchase order for us so let's see how this is actually used so this gets implemented in a concrete class fluent purchase order builder it still has the same backing fields that we saw in our other purchase order builders except rather than have those void methods as was mentioned we're actually going to return this so we're returning the same instance of the thing that we're working with here so we set the id based on some parameter that we pass the method for with id and then we just return that same object this is actually going to let us do method chaining against the any particular instance of a fluent purchase order builder so we're going to be able to set the id then we can chain in like the request date the address the company the line items and then ultimately we still build a purchase order if we like but this is where this sort of fancy implicit operator method comes into play and what this is is it's a way to tell the compiler that when i have an instance of a fluent purchase order builder we can treat this as a purchase order i just need to tell you explicitly how to convert it and by telling it how to convert it here then the compiler can implicitly treat any fluent purchase order builder as a purchase order so what this allows us to do is to use the builder and then without explicitly ourselves calling build purchase order we can just use that builder instance as a purchase order itself so we just return builder that build purchase order that's going to return a new purchase order instance this new curly brace syntax is using target type so this is a c sharp 9 feature i believe but what we're going to do is return a new instance of this purchase order and then we use all those backing fields that we've been setting as the values on the purchase order instance itself okay so what would this look like in use well this is part two the second approach this is our custom builder using affluent syntax and so the director is not in play here we're just going to use the fluent builder directly in our client and so we can see that we're going to new up a fluent purchase order builder instance this is that builder that we were just looking at we have some line items that we want to use and a new supplier that we want to use and then we can chain together those methods on that fluent purchase order builder to just say with id at address for company because each time we call one of these methods it's actually just returning the object the custom order that we're working with and so we're modifying the state each time we call one of these methods and we're returning it back to the caller here we can continue chaining for all of the various properties so this gives us a or backing fields in this case this gives us a nice fluent syntax to say things like an order with an id at an address for a company from a supplier etc and then we can actually just use this as if it were a purchase order rather than a fluent purchase order builder because of the fact that we have this implicit operator which does that conversion for us the compiler sees this and then anywhere we accept a purchase order instance the the compiler knows how to do that conversion because we've told it in that implicit operator method so then we can do the same types of things we were doing in our director class earlier we can save it to a database we can print the purchase order so with that let's go ahead and run the program and we can see it in action okay so here we are in the example program slash creation examples slash bakery purchase order system and we can just run dot net run here so we're going to see both examples run here we have a po for our bakery we'll see it connect to the database using that database dependency we'll generate the purchase order for the coffee and then our custom purchase order using the fluent builder will get executed in the third sort of example here so with that i hope this helps you understand the builder pattern a little bit better than perhaps you did before one of the things i want to mention here very quickly is a trade-off as you noticed especially if you've taken a look at the factory method example the builder pattern introduces a bit more complexity we have more objects to work with their orchestration is a little bit more involved than just simply setting up a factory method and so the trade-off that we have here is with the introduction of that complexity which will need to be maintained and understood by anyone working on it we now have the benefit of being able to relatively easily creating complex objects that whose state needs to be set in perhaps many different ways and so by being able to work with the construction of those objects at a high level of abstraction we have perhaps reduced complexity and readability of the higher level code but the implementation details here are a bit more complex than just a regular factory method so something to keep in mind something that the authors mention is that design patterns like the builder pattern often evolve out of simple use cases where a factory method is used initially and so if you find yourself in a situation where you're saying hey i need to i suddenly realized that i need to create this complex object we have this huge constructor and every time i need a new instance of something i'm filling in like you know six fields in this constructor or more maybe it's time to start thinking about abstracting that away into these objects that explicitly do the building of that that object that you want to use in a step-by-step way and then maybe even thinking about using this director to encapsulate all of that logic in one place that can be used by a client so that's the builder pattern in a nutshell two variations one strictly speaking the gang of four implementation and then a look at this fluent builder pattern thanks for watching and i'll see in the next design patterns video
Info
Channel: Wes Doyle
Views: 3,704
Rating: undefined out of 5
Keywords:
Id: _sa2WlAFWQo
Channel Id: undefined
Length: 29min 38sec (1778 seconds)
Published: Sun Jan 24 2021
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.