Circle–ellipse Problem
   HOME

TheInfoList



OR:

The circle–ellipse problem in
software development Software development is the process of designing and Implementation, implementing a software solution to Computer user satisfaction, satisfy a User (computing), user. The process is more encompassing than Computer programming, programming, wri ...
(sometimes called the square–rectangle problem) illustrates several pitfalls which can arise when using subtype polymorphism in object modelling. The issues are most commonly encountered when using
object-oriented programming Object-oriented programming (OOP) is a programming paradigm based on the concept of '' objects''. Objects can contain data (called fields, attributes or properties) and have actions they can perform (called procedures or methods and impl ...
(OOP). By definition, this problem is a violation of the Liskov substitution principle, one of the
SOLID Solid is a state of matter where molecules are closely packed and can not slide past each other. Solids resist compression, expansion, or external forces that would alter its shape, with the degree to which they are resisted dependent upon the ...
principles. The problem concerns which subtyping or
inheritance Inheritance is the practice of receiving private property, titles, debts, entitlements, privileges, rights, and obligations upon the death of an individual. The rules of inheritance differ among societies and have changed over time. Offi ...
relationship should exist between classes which represent
circle A circle is a shape consisting of all point (geometry), points in a plane (mathematics), plane that are at a given distance from a given point, the Centre (geometry), centre. The distance between any point of the circle and the centre is cal ...
s and
ellipse In mathematics, an ellipse is a plane curve surrounding two focus (geometry), focal points, such that for all points on the curve, the sum of the two distances to the focal points is a constant. It generalizes a circle, which is the special ty ...
s (or, similarly,
squares In geometry, a square is a regular polygon, regular quadrilateral. It has four straight sides of equal length and four equal angles. Squares are special cases of rectangles, which have four equal angles, and of rhombuses, which have four equal si ...
and
rectangle In Euclidean geometry, Euclidean plane geometry, a rectangle is a Rectilinear polygon, rectilinear convex polygon or a quadrilateral with four right angles. It can also be defined as: an equiangular quadrilateral, since equiangular means that a ...
s). More generally, the problem illustrates the difficulties which can occur when a base class contains methods which mutate an object in a manner which may invalidate a (stronger) invariant found in a derived class, causing the Liskov substitution principle to be violated. The existence of the circle–ellipse problem is sometimes used to criticize object-oriented programming. It may also imply that hierarchical taxonomies are difficult to make universal, implying that situational classification systems may be more practical.


Description

It is a central tenet of
object-oriented analysis and design Object-oriented analysis and design (OOAD) is a technical approach for analyzing and designing an application, system, or business by applying object-oriented programming, as well as using visual modeling throughout the software development pro ...
that subtype polymorphism, which is implemented in most object-oriented languages via
inheritance Inheritance is the practice of receiving private property, titles, debts, entitlements, privileges, rights, and obligations upon the death of an individual. The rules of inheritance differ among societies and have changed over time. Offi ...
, should be used to model object types that are subsets of each other; this is commonly referred to as the
is-a In knowledge representation, ontology components and ontology engineering, including for object-oriented programming and design, is-a (also written as is_a or is a) is a subsumptive relationship between abstractions (e.g., types, classes), wh ...
relationship. In the present example, the set of circles is a subset of the set of ellipses; circles can be defined as ellipses whose major and minor axes are the same length. Thus, code written in an object-oriented language that models shapes will frequently choose to make a subclass of , i.e. inheriting from it. A subclass must provide support for all behaviour supported by the super-class; subclasses must implement any
mutator method In computer science, a mutator method is a method used to control changes to a variable. They are also widely known as setter methods. Often a setter is accompanied by a getter, which returns the value of the private member variable. They are also ...
s defined in a base class. In the present case, the method alters the length of one of its axes in place. If inherits from , it must also have a method , but the result of this method would be to change a circle into something that is no longer a circle. The class cannot simultaneously satisfy its own invariant and the behavioural requirements of the method. A related problem with this inheritance arises when considering the implementation. An ellipse requires more states to be described than a circle, because the former needs attributes to specify the length and rotation of the major and minor axes whereas a circle needs only a radius. It may be possible to avoid this if the language (such as Eiffel) makes constant values of a class, functions without arguments, and data members interchangeable. Some authors have suggested reversing the relationship between circle and ellipse, on the grounds that an ellipse is a circle with more abilities. Unfortunately, ellipses fail to satisfy many of the invariants of circles; if has a method , must now provide it, too.


Possible solutions

One may solve the problem by: * changing the model * using a different language (or an existing or custom-written extension of some existing language) * using a different paradigm Exactly which option is appropriate will depend on who wrote and who wrote . If the same author is designing them both from scratch, then the author will be able to define the interface to handle this situation. If the object was already written, and cannot be changed, then the options are more limited.


Change the model


Return success or failure value

Allow the objects to return a "success" or "failure" value for each modifier or raise an exception on failure. This is usually done in the case of file I/O, but can also be helpful here. Now, works, and returns "true", while simply returns "false". This is in general good practice, but may require that the original author of anticipated such a problem, and defined the mutators as returning a value. Also, it requires the client code to test the return value for support of the stretch function, which in effect is like testing if the referenced object is either a circle or an ellipse. Another way to look at this is that it is like putting in the contract that the contract may or may not be fulfilled depending on the object implementing the interface. Eventually, it is only a clever way to bypass the Liskov constraint by stating up-front that the post condition may or may not be valid. Alternately, could throw an exception (but depending on the language, this may also require that the original author of declare that it may throw an exception).


Return the new value of X

This is a similar solution to the above, but is slightly more powerful. now returns the new value of its X dimension. Now, can simply return its current radius. All modifications must be done through , which preserves the circle invariant.


Allow for a weaker contract on Ellipse

If the interface contract for states only that "stretchX modifies the X axis", and does not state "and nothing else will change", then could simply force the X and Y dimensions to be the same. and both modify both the X and Y size.


Convert the Circle into an Ellipse

If is called, then changes itself into an . For example, in
Common Lisp Common Lisp (CL) is a dialect of the Lisp programming language, published in American National Standards Institute (ANSI) standard document ''ANSI INCITS 226-1994 (S2018)'' (formerly ''X3.226-1994 (R1999)''). The Common Lisp HyperSpec, a hyperli ...
, this can be done via the method. This may be dangerous, however, if some other function is expecting it to be a . Some languages preclude this type of change, and others impose restrictions on the class to be an acceptable replacement for . For languages that allow implicit conversion like C++, this may only be a partial solution solving the problem on call-by-copy, but not on call-by-reference.


Make all instances constant

One can change the model so that instances of the classes represent constant values (i.e., they are
immutable In object-oriented (OO) and functional programming, an immutable object (unchangeable object) is an object whose state cannot be modified after it is created.Goetz et al. ''Java Concurrency in Practice''. Addison Wesley Professional, 2006, Secti ...
). This is the implementation that is used in purely functional programming. In this case, methods such as must be changed to yield a new instance, rather than modifying the instance they act on. This means that it is no longer a problem to define , and the inheritance reflects the mathematical relationship between circles and ellipses. A disadvantage is that changing the value of an instance then requires an assignment, which is inconvenient and prone to programming errors, e.g., A second disadvantage is that such an assignment conceptually involves a temporary value, which could reduce performance and be difficult to optimise.


Factor out modifiers

One can define a new class , and put the modifiers from in it. The only inherits queries from . This has a disadvantage of introducing an extra class where all that is desired is specify that does not inherit modifiers from .


Impose preconditions on modifiers

One can specify that is only allowed on instances satisfying , and will otherwise throw an exception. This requires anticipation of the problem when Ellipse is defined.


Factor out common functionality into an abstract base class

Create an abstract base class called and put methods that work with both s and s in this class. Functions that can deal with either type of object will expect an , and functions that use - or -specific requirements will use the descendant classes. However, is then no longer an subclass, leading to the "a is not a sort of " situation described above.


Drop all inheritance relationships

This solves the problem at a stroke. Any common operations desired for both a Circle and Ellipse can be abstracted out to a common interface that each class implements, or into
mixin In object-oriented programming languages, a mixin (or mix-in) is a class that contains methods for use by other classes without having to be the parent class of those other classes. How those other classes gain access to the mixin's methods depe ...
s. Also, one may provide conversion methods like , which returns a mutable Ellipse object initialized using the circle's radius. From that point on, it is a separate object and can be mutated separately from the original circle without issue. Methods converting the other way need not commit to one strategy. For instance, there can be both and , and any other strategy desired.


Combine class Circle into class Ellipse

Then, wherever a circle was used before, use an ellipse. A circle can already be represented by an ellipse. There is no reason to have class unless it needs some circle-specific methods that can't be applied to an ellipse, or unless the programmer wishes to benefit from conceptual and/or performance advantages of the circle's simpler model.


Inverse inheritance

Majorinc proposed a model that divides methods on modifiers, selectors and general methods. Only selectors can be automatically inherited from superclass, while modifiers should be inherited from subclass to superclass. In general case, the methods must be explicitly inherited. The model can be emulated in languages with
multiple inheritance Multiple inheritance is a feature of some object-oriented computer programming languages in which an object or class can inherit features from more than one parent object or parent class. It is distinct from single inheritance, where an object ...
, using
abstract class In object-oriented programming, a class defines the shared aspects of objects created from the class. The capabilities of a class differ between programming languages, but generally the shared aspects consist of state ( variables) and behavior ( ...
es.Kazimir Majorinc, Ellipse-Circle Dilemma and Inverse Inheritance, ITI 98
Proceedings of the 20th International Conference of Information Technology Interfaces, Pula, 1998
/ref>


Change the programming language

This problem has straightforward solutions in a sufficiently powerful OO programming system. Essentially, the circle–ellipse problem is one of synchronizing two representations of type: the ''de facto'' type based on the properties of the object, and the formal type associated with the object by the object system. If these two pieces of information, which are ultimately only bits in the machine, are kept synchronized so that they say the same thing, everything is fine. It is clear that a circle cannot satisfy the invariants required of it while its base ellipse methods allow mutation of parameters. However, the possibility exists that when a circle cannot meet the circle invariants, its type can be updated so that it becomes an ellipse. If a circle which has become a ''de facto'' ellipse doesn't change type, then its type is a piece of information which is now out of date, reflecting the history of the object (how it was once constructed) and not its present reality (what it has since mutated into). Many object systems in popular use are based on a design which takes it for granted that an object carries the same type over its entire lifetime, from construction to finalization. This is not a limitation of OOP, but rather of particular implementations only. The following example uses the
Common Lisp Object System The Common Lisp Object System (CLOS) is the facility for object-oriented programming in American National Standards Institute, ANSI Common Lisp. CLOS is a powerful dynamic programming language, dynamic object system which differs radically from t ...
(CLOS) in which objects can change class without losing their identity. All variables or other storage locations which hold a reference to an object continue to hold a reference to that same object after it changes class. The circle and ellipse models are deliberately simplified to avoid distracting details which are not relevant to the circle–ellipse problem. An ellipse has two semi-axes called and in the code. Being an ellipse, a circle inherits these, and also has a property, which value is equal to that of the axes (which must, of course, be equal to each other). (defgeneric check-constraints (shape)) ;; The accessors on shape objects. Constraints on objects ;; need to be checked after either axis value is set. (defgeneric h-axis (shape)) (defgeneric (setf h-axis) (new-value shape) (:method :after (new-value shape) (check-constraints shape))) (defgeneric v-axis (shape)) (defgeneric (setf v-axis) (new-value shape) (:method :after (new-value shape) (check-constraints shape))) (defclass ellipse () ((h-axis :type real :accessor h-axis :initarg :h-axis) (v-axis :type real :accessor v-axis :initarg :v-axis))) (defclass circle (ellipse) ((radius :type real :accessor radius :initarg :radius))) ;;; ;;; A circle has a radius, but also a h-axis and v-axis that ;;; it inherits from an ellipse. These must be kept in sync ;;; with the radius when the object is initialized and ;;; when those values change. ;;; (defmethod initialize-instance :after ((c circle) &key radius) (setf (radius c) radius)) ;; via the setf method below (defmethod (setf radius) :after ((new-value real) (c circle)) ;; We use SLOT-VALUE, rather than the accessors, to avoid changing ;; class unnecessarily between the two assignments; as the circle ;; will have different h-axis and v-axis values between the ;; assignments, and then the same values after assignments. (setf (slot-value c 'h-axis) new-value (slot-value c 'v-axis) new-value)) ;;; ;;; After an assignment is made to the circle's ;;; h-axis or v-axis, a change of type is necessary, ;;; unless the new value is the same as the radius. ;;; (defmethod check-constraints ((c circle)) (unless (= (radius c) (h-axis c) (v-axis c)) (change-class c 'ellipse))) ;;; ;;; Ellipse changes to a circle if accessors ;;; mutate it such that the axes are equal, ;;; or if an attempt is made to construct it that way. ;;; (defmethod initialize-instance :after ((e ellipse) &key) (check-constraints e)) (defmethod check-constraints ((e ellipse)) (when (= (h-axis e) (v-axis e)) (change-class e 'circle))) ;;; ;;; Method for an ellipse becoming a circle. In this metamorphosis, ;;; the object acquires a radius, which must be initialized. ;;; There is a "sanity check" here to signal an error if an attempt ;;; is made to convert an ellipse which axes are unequal ;;; with an explicit change-class call. ;;; The handling strategy here is to base the radius off the ;;; h-axis and signal an error. ;;; This doesn't prevent the class change; the damage is already done. ;;; (defmethod update-instance-for-different-class :after ((old-e ellipse) (new-c circle) &key) (setf (radius new-c) (h-axis old-e)) (unless (= (h-axis old-e) (v-axis old-e)) (error "ellipse ~s can't change into a circle because it's not one!" old-e))) This code can be demonstrated with an interactive session, using the CLISP implementation of Common Lisp. $ clisp -q -i circle-ellipse.lisp (make-instance 'ellipse :v-axis 3 :h-axis 3) # (make-instance 'ellipse :v-axis 3 :h-axis 4) # (defvar obj (make-instance 'ellipse :v-axis 3 :h-axis 4)) OBJ (class-of obj) # (radius obj) *** - NO-APPLICABLE-METHOD: When calling # with arguments (#), no method is applicable. The following restarts are available: RETRY :R1 try calling RADIUS again RETURN :R2 specify return values ABORT :R3 Abort main loop Break 1 :a (setf (v-axis obj) 4) 4 (radius obj) 4 (class-of obj) # 0 (setf (radius obj) 9) 9 1 (v-axis obj) 9 2 (h-axis obj) 9 3 (setf (h-axis obj) 8) 8 4 (class-of obj) # 5 (radius obj) *** - NO-APPLICABLE-METHOD: When calling # with arguments (#), no method is applicable. The following restarts are available: RETRY :R1 try calling RADIUS again RETURN :R2 specify return values ABORT :R3 Abort main loop Break 1 6 :a 7


Challenge the premise of the problem

While at first glance it may seem obvious that a Circle is-an Ellipse, consider the following analogous code. class Person Now, a prisoner is obviously a person. So logically, a sub-class can be created: class Prisoner extends Person Also obviously, this leads to trouble, since a prisoner is ''not'' free to move an arbitrary distance in any direction, yet the contract of the class states that a Person can. Thus, the class could better be named . If that were the case, then the idea that is clearly wrong. By analogy, then, a Circle is an Ellipse, because it lacks the same degrees of freedom as an Ellipse. Applying better naming, then, a Circle could instead be named and an ellipse could be named . With such names it is now more obvious that should extend , since it adds another property to it; whereas has a single diameter property, has two such properties (i.e., a major and a minor axis length). This strongly suggests that inheritance should never be used when the sub-class restricts the freedom implicit in the base class, but should only be used when the sub-class adds extra detail to the concept represented by the base class as in 'Monkey' is-an 'Animal'. However, stating that a prisoner can not move an arbitrary distance in any direction and a person can is a wrong premise once again. Any object which is moving to any direction can encounter obstacles. The right way to model this problem would be to have a contract. Now, when implementing walkToDirection for the subclass Prisoner, you can check the boundaries and return proper walk results.


Invariance

One may, conceptually, consider a and to both be mutable container types, aliases of and respectively. In this case, may be considered a subtype of . The type in can be both written to and read from, implying that it is neither covariant nor contravariant, and is instead invariant. Therefore, is not a subtype of , nor vice versa.


References

* Robert C. Martin
The Liskov Substitution Principle
C++ Report, March 1996.


External links

* https://web.archive.org/web/20150409211739/http://www.parashift.com/c++-faq-lite/proper-inheritance.html#faq-21.6 A popular C++ FAQ site by Marshall Cline. States and explains the problem.
''Constructive Deconstruction of Subtyping''
by
Alistair Cockburn Alistair Cockburn ( ) is an American computer scientist, known as one of the initiators of the agile movement in software development. He cosigned (with 16 others) the Manifesto for Agile Software Development. Life and career Cockburn starte ...
on his own web-site. Technical/mathematical discussion of typing and subtyping, with applications to this problem. * * http://orafaq.com/usenet/comp.databases.theory/2001/10/01/0001.htm Beginning of a long thread (follow the ''Maybe reply:'' links) on Oracle FAQ discussing the issue. Refers to writings of C.J. Date. Some bias towards
Smalltalk Smalltalk is a purely object oriented programming language (OOP) that was originally created in the 1970s for educational use, specifically for constructionist learning, but later found use in business. It was created at Xerox PARC by Learni ...
.
LiskovSubstitutionPrinciple
at
WikiWikiWeb The WikiWikiWeb is the first wiki, or user-editable website. It was launched on 25 March 1995 by programmer Ward Cunningham and has been a read-only archive since 2015. The name ''WikiWikiWeb'' originally also applied to the wiki software that o ...

''Subtyping, Subclassing, and Trouble with OOP''
an essay discussing a related problem: should sets inherit from bags?
''Subtyping by Constraints in Object-Oriented Databases''
an essay discussing an extended version of the circle–ellipse problem in the environment of object-oriented databases. {{DEFAULTSORT:Circle-ellipse problem Object-oriented programming