Type Flexibility is the ability for an entity to change its type in response to an event, even if that change was not anticipated when the type’s interface was designed. In Object Oriented Programming, the inability for an object to change its type is sometimes referred to as the square-rectangle or circle-ellipse problem. In this problem, a square is modified so that its width no longer matches its height, resulting in it no longer being a square. If the change to the square’s width is applied in the form of a state mutation, however, the square cannot change its type to match its state, and so the program must be designed to account for this possibility. There are four options to overcome the square-rectangle problem. The first is to use Immutable Values, such that the modification takes the form of a new value that may assume the type of a rectangle. The second is to generalize the type to that of a rectangle, even in the case of a matching width and height, such that the modification may be applied without issue. The third is to correct the squares hight to match its width in order to maintain the Invariant of the squares diminsions. The final option is to fail the change, as with the
NotImplementedException in C#.
The four options.
If the square is immutable, then every “modification” would be applied by creating a new shape from the current one with some changed parts. Since the value is new anyways, it is easier to simply create a rectangle, though this doesn’t always solve the flexibility issue. If the square’s Contract, that being its interface of documentation, guarentees that the setWidth command will return a square, then it does not have the flexibility to suddenly return a rectangle without potentially causing issues. This problem is similar to that of Object Oriented Programming, where the interface must account for the change ahead of time.
The Object Oriented analogue to returning a Rectangle during a modification is to generalize the Square into just another Rectangle. Instead of having a Square type with its own methods and properties, there would be a Rectangle type with conditional behaviors when it finds that it happens to be a Square. It may do this with branching or by the State Pattern, such that the objects shape may change without the objects type having to change. This is similar to the immutable solution where the solution needs to be selected in advance. If a Square is already implemented and code already relies on its existence and special behavior then the Sqaure cannot be generalized later without rewriting some external code.
Another option is to treat the change as a validity problem, and to modify the height to match the width in order to avoid breaking the Invariant that a Square’s width match its height. This option allows for the width to be modified and allows for the Square to keep its type, but it introduces a consequence of which not all code that sets the Squares width might be aware. Some code might have just set the Squares height, thinking of the Square as a rectangle, and that code might be relying on the shape maintaining its hight for some purpose.
Finally, the Square could fail when the width is changed specifically, which the Square may do by throwing an error or by quietly not modifying the value. If the modification throws an error, then the Square has broken the Liskov Substitution Principle, and cannot be considered a Rectangle, even if the use of an error tricks the compiler into allowing the code to reach production. There are some cases where this may be justified, as with deprecated, outdated methods, but should be avoided as a first option. Similarly, if the Square fails to modify its width silently, then you have the same issue as with silently changing the Squares height to match its width, expect that it is even more likely that the requesting code will not suspect the outcome, cause if they did, they wouldn’t have requested the modification.