The Square-Rectangle Problem

Поділитися
Вставка
  • Опубліковано 21 лис 2024

КОМЕНТАРІ • 176

  • @mattiasandersson9825
    @mattiasandersson9825 8 місяців тому +29

    Ok, so here it is. After beeing active in the profession of Software Engineering for 31 years and spreading the gospels of SOLID design priciples for allmost as long, you still make me want to go back to school, more in particular, attending your classes.
    You are a tribute to Uncle Bob & Barbara Liskov, love it!

  • @praveenr3396
    @praveenr3396 8 місяців тому +46

    Welcome back Chris. Please continue posting videos. I like your way of explaining the stuff

  • @MegaCuerdas
    @MegaCuerdas 8 місяців тому +32

    Your energy and passion are contagious, and your knowledge invaluable, thank you very much!

    • @ChristopherOkhravi
      @ChristopherOkhravi  8 місяців тому +1

      🙏

    • @Tsunami14
      @Tsunami14 8 місяців тому

      Pretty much took the words out of my mouth.
      I only discovered his channel today, and I'm totally digging the vibe.

  • @Flutterdev6391
    @Flutterdev6391 8 місяців тому +9

    Men you'r teaching way is really awesome ur examples left bluePrint in our mind for forever.

  • @abdoulkarimbalde2802
    @abdoulkarimbalde2802 8 місяців тому +11

    Hello Christopher Okhravi, it's Abdoul Karim BALDE, from Guinea, West Africa.
    Very happy to watch your videos again, it's longtime.
    I like your videos, you explain very well, keep continue please.

  • @Rajmanov
    @Rajmanov 8 місяців тому +5

    Welcome back, my friend! It's fantastic to see you again.

  • @MrAbhinavbaijal
    @MrAbhinavbaijal 8 місяців тому +4

    Nice to see you back. I have learned a lot from them. Especially the design pattern series is one of my favourites.
    Thank you 🙏

  • @verfran
    @verfran 8 місяців тому +3

    My take away: Use base class only to hold common behavior, anything else would break LSP.
    I always had difficulty convincing developers to follow LSP, but this example makes a great argument/point. Thank you

    • @bogdanb904
      @bogdanb904 7 місяців тому +2

      I think this is exactly what's explained in his previous video. Only use sub-types for behavior changes, not for data changes.

  • @droam129
    @droam129 8 місяців тому +5

    Great video as always! In addition to “if I change the width of a rectangle, I would expect it to not affect the height”, the other side of the coin is “if I have a rectangle, I would expect to be able to set the width to say 3 and the height to say 5.” But if the rectangle is secretly a square, you’ll end up with a 5x5 rectangle rather than a 3x5 rectangle.

    • @ChristopherOkhravi
      @ChristopherOkhravi  8 місяців тому +1

      Exactly! You get it 😊😊

    • @JohnEkare
      @JohnEkare 5 місяців тому

      I believe that is too many assumptions made of rectangles: A four-sided polygon with four right angles. Nothing in that definition states the expected relationship, or lack thereof, between its sides.

  • @MrAymenmatador
    @MrAymenmatador 8 місяців тому +2

    woow amazing, I have never grasped the LSP easily as it was explained in this video.

  • @brtk7
    @brtk7 8 місяців тому +3

    That example you presented helps me understand what Liskov Substitution Principles really is. Thanks 🙏 and as you suggested the better refine contract could be an interface with only readonly properties that both classes would implement.

    • @olivermeyer3963
      @olivermeyer3963 8 місяців тому

      What would the name of this interface be?

    • @olivermeyer3963
      @olivermeyer3963 8 місяців тому

      ISomewhatKindOfSquaReact maybe just kidding

    • @brtk7
      @brtk7 8 місяців тому

      @@olivermeyer3963 you know you need to remember to not overthinking here, it’s just an exercise, name is here secondary

  • @MMMM-vc5oi
    @MMMM-vc5oi 8 місяців тому +1

    Exactly correct, immutable class is the solution. Your approach and method to explain problems is awesome. Another way is to define just Rectangle class with boolean field IsSquare inside

    • @karlmehltretter2677
      @karlmehltretter2677 6 місяців тому

      I make all my classes immutable now so that I never will have any issues and bugs anymore.

  • @guidoerfen7944
    @guidoerfen7944 8 місяців тому +2

    Best explaination I came across so far.

  • @johnny5gr
    @johnny5gr 8 місяців тому +4

    Nicely put. We want more! Thank you!

  • @alexstone691
    @alexstone691 8 місяців тому +1

    Truly love your energy in these videos, makes me think this through
    If im understanding it correctly its about expectations/contract, the code is hidden in this case as you get the super type Rectangle but the subtype breaks the expectations/contract by changing the behaviour when setting width/height

  • @karlmehltretter2677
    @karlmehltretter2677 6 місяців тому

    Thanks so much for this ! I had previously implemented a "PrimeNumber" class in Java.. Now I'm deriving a "Two" class, a "Three" class, and a "Five" classs and so on. I'm gonna watch the next video when I'm done ;)

  • @AussieRossco
    @AussieRossco 8 місяців тому

    Nice. You made it so concrete. The principle is normally hand waved away as pedantic.

  • @siddharthkothari007
    @siddharthkothari007 8 місяців тому +2

    Thanks Chris for nicely putting the topic. Waiting for more cool stuff.

  • @sanjubaba1339
    @sanjubaba1339 8 місяців тому +2

    It’s good to see you back after long time. Was missing your good content .

  • @RezaulKarim-cq5ft
    @RezaulKarim-cq5ft 8 місяців тому +2

    If the properties of square and rectangle are immutable and can only be set through constructor, the things left are the potential methods of both class. For example finding out the area.To find out the area of a rectangle , we multiply height and width, again to find out the area of square, we only have to do height^2 or width^2. Since both height and width of a square is equal, it can be achieved with the rectangle's Area method that does height*width to find out the area. Now the thing is, what if the user does not use the Area method of the superclass and instead does the operation on their own. So, if we treat rectangle as super class, the user will end up doing height * width to find out the area. it also covers square. But if we treat square as super class, and rectangle as sub class, then user might end up doing Height*Height or Width*width to find out the area which does not satisfy the condition of the area of a rectangle. So, in my opinion if all the fields are immutable, treating rectangle as super type is enough.

  • @personalJoke
    @personalJoke 8 місяців тому

    Why Square, Equilateral Triangle and Circle can share the same common ancestor?
    I love the opportunity to talk about these topics.... I haven't written code in soooo many years.
    Here are my three implementations:
    1. An abstract class called shape having a property called length, and another one called sides. From there I implemented only the behaviours Square, EquilateralTriangle, and Circle.
    2. A class, and three methods: AsEquilateral, AsSquare, AsCircle.
    3. A class Shape accepting functions as parameters to calculate the area.
    Circle doesn't use Sides in any of those implementations.
    So, the big question is: Are these implementations breaking the Liskov's Substitution Principle?
    BTW, thanks for bringing back to me the enthusiasm and joy of writing code again!

  • @olivierbouzonnie8025
    @olivierbouzonnie8025 8 місяців тому +1

    Also very glad to see you back. Your series on "head first : design pattern" was a huge help while i was trying to become a professionnal programmer (which I am now). Do you have plan to do more of them, or tackle architecture design patterns in the futur ?

  • @MichaelKire
    @MichaelKire 8 місяців тому +2

    Good to have you back 😊

  • @Luis-Torres
    @Luis-Torres 8 місяців тому +1

    So good to have you back Chris! I hope you've been well :)

  • @snail8720
    @snail8720 8 місяців тому

    Good video! I made a similar example for immutable types and indeed the issue does not persist, because the initialization of the object's properties are set, the square can be treated exactly the same as a rectangle. The guarantee that height and width are equal is enforced by the constructor, and past that you don't need to think about it.
    The next obvious question is how do we resolve this conundrum while allowing the objects to be mutable?

  • @sanjaycs89
    @sanjaycs89 8 місяців тому +4

    Glad to see you posting again 😊

  • @faduregis
    @faduregis 8 місяців тому

    There is always something new to learn in programming, thank you Christopher for answering this challenging problem. Btw longtime!

  • @krozaine
    @krozaine 8 місяців тому

    As is rightly captured, the definition of a Rectangle matters a lot! Statement: A rectangle never said that changing the width will NOT change its height.
    If all the Rectangle said was that - It's a 4 sided polygon with all inner angles as 90 degrees, all the formulae for area, diagonal length and perimeter would hold true for a Square implementation of a Rectangle. The client should not get spooked about height getting changed when width is changed - the client shouldn't care as long as the initial idea of what it means to be a Rectangle isn't violated. I guess the same case can be built for Rhombus and Square also. Very interesting video and a good revision for LSP!

    • @silberwolfSR71
      @silberwolfSR71 8 місяців тому

      I agree with the general sentiment of your comment - what really matters is the contract. I think a big part of the issue is the contract of rectangle as defined by the problem.
      An example of a contract that could make sense:
      Rectangle {
      float height()
      float width()
      void scaleBy(float factor)
      }
      We're now defining another invariant of a rectangle - its aspect ratio. This allows us to make both rectangle and square be mutable, but without violating the contract.
      But you can do even cooler things if you make both immutable. For example, you can still do single-side scaling without breaking contracts:
      Rectangle {
      float height()
      float width()
      Rectangle scaleBy(float factor)
      Rectangle scaleX(float factor)
      Rectangle scaleY(float factor)
      }
      Now the "mutated" object no longer has to be a square (and thus is freed from having to adhere to the invariants of a square). Scaling a square along only one of the axes can simply return a rectangle. (Note that it would be enough to write the implementations of these methods in Rectangle and simply inherit them as is for Square)
      You could even add methods like "Rectangle withHeight(float newHeight)" if you want more control than that.

  • @kamalam29
    @kamalam29 8 місяців тому

    @Christopher, you can also teach us History or Economics or Art or Maths but request you to not hold yourself to make videos. We love listening you and engage with you. Thank you for being awesome teacher.

  • @francescoleto2823
    @francescoleto2823 8 місяців тому

    You are amazing. Maybe a Shape superclass can solve the LSP?

  • @josda1000
    @josda1000 8 місяців тому

    mannnn it's good to see you again! And yes I love using behaviors (strategy) for this kind of situation. I really can't stand conditionals anymore, and factories/builders truly keep that to a minimum.

  • @DoChDev
    @DoChDev 8 місяців тому +1

    The problem here is that getting width and height should be a different interface/contract than setting them. For a square you wouldn't actually set width or height, you'd set the side length. Getting width or height however would be no issue, both would return the side length.

  • @TheAd021
    @TheAd021 8 місяців тому +2

    I would prefer composition over inheritance. Square is a wrapper (or an adapter) for a Rectangle and has only the public methods setSide, getSide

    • @ChristopherOkhravi
      @ChristopherOkhravi  8 місяців тому +2

      I agree that for more complex cases (ie where the classes have more code) that is probably the best solution 😊

  • @juanibuscaglia3239
    @juanibuscaglia3239 8 місяців тому +3

    The way I see it, just because something has been defined in some way in some specific field, that doesn't mean it's a good idea to implement it that way. Not only is a square a special case of a rectangle, but also of a parallelogram and trapezoid. And I've been taught to avoid multiple inheritance. Besides, in mathematics you usually have different ways of defining the same thing (there's a definition of rectangles defined by their internal angles and axes of symmetry).
    My point is that one shouldn't feel too restricted by formal definitions and should instead try to approach it in a way that makes sense for the particular requirement you're trying to model. A simple web app to calculate the area of different shapes is going to have a very different implementation of a rectangle-square as a 3D CAD program, and so on.
    One potential approach is that of Maya's API (a 3D DCC package), with objects and function sets that you attach to those objects to make them behave in specific ways. You could have a rectangle (or parallelogram, even) as a basic shape, and then you can attach a square function set to the shape. There's no inheritance, so there's no contract that gets violated.
    Say your basic shape is the rectangle, var myrect = new Rectangle() is the only possibility.
    You want to have a square, you create the function set var fn_set = new SquareFnSet(myrect), fn_set.set_side_length(10) will mutate your rectangle as needed, perhaps squaring it by default if it's oblong)
    The downside (which I don't even think is a downside) is that given a shape you wouldn't know what specific shape it is. But you could either have some general functions in the base class (is_square), or through some other way.
    Of course, this is completely overkill for a simple program, but it's probably how I would model such relationship.

  • @DrSpooglemon
    @DrSpooglemon 8 місяців тому

    If the Rectangle class has a method makeSquare(int side) as well as setters for width and height then the object can be either a rectangle or a square depending on usage. It could also have a method isSquare() to determine whether the sides are of the same length.

  • @unofficialshubham9026
    @unofficialshubham9026 8 місяців тому +1

    thanks for starting your amazing video again

  • @babaiiseuphoric
    @babaiiseuphoric 8 місяців тому

    You are back with a bang! Glad to see you back😊

  • @juriddu
    @juriddu 8 місяців тому +2

    welcome back master.

  • @jf3518
    @jf3518 8 місяців тому

    That is why I like composition over inheritance.

  • @TheBorsooq
    @TheBorsooq 8 місяців тому +1

    I love the content :)
    Great to have you back!

  • @GooogleGoglee
    @GooogleGoglee 8 місяців тому +1

    You are really good man! I appreciate your videos and clear explanations...
    Subscribed ❤ 👍🏻

  • @bunglegrind1
    @bunglegrind1 6 місяців тому

    An enlightening reading is smalltak objects and design by chaimond liu, chapter 17.
    The problem with class inheritance is that it allows method overrides, which in practice violate LSP.
    If the instances are read-only you don't override get/set and LSP is satisfied

  • @SunsetNova
    @SunsetNova 8 місяців тому +2

    Glad to see you back

  • @olivierbernard2733
    @olivierbernard2733 8 місяців тому

    At beginning you Speak like a guru ^^

    • @ChristopherOkhravi
      @ChristopherOkhravi  7 місяців тому

      Haha. Not sure if this is a good thing or a bad thing 😊 Nevertheless, thank you for watching 😊

  • @corlaez
    @corlaez 7 місяців тому

    Bro made the best argument for why inheritance sucks without even trying.
    A function isSquare() and a function changeBothWidth andHeight (if you really want to go that far) would get rid of all issues.

  • @akashacharya2813
    @akashacharya2813 8 місяців тому

    Hey Chritopher, Your explanation about SOLID principles and OOP Design Pattern made me a better software engineer, do you have plans to make cloud design patterns principles playlist. Would really appreciate if you do that, the way you explain concepts are awesome, and it gets ingrained in the brain.

  • @Templarfreak
    @Templarfreak 8 місяців тому

    before watching the full video: i think one issue with understanding squares and rectangles in this way is that a square is actually a regular polygon but a rectangle is not. you can define *all* regular polygons with just one length value that is applied to every side, but you cant do that with a rectangle. however, you *can* define a rectangle with 2 different values, and you also still need two different values to define any polygon, the length of the side and the number of sides. for a rectangle, you need the length and the height instead of the number of sides. and, interestingly enough, ellipses also are defined with 2 values, and circles can be defined like an ellipse but where the two points of the ellipse are just at the same position. this means that all of these shapes can be defined with only 2 values, its just that how those 2 values are interpreted varies from polygon to rectangle to ellipse to circle.

  • @enricomariadeangelis2130
    @enricomariadeangelis2130 8 місяців тому

    My takeaways: 1. Embrace immutably whenever possible, 2. Prefer concept-based duck typing. Imho the two mutable classes should not have an inheritance relationship, but be mutually convertible and ==comparable to each other, with the conversion from rectangle to square asserting if the sides are different. The client API should not take either class, but any class having members to get/set sides, compute area, and so on.

  • @davithov
    @davithov 8 місяців тому

    Then, how to solve the issue described in case of mutable members? Maybe avoiding inheritance altogether (or any form of subtyping) could be a viable approach?
    BTW, although I am aware that you have videos covering SOLID principles, but collecting them into one comprehensive resource with examples would be highly valuable.
    In any resource known to me I like your explanations the most. Thank you very much for your effort!

  • @JohnEkare
    @JohnEkare 5 місяців тому

    Great video! I think the problem arises from inferring to many properties of a rectangle. I believe the definition is that it's four sides with 90-degree angles between its sides. There is no behavioral specification as to how height and width relate to each other. So, if you write code that deals with rectangles, there shouldn't be any assumptions on how their sides relate. How about a Golden Rectangle, where the relationship between L and H is that of the golden ration, should that not be a subtype of rectangle either?

    • @devmesa8398
      @devmesa8398 Місяць тому

      I'm still relatively new to working in strongly typed systems, so please correct me if I'm misguided; but isn't it more problematic to make the assumption that the properties length and width of the mutable Rectangle _do_ hold a relationship other than that specified in the Rectangle's implementation? It's not that the rectangle's length and width have an unspecified relationship, but instead that the length and width have _no_ relationship. That is, one property should not change with respect to the other, and it's because the implementation of Rectangle does not allow it.
      By using inheritance-which according to LSP necessarily says the subtype is substitutable for the supertype-you say that a Square, when used in place of a Rectangle, will behave the _same_ as a Rectangle. But this is clearly not true.
      Because of this, I would also say that a mutable GoldenRectangle is not a valid subtype of Rectangle. It violates the same principle that Square does. Rectangle behaviorally establishes no variable relationship between length and width, and so for GoldenRectangle to force both properties to change when only one is directly changed makes Rectangles unpredictable.

  • @Google_Censored_Commenter
    @Google_Censored_Commenter 8 місяців тому

    The way I like to deal with these problems is using what I call the "1 assumption rule". Whenever you have some definable object with values or properties you want to set, you're only allowed to program in 1 assumption per input.
    So in this example, if I was writing a width *input* for a rectangle object, and I wanted the height to be assumed, that's it. I have no more assumptions left. I can't assume to know the color of the rectangle, of what kind of subtype it is, such as a square. I can assume nothing else about it, if I want to assume its height. I only get 1 assumption. If I want more assumptions, I'll have to use more user inputs. This really helps when you're dealing with 3 or 4 inputs and you're unsure if the assumptions you're trying to program in are really feasible or not, or if you're just a bad programmer. Carefully count how many assumptions you're dealing with, and you'll know the answer.
    I don't know if there's any mathematical validity to the rule, but it's worked for me so far.

    • @r0bertdenir0
      @r0bertdenir0 8 місяців тому

      Is this "1 assumption rule" enforceable in code? How would I write my code to restrict the future me or team mates from violating this?

    • @Google_Censored_Commenter
      @Google_Censored_Commenter 8 місяців тому

      @@r0bertdenir0 depends what you mean by enforceable. You could just add a comment next to the input / assumption line in your code, isn't that sufficient?
      Assumptions are very broad, so if you were looking for a way to automatically detect how many you've made, don't think that's feasible. Programs can't read your mind and know exactly what your goal is.
      I should also mention that "inputs" count as any kind of certain information. Not just what the user defines. Like suppose you have some object with a velocity input X. Strictly speaking, because movement entails a change in position, the information about its positional coordinate along the direction you applied a velocity, is another "input". It isn't an assumption, it's a derived mathematical certainty. (If there's nothing else that might change its speed, like a collision with a wall can be ensured)
      So it can be tricky to know exactly how many inputs you're dealing with. You have to ask yourself "could this thing be otherwise in any realistic scenario?" if no, it's probably a safe input to rely an assumption on.

  • @rahullama395
    @rahullama395 8 місяців тому +2

    Legend is back.

  • @neozes
    @neozes 8 місяців тому

    Maybe I missed the point, but a rectangles contract is that is has 4 right angles, not that it has a width and height. So the square would never violate the rectangle contract. It looks like a problem of contract definition in this example.
    But I think I get a underlying idea.

  • @ApprendreSansNecessite
    @ApprendreSansNecessite 8 місяців тому

    That's one of the many reasons why FP > OOP for domain modeling.
    Mainy people say that FP is unintuitive but people intuitively expect immutability when they think structurally about anything.

  • @AhmedAli-ld6en
    @AhmedAli-ld6en 8 місяців тому

    strong stuff hope to see more in the future

  • @AnotherFancyUser
    @AnotherFancyUser 8 місяців тому +5

    I hate LSP, it is one of those principles in SOLID that usually you don't mess around a lot, you usually try to not violate SRP, OCP and DI.

    • @ChristopherOkhravi
      @ChristopherOkhravi  8 місяців тому

      Thanks for the perspective. You mean that it’s uncommon to accidentally violate LSP?

    • @brtk7
      @brtk7 8 місяців тому +1

      In my opinion it is not uncommon as Christopher described in the video.
      You simply can make your code more error prone, buggy and be unaware that you break lsp. It’s bad design which isn’t something that makes your code impossible to run. And sometimes you do it on purpose to somehow glue everything together. Example could be c# array implements ICollection but when called (ICollection) Add on array it throws exception.

  • @rafaelocariz1384
    @rafaelocariz1384 8 місяців тому +2

    Honestly, I would have a base type named Shape, then a subtype Quads or Polygon4Sides and then Rectangle, Square, Trapezoids and Kites for example.

    • @ChristopherOkhravi
      @ChristopherOkhravi  8 місяців тому +1

      Such a hierarchy would probably not violate LSP 😊 Thanks for the thoughts.

  • @maorof
    @maorof 7 місяців тому

    great video

  • @IrizarryBrandon
    @IrizarryBrandon 7 місяців тому

    One thought: this reminded me of the "don't use polymorphism unless" video where you explain that subtyping based on variations in _data_ is a bad idea. This looks like a prime example of that, amirite?

  • @AFPinerosG
    @AFPinerosG 8 місяців тому +1

    I honestly don't see the issue. It just depends on the contract you define.
    If your contract is that the width and height of a rectangle is provides during initialization and then can't be changed, this isn't an issue.
    Rectangle r = new Square(3);
    That's it. Then you can get the area and other things from the rectangle contract.
    So, it's not a problem with the nature of the objects but with the nature of the contract.

  • @josephlagrange9531
    @josephlagrange9531 8 місяців тому

    Hi, Chris, your best friend here!

  • @praneetrane2993
    @praneetrane2993 8 місяців тому +1

    Welcome back

  • @faouzibenmabrouk4538
    @faouzibenmabrouk4538 8 місяців тому

    Welcome back !

  • @AfriandiHaryanto
    @AfriandiHaryanto 7 місяців тому

    I think, if we want to keep the relation (square is a rectangle) then width - height should be immutable and passed from constructor. The violation happen when we have to deal with mutability down the line. That's my opinion, CMIIW.

  • @andreipacurariu2013
    @andreipacurariu2013 8 місяців тому

    This is a well-known problem, indeed. However, the way I rationalize that a Rectangle can legitimately be viewed as a subtype of Square is by pointing out that the Liskov substitution principle is not in fact violated here unless the client breaks encapsulation, such as by calculating the area of the rectangle outside of the class instead of using a provided method or property such as Area. It can be argued that a 3x3 rectangle is a legitimate rectangle. There is no contract that a rectangle MUST have a width different from its height. While a rectangle CAN have a different width and height, there is no contract that it MUST, so a NxN rectangle is a perfectly valid rectangle. Therefore, the apparent Liskov substitution violation can only occur if the client assumes that the width and height of the rectangle MUST be different. For example, if the client multiplies the width of the rectangle by 2 and ASSUMES that this multiplies the area of the rectangle by 2 then the Liskov substitution principle would appear to be violated. However, this is only because the client broke encapsulation and calculated the area of the rectangle outside the class itself rather than using the GetArea() method or Area property. If, however, the client would not make such assumption, and instead would multiply the width by 2, or set it to whatever value they chose, and then use the provided Area property, then there would be no bug introduced in the code. This might violate the expectation and assumption around how the area evolves in relation to altering the width, so I can see why this would look like a Liskov substitution violation, however, if encapsulation is not violated, there should be no unexpected effects.
    Rectangle rect = new Square(); // 1x1
    rect.Width = 3; // 3x3
    Console.WriteLine(rect.Area); // 9 --> While we might expect it to be 3 this expectation is based on violating encapsulation and performing the calculation in our minds, outside of the provided Area property.
    In other words, while we assumed we're dealing with a 3x1 rectangle, we were in fact dealing with a 3x3 rectangle. This is still a perfectly valid rectangle which perfectly honors the contract that Area = Width x Height. The only thing being violated here is our assumption that we were dealing with a 3x1 rectangle when in fact we were dealing with a 3x3 rectangle. So, the way I see it, the contract is stricter for the square, in that its width MUST equal its height but there is no requirement or contract for a rectangle that its width MUST NOT equal its height. In other words, the contract for a Rectangle is not that its width and height MUST be different but merely that they CAN be different. Simply put, any square is a legitimate rectangle. So, as long as we don't materialize our expectations and assumptions in a way which violate encapsulation, then there should be no unexpected side-effects.
    However, I can see how a case might be made that Square and Rectangle are in fact both subtypes of Parallelogram which is in itself a subtype of Quadrilateral which is in itself a subtype of a Polygon. Thereby, a Square and a Rectangle are not interchangeable but instead the immediate superclass of both is a Parallelogram.

  • @karanbopche
    @karanbopche 8 місяців тому

    Thank you. 😊

  • @Mihes22
    @Mihes22 8 місяців тому

    Thank you 🙏

  • @Michaeljamieson10
    @Michaeljamieson10 8 місяців тому

    awesome stuff

  • @AshleyPillay99
    @AshleyPillay99 8 місяців тому

    In this example, the problem does seem to arise from the types being mutable. When some code get a Rectangle object & wants to mutate that object, it implicitly must have a strong concept of what a Rectangle is. If both the Square & Rectangle types are immutable, then only the creator of the object instances have a strong idea of what those instances are, but that is fine because they are creating the objects. Some other code that is handed a Rectangle can only inspect the object but not change it. This only requires weak knowledge of a Rectangle as a thing having Width & Height. It's fine for such code to retrieve that Width==Height, there's nothing special about that - it's dealing with a Rectangle it doesn't know/care about Squares. Perhaps this is why Squares are special case Rectangles in math? Because in math they're implicitly immutable?

  • @MatrixQ
    @MatrixQ 8 місяців тому

    I'd look at it from a data perspective. To define a square, I need 1 value: the length of the sides. To define a rectangle, I need an additional value. Therefore, I could inherit from square and add that value as a new property.

    • @ChristopherOkhravi
      @ChristopherOkhravi  8 місяців тому

      If Square does not expose separate properties for Width and Height that could work without violating Liskovs Substitution Principle. Thanks for the comment. 😊

  • @ashishkarn9283
    @ashishkarn9283 8 місяців тому

    wow. This is crystal clear. However, about the solution. I wonder if making both of the classes would solve the real problem or is just a work-around ? I believe that it would be a way to avoid the problem and it is not a perfect solution and ,so, either square or rectangle should never be considered a subtype of the other. @Christopher Okhravi sir, could you answer this?

  • @othmanothman4530
    @othmanothman4530 8 місяців тому

    love it man
    glad u r back

  • @BlindVirtuoso
    @BlindVirtuoso 8 місяців тому +2

    I think it's wrong to treat inheritance as "is-a" relationship. It's clear that square is a rectangle, but that doesn't work. Instead inheritance is kind of "behaves like" relationship. In that sense square is a rectangle but "does not behave like" rectangle from width and height point of view :)

    • @ChristopherOkhravi
      @ChristopherOkhravi  8 місяців тому +1

      Hmm. 🤔Would you care to elaborate? Inheritance does indeed establish a subtype relationship (in virtually all OO languages) such that objects of the subtype can be used when objects of the super type is expected. This means that establish what, for most intents and purposes, can be thought of as an is-a relationship.

    • @BlindVirtuoso
      @BlindVirtuoso 8 місяців тому +1

      @@ChristopherOkhraviOk. Is square a rectangle?) It definitely is. So if we think of inheritance as "is-a" relationship, then our square should be able to inherit from a rectangle, but you showed in the video that it violates LSP. So a contradiction here in the sense that square IS A rectangle geometrically , inheritance is "IS-A" relationship, square cannot be inherited from a rectangle. So my suggestion is that inheritance is "behaves like" relationship. I hope it's a bit clearer now.

  • @uscorporation
    @uscorporation 8 місяців тому

    Good video! I still have a question. I understood it is not good to have a Square as a parent of a Rectangle if a Square has both Width and Height. But what if Square has only one property A, and Rectangle is inherited and extended by property B? Then when we are calculating the square of Square we have A*A, and overridden function in the Rectangle as A*B

    • @ChristopherOkhravi
      @ChristopherOkhravi  8 місяців тому

      Great question. This would probably work for the "setter" but probably not for the "getter". If Rectangle is a subtype of Square, how would you override the method "getSide" in Rectangle? More generally, the question we should ask is: Can the subtype inherit or override all the public methods of its super type without behaving in an unexpected manner, if someone expects an object that behaves like the supertype? Thank you for the question 😊

  • @lima1877
    @lima1877 8 місяців тому

    You’re back? Wow

  • @silberwolfSR71
    @silberwolfSR71 8 місяців тому +1

    09:09 If both types are immutable, they are able to establish their invariants during construction (or fail) and guarantee that they will hold throughout the lifetime of the object.
    There is still a danger of violating LSP though, if we aren't careful with the contracts of the two types and the relationship between them.
    For instance, having square be the supertype can lead to problems like e.g. some other component that computes the area of a square using the width^2 formula, and getting a wrong result when doing the same for a rectangle.
    So rectangle should definitely be the supertype. If we do that, make both types immutable and use the common mathematical conceptions of what it means to be a square, respectively rectangle, then I think we're in the clear.
    The _is-a_ relationship holds, we just need to define square and rectangle correctly.
    I feel like Liskov is the most critical part of SOLID: all the other guidelines are nice and help you keep your code in order; if you violate them you'll have a hard time when you want to change something.
    If you violate Liskov, your code is broken. By definition, assumptions are violated. If anything at all depends on those assumptions, you have a bug.
    Relating back to your previous video on data-invariant polymorphism, one could argue that there is no need for the concept of a square, right? Granted, the relationship is ancestral rather than fraternal in this case, but the same arguments apply, I would think. (BTW I disagree with that position; I like having squares even if they're just custom rectangles)

    • @ChristopherOkhravi
      @ChristopherOkhravi  8 місяців тому +1

      Wow. Such an elegant comment. Thank you very much for sharing this. We are in agreement on all accounts 😊 Especially about LSP being the most important principle.

    • @Mig440
      @Mig440 8 місяців тому

      Or you could forgo inheritance wholesale and just never abstract, although that would be more procedural oriented atleast you can be very specific in you methods and only accept the specific types you want to operate on. Maybe that is the reason why real world software projects are more procedural since atleast it does not pretend to be universalizable/abstractable but concrete everytime since more often than not, the invariants of any concept is unclear at the inception of the project and probably remains so until someone finds out what those invariants are, but then it is too late and risky to reify those constraints and so it never gets done🤣

  • @BlindVirtuoso
    @BlindVirtuoso 8 місяців тому +1

    Hi Chris. Highly appreciate. Please Don't disapperar again. By the way, I think you should start writing a book on object-oriented software design :)

  • @bovineox1111
    @bovineox1111 7 місяців тому

    I’m not sure I agree this violates Liskov. Just because the super type has width and height, where is it written that changing either cannot change the other? It may even be that conceptually this is exactly right. The thing that confuses this is the relationship we attach because of the names of the classes. If they were called Parent and child and the props are Number1 and Number2. Is it still incorrect that changing either changes the other? Isn’t his about inversion of control? We have an expectation that the two values are independent but why? Again it comes down to naming - if the parent type were shape with a width and height we wouldn’t be saying this is wrong.
    You can definitely substitute squares for rectangles and this compiles and also functions fine - why does it not work? Nothing in the pure contracts discussed here enforces the two properties to he independent, it’s only our minds putting an expectation on Rectangles and squares. So what if changing the width also changes the height?

  • @r1konTheAutomator
    @r1konTheAutomator 8 місяців тому

    Havent watched the video yet, stopped after intro - the way I would do it out of these two is rectangle being a sub type of square. Having a default of all equal sides and angles is basically the definition of a sensible default. Lets see what happens 😅

    • @ChristopherOkhravi
      @ChristopherOkhravi  8 місяців тому

      Thank you very much for sharing this! Makes for a much more interesting journey 😊 Let me know if you have questions along the way.

  • @Wickerman1989
    @Wickerman1989 8 місяців тому

    Great example. Is this also a violation of the Liskov-substitution principle? I don't expect an elaborate answer :)

    • @Wickerman1989
      @Wickerman1989 8 місяців тому +1

      Uh oh, okay I made it to the end, case closed :D

    • @ChristopherOkhravi
      @ChristopherOkhravi  8 місяців тому +1

      😊😊 But good on you man for spotting that before I said it. 👏

  • @dominikvonlavante6113
    @dominikvonlavante6113 8 місяців тому

    Liskov Substitution principle: always go from abstract to concrete

  • @RiazShageer
    @RiazShageer 8 місяців тому

    Holy crap this was a good video....

  • @arminium56
    @arminium56 8 місяців тому

    Awesome 🎉

  • @pathakvivek7865
    @pathakvivek7865 8 місяців тому

    Awesome.

  • @thefattysplace
    @thefattysplace 7 місяців тому

    What if you use reflection to see what type the compiler is expecting in the sub class square, and only update the height if it is expecting square and not rectangle? I appreciate this pretty isn't possible from system.reflection, but if it was, it would solve the problem!

  • @zeocamo
    @zeocamo 8 місяців тому

    i never got this problem as i don't use inheritance in my code, it is a GREAT a idea for planning on a whiteboard, but when you need to change the program, it is the worst idea in the world.
    and in this example that could be the Rectangle become a heart form, and now you need to change 20% of the program.

  • @karlmehltretter2677
    @karlmehltretter2677 6 місяців тому

    If I derive a class "SlowRectangle" from "Rectangle" that adds 1 second artificial delay to every method, will I break LSP ? It depends on if there is a guaranteed execution time in the specification, that is now broken.

  • @julians.2597
    @julians.2597 8 місяців тому +1

    The solution is composition

    • @ChristopherOkhravi
      @ChristopherOkhravi  8 місяців тому

      Yes, but we must still understand LSP because composition without subtyping is less powerful than composition with subtyping 😊😊

  • @guilhermecampos8313
    @guilhermecampos8313 8 місяців тому

    Doesn't that mean that method overriding always violate LSP?

  • @VetaliySl
    @VetaliySl 8 місяців тому

    The guy is a little crazy but definitely not boring 😄

  • @adambickford8720
    @adambickford8720 8 місяців тому

    While both have the same properties, they do not have the same behavior and mutability makes this problematic.
    Imagine instead both are simple immutable types/structs/records. You could then have a service that has a `withWidth(Rectangle | Square shape, int w)` but no matter what it's passed you're getting back a `Rectangle` because that's the only contract it can provably honor. While a `withSize(Rectangle | Square shape, int size)` _could_ give you back either, a square is more useful/accurate and it's now up to the caller to decide how fungible the 2 types are.
    (I'm assuming a discriminated union, but there's other ways to model it)

  • @Mig440
    @Mig440 8 місяців тому

    Or maybe.. just maybe introduced union types in dotnet (youre writing c# code anyway in the examples)

  • @eduardoandrescastilloperer4810
    @eduardoandrescastilloperer4810 5 місяців тому

    It goes in the square whole 😅

  • @janailtongoncalvesdesouza4160
    @janailtongoncalvesdesouza4160 8 місяців тому

    Posted the question to chatgpt and he gave me an answer with square extending rectangle... your job is safe lol

  • @InforSpirit
    @InforSpirit 8 місяців тому

    This one is direct violation of last video...
    Since I pondered this some time I come to conlusion that there can be only Rectangle-class and instance of square. If later someone want to transform it to rectangle, it is Clients problem in that point. (immutable typing if really needed)

    • @ChristopherOkhravi
      @ChristopherOkhravi  8 місяців тому

      Thank you for the comment. Great question. Just because it is possible to create a subtype relationship (based on behavioral variations) that violates LSP doesn’t necessarily mean that ALL subtype relationships (with behavioral variation) will violate LSP. See what I mean?

    • @InforSpirit
      @InforSpirit 8 місяців тому

      @@ChristopherOkhravi Not really sure that I Understand. I come clearly from practical standpoint so maybe you should define subtype by rules of stasis theory. if subtype include instances and SubClasses then I understand all.
      Academic question: Can you proof that LSP will give benefit in every use cases? I bet it is as hard to proof as halting problem.
      Because now you have subjective parameter of 'benefit'
      How you can universally define benefit? You can't because it is derivate of relation of infinite combinations.
      Circle-Ellipsis proofs that in atleast in some cases adhearing to LSP make everything more difficullt (less benefit)
      In my experience interface contracts (methods) is above all other rules. Client gives information to method and it returns type that is defined in contract. If it is dimensions, Rectangle returns x = n, y = n
      If you have no interface, there could not be any problems, but that point it is useless structure.
      Does Circle or any geometric shape has any behaverion in first place? IS this just primacy bias (thing that we learn first has biased weighting in namespace) whispering lies? You have condition to think circles and ellipsis as different group from kindergarden to today. You had the calculation power to define Area of circle, but circle If you rethink had no function by themself.
      And thats why fundamentally they should be immutable type. (In real application you just return some abstract vector cloud and doesn't care what Client do for that)

  • @bovineox1111
    @bovineox1111 7 місяців тому

    You done’t have both classes. The Squareness is a property of the rectangle and rectangles would include the ability to check squareness and set squareness.

  • @shahaffrsshahaffrs5190
    @shahaffrsshahaffrs5190 7 місяців тому

    Solved: IPolygon -> IFourSidedPolygon -> IRightAnglePolygon -> Square, Rectangle 😂

  • @loam
    @loam 6 місяців тому

    I don't see violation there, because subtype modifies further the original behaviour, and it's normal
    In this case it's not some completely unexpected behaviour for rectangle.
    Update:
    And if you instantiate the square into base rectangle object, you know there's underlying square object, and when you use it, it might implement its operations accordingly to itself, and this is normal, it is what polymorphism for. If you have list of rectangles, some of them being just rectangles, and some of them being Squares, you know for squares they'll do their own logic, when you set values... Ok, ok, now I see 😂 So, if you set Width and Height for them to some different values, and expect all of them to have those set to specific different values, that will not happen.
    Ok, may be some violation is having place here 😂
    I guess this is related to Liskov substitution principle.
    For instance, I saw examples in C#, where subtype doesn't implement method its base class has, because in the context of subtype that method was not applicable, and throws NotImplemented exception. In this case I see violation, because it's kinda like you can't say the subtype is a base type, it's only partially.

  • @alexandernava9275
    @alexandernava9275 8 місяців тому

    Still watching. I would just make a state check of square, as square is a state of a rectangle. That way the behavior is separate from the state.

  • @AnalogDude_
    @AnalogDude_ 8 місяців тому

    lol, good video.