En un artículo de Wikipedia[1] se presenta un pretendido problema de la POO en la herencia. Peor aún, se dice que los problemas presentados incumplen con el genial axioma de Barbara Liskov[2] que le da base a toda la teoría moderna de tipos.
Debieron decir que la POO no tiene ningún problema y que este caso (Herencia entre Elipses y Círculos) es muy simple conceptualmente; que el problema está en la muy mala implementación de muchos de los lenguajes de programación más usados.
Los modelos OO deben representar lo mejor posible la realidad, para demostrar la superioridad del Python frente a otros lenguajes muy establecidos programé este código en apenas 15 minutos y lo he usado para lanzar retos a ver quiénes ofrecen una solución similar en otros lenguajes como C++, C#, Delphi o Java.
Por supuesto que el código está muy simplificado porque no tiene propósitos de resolver un problema real, sólo ilustra la falta de rigor de esté artículo de la Wiki.
#!/usr/bin/env python
# -*- coding: UTF-8 -*-
# Copyright (C) 2008 Medardo Rodriguez
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
# MA 02110-1301, USA.
class Shape(object):
def __init__(self, x, y):
self.x = x
self.y = y
class SizeableShape(Shape):
def __init__(self, x, y, dx, dy):
Shape.__init__(self, x, y)
self.SetSize(dx, dy)
def SetSize(self, dx, dy):
self.dx = dx
self.dy = dy
def _IsSquarish(self): Epsilon = 1E-10 return abs(self.dx - self.dy) < Epsilon
class MutableShape(SizeableShape):
# analize if some checks are needed here
def _Mutate(self, _class):
self.__class__ = _class
def __SetSize(self, dx, dy):
SizeableShape.SetSize(self, dx, dy)
subclasses = self.__class__.__subclasses__()
IsSuperClase = (len(subclasses) > 0)
NeedMutation = (IsSuperClase == self._IsSquarish())
if NeedMutation:
if IsSuperClase:
_class = subclasses[0]
else:
_class = self.__class__.__base__
self._Mutate(_class)
class Ellipse(MutableShape):
def SetSize(self, dx, dy):
MutableShape.SetSize(self, dx, dy)
if self._IsSquarish() and (type(self) is Ellipse):
self._Mutate(Circle)
class Circle(Ellipse):
def __init__(self, x, y, radius):
diameter = 2*radius
Ellipse.__init__(self, x, y, diameter, diameter)
def SetSize(self, dx, dy):
Ellipse.SetSize(self, dx, dy)
if not self._IsSquarish() and (type(self) is Circle):
self._Mutate(Ellipse)
class Rectangle(MutableShape):
def SetSize(self, dx, dy):
MutableShape.SetSize(self, dx, dy)
if self._IsSquarish() and (type(self) is Rectangle):
self._Mutate(Square)
class Square(Rectangle):
def __init__(self, x, y, width):
Rectangle.__init__(self, x, y, width, width)
def SetSize(self, dx, dy):
Rectangle.SetSize(self, dx, dy)
if not self._IsSquarish() and (type(self) is Square):
self._Mutate(Rectangle)
class Point(Shape): pass
if __name__ == '__main__':
for code in ('ce = Circle(0, 0, 10)', 'ce.SetSize(5, 6)',
'ce.SetSize(15, 15)'):
exec code
print '>>> %s' % code
for cls in [Circle, Ellipse]:
print '%s: %s' % (cls.__name__, isinstance(ce, cls))
print
…
[1] http://en.wikipedia.org/wiki/Circle-ellipse_problem
[2] http://en.wikipedia.org/wiki/Liskov_substitution_principle

