You Will Add Smart Constructor to Your Rich Domain Models When You See This!

Поделиться
HTML-код
  • Опубликовано: 15 сен 2024

Комментарии • 71

  • @MilanJovanovicTech
    @MilanJovanovicTech Год назад +39

    It's hard to explain how much I love your videos 😁😁

    • @zoran-horvat
      @zoran-horvat  Год назад +4

      And vice versa!

    • @antonionakic2690
      @antonionakic2690 Год назад +4

      Just found Milan's channel through this comment and subscribed. Excellent content on both channels. Thanks, guys!

    • @ConsoleHelloWorld
      @ConsoleHelloWorld Год назад +3

      Two of my three favorites youtubers here together! You guys are simply awesome!!!!❤

    • @elpe21
      @elpe21 Год назад +2

      @@ConsoleHelloWorld Who's the 3rd? Nick ?

    • @ivandrofly
      @ivandrofly Год назад

      @rawcoding@@elpe21

  • @GiovanniCostagliola
    @GiovanniCostagliola Год назад +6

    My “smart” constructors return Result ti encapsulate business rules violations, and throw exceptions when the invariant is violated by a corruption (db for example). This approach, as you can guess, enable the beauty of functional composition and the clean propagation of the logic errors from the very deep of the domain.
    I’m very interested on your opinion.
    I really enjoy your contents.
    Bravo

    • @zoran-horvat
      @zoran-horvat  Год назад +7

      Unfortunately, majority of C# programmers are not ready to see their code that way. My production code also returns Option or Either for the same purpose.

    • @GiovanniCostagliola
      @GiovanniCostagliola Год назад +1

      ​@@zoran-horvat Absolutely agree. Unfortunately.

  • @m5s10
    @m5s10 Год назад +8

    The only thing that I'd like to add is to consider moving this smart constructor into a standalone factory class. Money object with the smart constructor feels like it's having too many responsibilities and one red flag is that it knows about other implementations of IMoney. If you extract the smart constructor logic in the separate class, it will know about concrete implementations, which is fine, it will only ever do one thing (create instances IMoney). So it will be small, concise, very easy to unit test and will respect single responsibility principle. Last, but not least, if we ever expand IMoney with new implementation (let's say Debt, where amount does go in negative value), to me it makes more sense to change the MoneyFactory, rather than Money.

    • @zoran-horvat
      @zoran-horvat  Год назад +8

      I usually don't rush with introducing a new class for the purpose, simply because the design might not grow any further. For instance, nothing warrants that there will ever exist the third class implementing IMoney.
      On a related note, keep in mind that NoMoney is a Null Object implementation and it belongs with the interface, not with other concrete implementations. I often define a static factory (static property getter) on the interface, which returns the single instance of that interface's Null Object. Such static property getter would be called None, Empty, or Zero when speaking of money.
      If Money were defined in a sub-namespace, NoMoney would remain in the same namespace where the interface is defined. Therefore, any dependency on NoMoney is no more concrete than the dependency on IMoney.

  • @asdasddas100
    @asdasddas100 Год назад +2

    The whole time I was thinking about how I'd use an Option type instead of null
    Glad to see you mention it at the end, gives me more confidence in my decision making

    • @zoran-horvat
      @zoran-horvat  Год назад +3

      Actually, smart constructors come from functional languages which invariably have the Option type, and then use it in smart constructors.

  • @5cover
    @5cover Год назад +3

    Awesome video as always
    7:52 As a note, using the null-forgiving operator ('!') can lead to confusing exceptions such as NullReferenceException in seemingly unrelated code.
    I prefer to adopt the "fail fast" principle. If something that shouldn't be null is null, the developer should be warned immediately.
    Which is why I always implement a simple extension method that asserts that an object isn't null and returns it:
    public static T NotNull([NotNull] this T? t, string? message = null)
    {
    Debug.Assert(t is not null, message);
    return t;
    }
    Throwing a generic Exception() feels like a bad idea, an assertion makes more sense here since if t is null then it means there's a bug in the code.

    • @zoran-horvat
      @zoran-horvat  Год назад +2

      Good points. BTW I have thrown the Exception without getting into details because I was about to delete that code in a moment.

  • @seriyezh
    @seriyezh Год назад +1

    Thank you so much Zoran for sharing you knowledge and expertise. I definitely can recommend your courses to anyone who wants to improve their skills. I really enjoyed watching your course: Making Your C# 7 Code More Functional. I was wondering if we will see any F# courses from you?

  • @happy_burger
    @happy_burger Год назад +1

    Smart constructor can also be made async if that is what it takes to initialize an object

    • @zoran-horvat
      @zoran-horvat  Год назад +2

      I'd rather keep asynchronous execution as an orthogonal concern. An object that supports common creation is certainly creatable in a Task.

  • @jaredlash5002
    @jaredlash5002 7 месяцев назад

    At 6:02 there is a bug in the Money constructor for lines 10 and 11. It is possible that Math.Round(amount, 2) causes Money to get set to 0 (for instance, amount == 0.001). In this instance, the check from line 10 will already be passed, but you get an invalid Money object.

    • @jaredlash5002
      @jaredlash5002 7 месяцев назад

      The smart constructor preserves this bug.

  • @dxs1971
    @dxs1971 Год назад

    You can retrun Maybe or even Result and so avoid null reference.

    • @zoran-horvat
      @zoran-horvat  Год назад +1

      That is what the follow-up video is demonstrating: ruclips.net/video/8-2xr_kBRnQ/видео.html
      However, optional objects are not part of .NET and the whole idea is a bit hard for many C# practitioners to adopt.

  • @AndriyBezkorovayny
    @AndriyBezkorovayny Год назад +1

    Thank you so much Zoran. WDYT, if the TryCreate method will have the output parameter? In that case, we get the result (true or false) and the instance (out) to conform the Try... pattern.

    • @zoran-horvat
      @zoran-horvat  Год назад +3

      I'm not fond of out parameters because I cannot chain calls on such methods. They also break a few other common coding patterns, such as passing functions as arguments, etc.

    • @AndriyBezkorovayny
      @AndriyBezkorovayny Год назад +1

      @@zoran-horvat thank you, I got the point.

  • @rakkarajput
    @rakkarajput Год назад +1

    Your awesome as always

  • @7th_CAV_Trooper
    @7th_CAV_Trooper 4 месяца назад

    I know this wasn't the point of the video, but does "NoMoney" have a currency? I mean Zero dollars == Zero euros, right? For some reason this video reminded me of college chemistry conversion factors.

  • @nick_stelmakh
    @nick_stelmakh Год назад +1

    🔥🔥🔥

  • @thygrrr
    @thygrrr 3 месяца назад

    4:12 how do you hide the default constructor, and how wouly you recommend proceeding on a record struct?

    • @zoran-horvat
      @zoran-horvat  3 месяца назад +1

      You can declare a private parameterless constructor on classes. You can do the same on a struct, but the default keyword would evade it.

  • @Ruhrpottpatriot
    @Ruhrpottpatriot Год назад

    I never understood what makes constructors so much better than a factory method like `new()`, especially when you consider initializers. I don't even know when the last time was I wrote a constructor aside from making the default one private.

    • @zoran-horvat
      @zoran-horvat  Год назад +1

      Constructors are simple - that is their greatest benefit. If you don't have additional requirements, then you don't need an additional method besides a constructor.

    • @Ruhrpottpatriot
      @Ruhrpottpatriot Год назад

      @@zoran-horvat For that I could also write a parameterless and static `new()` method. Yes, I'm loosing a bit of syntactic sugar, but I'm also gaining a lot, like possible null/optional returns or async.

  • @kantagara
    @kantagara Год назад +2

    wouldn't it be better if TryCreate returned bool and had out IMoney ? Since , at least for me, when I see Try, I always assume it's returning a boolean rather than the actual value.

    • @zoran-horvat
      @zoran-horvat  Год назад +6

      That convention has its drawbacks, too. For one thing, it requires a variable. And even then, the variable must be nullable, for the case when the method returns false.
      The nullable/optional return method doesn't require a variable. You can chain a call on the result, use it in a null propagation or null coalescing operator or simply return it. All that requires two steps with the out variable.

    • @SuperJMN
      @SuperJMN Год назад +7

      Out parameters are considered inferior because they don't adapt well to the maxim that methods should either be commands or queries. Also, doing this allows for fluent syntax, that out parameters don't allow.

  • @seandonohue4048
    @seandonohue4048 Год назад

    Great video, thank you:-) One thing I've noticed in general is that exceptions often lose details which makes it harder to fix. So in this example, if somewhere on the call stack we catch an ArgumentException from Create, we don't know if it was the amount or the currency that is invalid.
    If we wanted to keep that detail, you might say: if amount is invalid then throw new ArgumentException("Invalid amount") else if currency is invalid then throw new ArgumentException("Invalid currency") else TryCreate(amount, currency).
    The drawback of this is that there is now duplicate code in Create and TryCreate. Is there a cleaner way to do this?

    • @zoran-horvat
      @zoran-horvat  Год назад +3

      You are generally right, though there is a caveat. Besides the argument name, you should stay away from including argument values into the exception, because that may open security holes or break privacy laws. While argument values are often the critical piece when debugging from logs, it is usually best not to have them in logs in the first place!
      Speaking of the video, I have just opted to go with the shortest possible code.

  • @larsp5109
    @larsp5109 Год назад

    5:51 Your three levels deep nested tertiary statement is terse but isn’t all that readable imho…

    • @zoran-horvat
      @zoran-horvat  Год назад +2

      It is actually chaining, not nesting. But I agree with you that it lacks readability. It is always the case with pattern matching whenever code becomes more complex than trivial demos.
      In the extreme cases, it makes sense to turn patterns into calls to methods that give them a readable, meaningful name.

  • @Rafa-fp4ih
    @Rafa-fp4ih Год назад

    Maybe it's stupid question but why You do not return NoMoney instead of null in TryCreate example. Returning null force responsibility of handling this null from object to client. Even using Optional will move this responsiblity to client, I wondering if it a "good" approach/idea. Maybe I don't get the difference between Optional/Maybe vs. Null object pattern or do not understand something :)

    • @zoran-horvat
      @zoran-horvat  Год назад +1

      The accent in smart constructors is on the cases where there is no substitute object to return and we want to force the caller to distinguish that case from a case where there is a valid object.
      Imagine a function which returns the price of an item, given a timestamp. What would it mean if we returned NoMoney instead? The caller might keep going and put the item into the basket instead of reporting that the item cannot be purchased.
      It is common to make smart constructors return optional objects instead of nullable references.

    • @Rafa-fp4ih
      @Rafa-fp4ih Год назад

      @@zoran-horvat Thanks, a lot for answer!

  • @DavidSilwal
    @DavidSilwal Год назад +1

    where can we get the source code of your awesome videos?

    • @zoran-horvat
      @zoran-horvat  Год назад +1

      Source code is available to patrons only. Here is the page with the source code of this video: www.patreon.com/posts/84130393

  • @i.o.5508
    @i.o.5508 Год назад +1

    In what order should I take your UDEMY courses from start to finish?

    • @zoran-horvat
      @zoran-horvat  Год назад +2

      The Beginning OOP course is the fundamental one. It covers concepts that every programmer should know well, but quite often even senior programmers are not sure about. I recommend that cou8ti anyone who asks.
      The other courses are more topic oriented. Refactoring to Patterns and the one on design patterns could be the next pick.

    • @i.o.5508
      @i.o.5508 Год назад

      @@zoran-horvat i want to completly learn about OOP with C#. I know the basics such as inheritance, interface, encapsulation etc...

    • @zoran-horvat
      @zoran-horvat  Год назад +2

      @@i.o.5508 I'd say that the Beginning OOP course is still the best option.

  • @ruekkart
    @ruekkart Год назад

    Great video!
    Maybe it's just me but I find a bit hard to follow the nested ternary operators and I'm guessing you're doing it that way because you need it to use arrow sintax declaration everywhere is that right? So I have another question, is there any advantage on only using arrow sintax to declare methods/constructors?

    • @zoran-horvat
      @zoran-horvat  Год назад +2

      Let me clarify the bit about ternary operators first. They are not nested, they are chained in my code. The difference is substantial because chained tests act as a pattern-matching expression.
      And that brings us to the second question. Pattern matching expression is the expression. It is calculated from inputs, so to produce the output. If you can organize your methods to act as expressions, then the expression-bodied syntax is a natural choice.
      Both concepts come from functional programming. You can witness that they are now an integral part of the C# syntax.

  • @SuperJMN
    @SuperJMN Год назад

    Are you dropping the usage of the Optional (Maybe) type altoghether in favor of nullable reference types? What do you think? Is is still worth using it?

    • @zoran-horvat
      @zoran-horvat  Год назад +3

      Nullable reference types are quite capable today, but solely thanks to the corresponding syntax. A custom optional type cannot compare with that, not until C# language adds syntactic sugar.
      However, I am using optionals in my projects. Thay are a powerful modeling tool, telling much more than a nullable reference can. I am only avoiding them in regular videos because many viewers would just be confused. For example, smart constructors in my production code invariably return an Option, the same way as it is a norm in Rust.

    • @SuperJMN
      @SuperJMN Год назад +1

      @@zoran-horvat That's great relief, because I also think that optionals are categorically better. Thanks!

    • @Mortizul
      @Mortizul Год назад

      @@SuperJMN Haha Categorically. Nice play on words!

  • @yousef.a.k3793
    @yousef.a.k3793 Год назад

    Thanks, it's great video.
    There is optional in DotnetNEXT Project, could you please explain how to use it and what is benefits of that wired implementation of optional

    • @zoran-horvat
      @zoran-horvat  Год назад +2

      I am trying to avoid showing third party libraries, especially if they are not backed by the language team or changes made to the language.

  • @robertmrobo8954
    @robertmrobo8954 Год назад +1

    I dont see the difference between a Smart Constructor and a Factory Method

    • @zoran-horvat
      @zoran-horvat  Год назад +2

      Smart constructor is an augmented factory method. The difference is in its ability to choose not to create the object - a decision we don't expect from a factory method.

  • @NirWeber
    @NirWeber Год назад

    also - Constructors can not be async...

    • @zoran-horvat
      @zoran-horvat  Год назад +2

      That is an interesting point that made me think for a moment, but I believe that the smart constructor, even though it *can* be async, should not be. Asynchronous execution is an orthogonal concern to creating objects. You can choose to create an object within or outside an asynchronous context.

  • @moneymaker7307
    @moneymaker7307 Год назад

    Great video, but why not call it a factory method. I would after understanding the context of what you are trying to do within the first 10 seconds of the video if you would have replaced “Smart Constructor” with “Factory Method”

    • @zoran-horvat
      @zoran-horvat  Год назад +1

      Smart constructor has one additional responsibility compared to a factory method - it can choose to *not* create an object when asked. Even its signature communicates that possibility.
      A factory method's signature announces that it will return an object of the declared type. Its only way out if it cannot fulfill the promise is to throw.
      That is the principal difference which grants smart constructor its own name.
      You will find smart constructors in any functional language alongside common factory functions. It is functional coding style that underlines the distinction between the two concepts.

  • @abj136
    @abj136 Год назад +1

    Make me want to try C++:
    template class optional_ptr /* like smart_ptr */ { …}
    class RealMoney : public IMoney
    {
    public: optional_ptr construct(parameters)
    {
    if (parameters are good) { return optional_ptr

    • @abj136
      @abj136 Год назад

      clarifying detail
      class NoMoneyClass : public IMoney {…};
      static optional_ptr IMoney::NoMoney(&NoMoney); // NoMoney object; will never be destroyed.
      this concept is not complete; needs more aspects of optional implemented yet.

    • @abj136
      @abj136 Год назад

      i’m not clear I got the right idiom. I’m thinking of a class object that you can invoke its methods, but the methods will generally all be no-op. But I think there’s a different pattern where you cannot invoke an object at all until you first pattern-match and establish the object is valid, and then use the valid object.

  • @ivandrofly
    @ivandrofly Год назад

    impl Money

  • @m4inline
    @m4inline Год назад

    I like your videos but I think you owe Flash Gordon an apology.