CSC/ECE 517 Fall 2010/ch7 7g mr: Difference between revisions

From Expertiza_Wiki
Jump to navigation Jump to search
No edit summary
No edit summary
Line 1: Line 1:
= Square-Rectangle Anti-Pattern =
= Square-Rectangle Anti-Pattern =


The Square-Rectangle [http://en.wikipedia.org/wiki/Anti-pattern Anti-Pattern] is a software development problem that can occur in [http://en.wikipedia.org/wiki/Object_oriented_programming Object Oriented Programming] when using inheritance.
The Square-Rectangle [http://en.wikipedia.org/wiki/Anti-pattern Anti-Pattern] is a software development problem that can occur in [http://en.wikipedia.org/wiki/Object_oriented_programming Object Oriented Programming] with inheritance when a base class contains methods which invalidate the derived class, and thereby violate the [http://en.wikipedia.org/wiki/Liskov_substitution_principle Liskov Substitution Principle].


== Description ==
== Description ==


Consider classes Rectangle and Square. One might reasonably define the Square class as a specific type of the Rectangle class since it passes the 'is a' test (Square is a Rectangle).  This leads to several problems however, since subclasses are supposed to support all the methods of their super class.
Consider classes Rectangle and Square. By the [http://en.wikipedia.org/wiki/Is-a 'is a'] test, one might reasonably define the Square class as a specialization of the Rectangle class (Square is a Rectangle), where all sides are of equal lengthBut suppose Rectangle defines a constructor that takes two arguments, x and y, for the lengths of the sides. How can Square provide an implementation for this constructor?  If the values passed for x and y are not the same, it would be unable to construct an instance of Square that satisfied the input criteria.


== Examples ==
The situation is worse for modifier methods on the base class. Suppose Rectangle has a method setX which modifies only two sides of the rectangle. When called on a Square, it would cause the object to no longer be a square. This can be particularly problematic when new modifiers are added to an existing base class after it has been subclassed. If it has been subclassed improperly, the subclasses may be broken by the new functionality in the base class.


=== Constructor ===
== Example ==


Conceptually this problem happens when the base class contains more information than the derived class. In the example of the Square and the Rectangle, both have two sides x and y, but in the case of a Rectangle the sides may be of different length and therefore two values must be maintained.
The following example illustrates the problem. In this case, Square has decided not to implement a constructor defined in Rectangle. But there is still a problem with the setX modifier:


Within the code the base class Rectangle may have a constructor that accepts two parameters, x and y, to set the length of its sides.  How is the Square class to implement this constructor? It could override the constructor but if the values passed for x and y are not the same, it would be unable to construct an instance of Square that satisfied the input criteria.
  class Rectangle  
  {
      protected int x, y;
 
      Rectangle() {}
   
      Rectangle(int x, int y) {
          this.x = x;
          this.y = y;
      }
 
      void setX(int x) {
          this.x = x;
      }
 
      void print() {
          System.out.println(this.getClass().getName() + " has sides of length " + x + " and " + y);
      }
  }
 
  public class Square extends Rectangle
  {
      Square(int x) {
          this.x = x;
          this.y = x;
      }
 
      public static void main(String[] args) {
          Square s = new Square(10);
          s.print();
          s.setX(5);
          s.print();
      }
  }


=== Mutating Methods ===
This produces the following output:


The situation is likewise for mutating methods, which modif an object which was already instantiated. For example the Rectangle class may have a method setX which modifies the size of only one side of the rectangle.  When called on a Square, it would cause the object to no longer be a square. 
  Square has sides of length 10 and 10
 
  Square has sides of length 5 and 10
This becomes particularly problematic when new methods are added to an existing base class after it has been subclassed.  If it has been subclassed improperly, the subclasses may be broken by the new functionality in the base class.


== Alternatives ==
== Alternatives ==
Line 31: Line 63:
=== Absent constructor ===
=== Absent constructor ===


The derived Rectangle class could just choose not to implement the constructor that takes arguments x and y.  This would prevent any caller from instantiating an instance of Square with arguments x and y.  This is confusing though, since one normally expects a subclass to support all the constructors of the base class.
The derived Rectangle class could choose to not provide an implementation for the constructor that takes arguments x and y.  This would prevent any caller from instantiating an instance of Square with arguments x and y.  This design maybe controversial since one normally expects a subclass to support all the constructors of the base class.


=== Throw exception ===
=== Throw exception ===
Line 51: Line 83:
If the constructor is called with different values for x and y, or if the setx method is called, the implementation on Square could instantiate an instance of Rectangle and return that instead. This requires the methods to declare a return type of the base class.  This could have unexpected consequences on the program.
If the constructor is called with different values for x and y, or if the setx method is called, the implementation on Square could instantiate an instance of Rectangle and return that instead. This requires the methods to declare a return type of the base class.  This could have unexpected consequences on the program.


=== Make all classes immutable ===
=== Use separate immutable and mutable classes ===
 
In this approach, the Rectangle class is [http://en.wikipedia.org/wiki/Immutable_object immutable] and therefore its methods cannot change derived instances, they can only return new instances of Rectangle.  A separate MutableRectangle class could be defined and all the modifiers put into it.
 
As in some of the previous solutions, this has the disadvantage of requiring significant modifications to the design of the base class.  It also requires the calling code to perform more assignments and may result in more memory being consumed by all the immutable instances around.
 
=== Initialize base class as mutable/immutable ===


In this approach, the classes are [http://en.wikipedia.org/wiki/Immutable_object immutable] and therefore the methods cannot change the existing class, they can only return new instances of the proper class. As in some of the previous solutions, this has the disadvantage of requiring significant modifications to the design of the base class.  It also requires the calling code to perform more assignments and may result in more memory being consumed by all the immutable instances around.  
This is similar to the above but not requiring separate mutable and immutable classes. Rectangle could be initialized, presumably by a constructor, with a state indicating if it can be modified. The modifier methods would check this state to see if the operation was allowed.  


== See also ==
== See also ==
* [http://en.wikipedia.org/wiki/Circle-ellipse_problem Circle-Ellipse Problem] (Wikipedia)
* [http://en.wikipedia.org/wiki/Liskov_substitution_principle Liskov Substitution Principle] (Wikipedia)


== References ==
== References ==
== External Links ==

Revision as of 14:48, 1 December 2010

Square-Rectangle Anti-Pattern

The Square-Rectangle Anti-Pattern is a software development problem that can occur in Object Oriented Programming with inheritance when a base class contains methods which invalidate the derived class, and thereby violate the Liskov Substitution Principle.

Description

Consider classes Rectangle and Square. By the 'is a' test, one might reasonably define the Square class as a specialization of the Rectangle class (Square is a Rectangle), where all sides are of equal length. But suppose Rectangle defines a constructor that takes two arguments, x and y, for the lengths of the sides. How can Square provide an implementation for this constructor? If the values passed for x and y are not the same, it would be unable to construct an instance of Square that satisfied the input criteria.

The situation is worse for modifier methods on the base class. Suppose Rectangle has a method setX which modifies only two sides of the rectangle. When called on a Square, it would cause the object to no longer be a square. This can be particularly problematic when new modifiers are added to an existing base class after it has been subclassed. If it has been subclassed improperly, the subclasses may be broken by the new functionality in the base class.

Example

The following example illustrates the problem. In this case, Square has decided not to implement a constructor defined in Rectangle. But there is still a problem with the setX modifier:

 class Rectangle 
 {
     protected int x, y;
 
     Rectangle() {}
   
     Rectangle(int x, int y) {
         this.x = x;
         this.y = y;
     }
 
     void setX(int x) {
         this.x = x;
     }
 
     void print() {
         System.out.println(this.getClass().getName() + " has sides of length " + x + " and " + y);
     }
 }
 
 public class Square extends Rectangle 
 {
     Square(int x) {
         this.x = x;
         this.y = x;
     }
 
     public static void main(String[] args) {
         Square s = new Square(10);
         s.print();
         s.setX(5);
         s.print();
     }
 }

This produces the following output:

 Square has sides of length 10 and 10
 Square has sides of length 5 and 10

Alternatives

Reverse the inheritance

Instead of having Square be a subclass of Rectangle, one could make Rectangle a subclass of Square. Afterall, you could say a Rectangle is a specialization of Square in which the sides don't need to be of the same length. Thus Square would define a constructor that takes only argument x. Rectangle could support that constructor and also provide another constructor which takes x and y. Likewise, Square could have only a setx method whereas Rectangle could also define a sety method.

This is somewhat of a contrived example since Square does not have any additional information than Rectangle. Such may not be the case in more realistic scenarios.

Absent constructor

The derived Rectangle class could choose to not provide an implementation for the constructor that takes arguments x and y. This would prevent any caller from instantiating an instance of Square with arguments x and y. This design maybe controversial since one normally expects a subclass to support all the constructors of the base class.

Throw exception

The Square class could override any methods or constructors which violate the contract and throw an exception. In the constructor example above, it could throw an exception if the lengths provided for x and y are not equal. In the setx method example, it could check the value of y and throw an exception if the passed in value for x wasn't equal to y. This is somewhat dubious but does allow the setx method to be called if the value isn't changing.

In Java the UnsupportedOperationException could be thrown, which is a RuntimeException and therefore does not need to be declared. If another type of exception was thrown, it would have to be declared.

Return new value

In this solution, the super class method would need to return its new value X when a method is called to change it. The derived class reacts by stubbornly just returning its current value, thus forcing the caller to use a method supported for that class. This solution avoids throwing exceptions but could have undesireable consequences if the calling code is not careful to check the return value to make sure it was modified. But perhaps worse, it requires the method signatures to be changed in the base class to support the problem in the derived class.

Make method more powerful

The implementation for the setx method in Square could just change the value of y also. This results in the method becoming more powerful, and might not be what the caller intended.

Return instance of new class

If the constructor is called with different values for x and y, or if the setx method is called, the implementation on Square could instantiate an instance of Rectangle and return that instead. This requires the methods to declare a return type of the base class. This could have unexpected consequences on the program.

Use separate immutable and mutable classes

In this approach, the Rectangle class is immutable and therefore its methods cannot change derived instances, they can only return new instances of Rectangle. A separate MutableRectangle class could be defined and all the modifiers put into it.

As in some of the previous solutions, this has the disadvantage of requiring significant modifications to the design of the base class. It also requires the calling code to perform more assignments and may result in more memory being consumed by all the immutable instances around.

Initialize base class as mutable/immutable

This is similar to the above but not requiring separate mutable and immutable classes. Rectangle could be initialized, presumably by a constructor, with a state indicating if it can be modified. The modifier methods would check this state to see if the operation was allowed.

See also

References