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

แชร์
ฝัง
  • เผยแพร่เมื่อ 27 ธ.ค. 2024

ความคิดเห็น • 74

  • @MilanJovanovicTech
    @MilanJovanovicTech ปีที่แล้ว +43

    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 ปีที่แล้ว +4

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

    • @elpe21
      @elpe21 ปีที่แล้ว +3

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

    • @ivandrofly
      @ivandrofly ปีที่แล้ว

      @rawcoding@@elpe21

  • @GiovanniCostagliola
    @GiovanniCostagliola ปีที่แล้ว +7

    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  ปีที่แล้ว +8

      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 ปีที่แล้ว +2

      ​@@zoran-horvat Absolutely agree. Unfortunately.

  • @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.

  • @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.

  • @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.

  • @jaredlash5002
    @jaredlash5002 10 หลายเดือนก่อน

    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 10 หลายเดือนก่อน

      The smart constructor preserves this bug.

  • @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.

  • @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.

  • @thygrrr
    @thygrrr 6 หลายเดือนก่อน

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

    • @zoran-horvat
      @zoran-horvat  6 หลายเดือนก่อน +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.

  • @codiore
    @codiore ปีที่แล้ว +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.

    • @codiore
      @codiore ปีที่แล้ว

      @@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

      @@codiore I'd say that the Beginning OOP course is still the best option.

  • @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!

  • @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.

  • @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: th-cam.com/video/8-2xr_kBRnQ/w-d-xo.html
      However, optional objects are not part of .NET and the whole idea is a bit hard for many C# practitioners to adopt.

  • @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.

  • @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!

  • @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.

  • @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

  • @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.

  • @rakkarajput
    @rakkarajput ปีที่แล้ว +1

    Your awesome as always

  • @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.

  • @7th_CAV_Trooper
    @7th_CAV_Trooper 7 หลายเดือนก่อน

    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.

  • @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  ปีที่แล้ว +3

      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.

  • @nick_stelmakh
    @nick_stelmakh ปีที่แล้ว +1

    🔥🔥🔥

  • @kenbrady119
    @kenbrady119 2 หลายเดือนก่อน

    No negative money? It's called "Debt" - lol. IMHO this object model can and should represent Debt. Of course, one must eventually deal with Debt ;(

    • @zoran-horvat
      @zoran-horvat  2 หลายเดือนก่อน

      @@kenbrady119 Debt is not negative. It is positive. You never owe a negative amount.

  • @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.

  • @m4inline
    @m4inline ปีที่แล้ว

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

  • @ivandrofly
    @ivandrofly ปีที่แล้ว

    impl Money