Immutable Design: Why You Should Care

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
Immutable design is not just a patch  to some traditional design approach,   like requesting a new object on every change.  That's stupid. That doesn't work. Immutable design is a separate strategy.  I will design a few models now,  both ways, and compare the results.  You might be astonished at how much  the immutable mindset differs from   the traditional object-oriented sense of  design, which is based on mutable objects.  Here is the simplest of all examples - a class  that models a book, only containing the title.  This class is immutable. Behind the scenes, it defines   a public read-only property with an init-only  setter, populated from the primary constructor.  You don't need these in C#. The record will do it all behind the scenes.  How about a mutable counterpart? In mutable classes, the state can change.  That rings some alarm bells, and  so we hold the state private.  Mutation is possible, and even welcome. But it must pass through the validation gate.  Initializing or mutating methods  also clean any incoming data.  There must be a valid state at the outset,  hence the need for a validating constructor.  But this is so embarrassing. The compiler cannot see I am   setting the field to a non-null value every time. Let me refactor it a bit to plug all the holes.  Both the primary constructor and the  property setter are now passing through   the same validation for the title. Immutable design removes a part   of this cumbersome code from your classes. A few lines less may not sound important, but   a bug should definitely attract your attention. The next request is to support keywords associated   with each book. With a caveat.  The request is also to include  words from the book's title.  Can you spot the bug already? I will add the same property   getter to the immutable class, and while I  do that, you will tell me: Where is the bug?  Well, it is definitely not  in this immutable class.  But the mutable variant is broken. I can change the title through the   public setter later, but the object would  retain keywords from the original title.  This happens when mutable state  components affect each other.  Managing mutable state suddenly  becomes complex and error-prone.  We do not observe those  difficulties in an immutable design.  The mutation causes the  recalculation of the entire state.  And it is only going to be worse. Adding new keywords at the back   of the mixed list of keywords will  certainly make this situation worse.  Let me fix this bug before implementing  the same feature in the immutable design.  I am separating the external  keywords from the title keywords.  Then, I will fix the bug in the title setter. I should pull this into a helper method to   avoid code duplication. This should have fixed   all the issues with the mutable class. But my implementation is already growing complex.  That is a severe issue for a class  that still has no behavior at all.  Adding any behavior will only make it longer. This method analyzes the private,   encapsulated state to produce a result. We encapsulate the state primarily   because we must control the mutation. Do you see the problem with this method?  Mutable classes protect the state and expose  public operations that encapsulate algorithms.  And algorithms are business-specific. This Boolean expression is certainly not   the only possibility when comparing phrases. But before I fix this issue, let me give you   some coupons for video courses on Dometrain. Dometrain is an online courses platform run   by fellow content creator Nick Chapsas, and  it is the go-to place for up-to-date courses   on modern software engineering topics. From testing to clean architecture and   modular monoliths all the way to DDD,  microservices, Docker and Kubernetes,   Dometrain has a rich collection of courses that  will help you get up to speed with each topic   by expert authors who have been practising  what they teach in real companies for years.  Dometrain just launched Dometrain Pro,  a subscription offering that gives   access to all of the platform’s courses. With more than 24.000 satisfied students,   you can’t go wrong with Dometrain, and  the first 100 of you can use code ZORAN,   to get 15% off any course. Now, back to the code.  There are two principal ways to avoid the  pitfall of varying algorithms in mutable design.  One is to turn the class  abstract and let a derived   class implement a concrete comparison algorithm. This approach quickly leads to inheritance hell,   all too well known to object-oriented programmers. A more flexible solution is to accept a strategy   that applies to each keyword and  let some other class implement it.  If I may twist your brain, this is the pure  functional implementation of that same method.  Where is the search phrase? It's  in the closure, don't worry.  This ends the demo segment where I  was struggling with mutable design.  The two principal problems in mutable  design are complexity and bugs.  You have seen both in this class. I will now implement the same   features in the immutable class. The complete source code for this   demo is significantly larger  than what I will display here.  The source code is available  to patrons of this channel.  Let me emphasize that every registered  patron helps hundreds of other programmers   watch these videos for free,  so please join me on Patreon   and help me maintain this free channel. I will now apply the immutable design.  Forget about keywords at first  and only focus on the title.  How shall I use this class? Instantiating the class only   gives you the immutable instance. You can ask it questions, apply   functions to it, but not change its state. C# supports with expressions for records,   which copy all pieces of state  except those you explicitly set anew.  However, I lost support for with expressions on  the title when I removed the init-only setter.  And I did that to enforce validation. Why? Because I still have that personality   disorder called primitive obsession, which I  developed while designing the mutable model.  This mutable model is a  remnant of the old days when   state encapsulation was the highest principle. The entire class is obsessed with primitive types.  Now, back to the immutable design, which should  favor domain-specific types over primitive ones.  Why not give title its own definition, wrap that  string, validate it in a trivial type, and then   use it with complete confidence everywhere else? Look at the book definition now.  With title being a domain type, the book  can finally focus on its own domain rules.  Titles will fight their  own battles in this design.  Maybe expose a static factory function  and ensure that all consumers use it.  Maybe turn it into a smart constructor,  avoiding needless exceptions.  In a proper functional design, this  static factory function would return   an optional title or a result type. Back to using the immutable objects.  Substituting a new title object  means we obtain a new book object.  Beware of the bang operator. This is not the proper use of it.  I have only cut a corner because  dealing with negative paths is   not the topic of this demo; that is all. Working with immutable objects substantially   differs from what we would do in mutable design. Here, I am setting the new title in place.  How could that turn into a bug? - tell me. I am printing the book's title and a dashed   line under it. This is the   corresponding operation on the immutable model. When I run the demo, they produce the same output.  Did you spot the bug? Let me show you.  Imagine a call to a function in the middle. The function operates on an immutable book.  The second function will do the same  thing, only this time to the mutable book.  And now I have the bug. Running the demo will show it.  The line under the mutable  book is not measured well.  That is the consequence of sharing a mutable  reference between the caller and the function.  This particular situation  is called the aliasing bug.  Alias is the ancient name used in  computer science for a shared variable,   when an object comes under two names. The immutable variant, however,   cannot contain an aliasing bug. The function would need to communicate   the modified object explicitly. There are always two of them,   one before and one after the change. And here is how immutable design   prevents repeating that bug. If I used the new object in the   printout, then I should notice a mismatched  dashed line in the rest of the printout.  I don't see another solution  but to prepare a brand-new line.  Making an aliasing bug becomes  very difficult this time.  Running the demo will show how the immutable  variant utilizes the modified object without the   aliasing bug observable in the mutable demo. This completes another stage in this   demonstration: working with immutable objects. The next topic is to add support for   collections in an immutable class. I will remind you that you can download   the completed source code, with a few details  added compared to what you can see in this video.  The source code is available on my Patreon page. I want to implement support for those same   keywords I had in the mutable class. Adding a list to an immutable class is nonsense.  That list would be a shared mutable  reference, bringing the aliasing bugs   back into the game, big time. I have just instantiated a   list containing a keyword and then  used it to construct a Book object.  The aliasing bug may manifest if I  continue adding items to this list.  How on earth will the book instance  react to this change if it is immutable?  Therefore, mutable collections are out  of the question in immutable classes.  Immutable classes usually  depend on immutable collections.  If this is the first time you have  seen an immutable list, go and learn   about immutable collections right now. An immutable collection would return a   new one whenever you try mutating it. This call returns a new ImmutableList,   which differs from the existing one in that  it contains one additional string at its back.  The AddKeyword method must return the new  instance with the modified keywords list.  Many programmers freak out when they see a  new object created, fearing it might be slow.  Somebody would have noticed performance  issues already if they were significant.  For example, in a database application, this  operation would be three orders of magnitude   faster than a single call to a database. Also, the with expression only makes a   shallow copy, which is blazingly fast. If we move back to the consumer,   we will see that the aliasing bug is now  impossible to make - it simply doesn't compile.  I can put an empty immutable list here. That would give me an intermediate Book instance.  Then, add the keyword to  obtain the final instance.  Alternatively, if there is already a list object,  convert it to an immutable list and detach the   Book object from that mutable list. The subsequent modification to   the list is pointless. The immutable Book class   has made any aliasing bugs impossible. The last remaining feature is to add   filtering of books by a search phrase. The request is to combine the book's   keywords with those extracted from the title. With the title being an individual type,   it can take part of that duty to itself. I think you should really start to enjoy   the simplicity of immutable  designs if you haven't already.  I will now make a change to the Book class. The external keywords will come into a   separate, private collection. The publicly visible keywords   will be a sequence, and I will construct that  sequence by concatenating the title's and the   book's keywords into one sequence. Where is that search and compare   method I had in the mutable class? Well... go to the place where it belongs,   and you will probably find it there. The incredible power of immutable   designs is that classes are finally free  to expose their components publicly.  I could not do that in the mutable  design because of the mutation!  Someone would scramble my object's state. Let me give you a hint of what   that would look like. This class could be part of   a library that specializes in matching  books against criteria of all kinds.  One algorithm is just to recognize  a search phrase in the keywords.  Another is to allow the keyword  to be longer than the phrase.  The third algorithm goes the other way,  letting the phrase contain an entire keyword.  Yet another algorithm would apply an  elaborate calculation of the likeness   between a search phrase and a keyword and  then take the maximum across all keywords.  Public components make immutable classes  highly susceptible to extension methods and   pattern-matching expressions. Both are out of reach for   most of the mutable classes. What you see here would be four derived   classes in a traditional mutable design. In an immutable design, it requires no   modifications to the subject class. It will remain simple forever after.  You can now step on learning how to design  proper, unbreakable immutable types.  Learn about the power of functional types and  start writing incredibly short and effective code.
Info
Channel: Zoran Horvat
Views: 6,597
Rating: undefined out of 5
Keywords: csharp, dotnet, programming, object-oriented programming, functional programming, c#, zoran, horvat, zoran horvat, c# programming, learn c#, c# tutorial
Id: jjf5nEmDjaE
Channel Id: undefined
Length: 14min 23sec (863 seconds)
Published: Thu May 23 2024
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.