## What are the real advantages of OOP, do we really need it? # Suppose I wanted to represent circles and calculate properties about them. # I can represent circle with a tuple (x,y,radius), why use a class? from math import pi # area of a circle def area_cir(c): # circle c in the form (x,y,r) rad = c[2] # extract radius return pi*rad*rad def circumference_cir(c): rad = c[2] return pi*rad*2 ### If all we had are circles, this is fine, don't need a class ##### But now say there's another kind of shape: rectangles (x,y,width,height) def area_rec(r): w,h=r[2],r[3] # extract width, height return w*h def circumference_rec(r): w,h=r[2],r[3] return 2*w+2*h ### ##### And what if I have a collection of rectangles and circles: Shapes = [ (3,7,3), (4,6,1,2), (8,4,2,2), (6,5,9) ] ## and I want to add up area of all shapes in Shapes # First we must distinguish circles from rectangles: if we only had # these two kinds of shapes, we can just see if the length of a tuple is # 3 or 4, but is this solution SCALABLE? def area_shape(s): # if len(s)==3: # assume circle return area_cir(s) elif len(s)==4: # assume rectangle return area_rec(s) else: raise Exception("what kind of shape is "+str(s)) # totalarea = 0 for s in Shapes: totalarea += area_shape(s) #### OK, no big deal? But WHAT ABOUT ###SCALABILITY###? CODE REUSE? #### Working with other people to write thousands of lines of code? # What if there are other kinds of shapes that are also represented # by 3 or 4 values? We may need to change the representation # of shapes in to something like ("circle",x,y,r) instead. But this # means all the "beautiful" code you just wrote must go out the window. # You will also need to rewrite area_shape and add other "elif" clauses. # An ellipse can be defined by two points (the foci), or by the # coordinates of a bounding rectangle, which is how most computer # graphical API's represent ellipses. But now is (x,y,w,h) supposed # to represent a rectangle or an ellipse? # The area of an ellipse is pi*w*h/4 # The circumference of an ellipse is approximated as follows: (Ramanujan) # a,b = width/2, height/2 # circumference = pi*(3*(a+b) - ((3*a+b)*(3*b+a))**0.5) ########### OOP solution using first a superclass: class shape: # generic class for geometric shapes def __init__(self,x,y): self.x = x # all shapes have x,y coordinates self.y = y # constructor def distanceto(self,other): # distance between two objects dx = self.x - other.x dy = self.y - other.y return (dx*dx + dy*dy)**0.5 # straight line distance #distance to def move(self,dx,dy): # destructively change shape self.x += dx self.y += dy # move def cmp(self,other): # note can't call because area() not defined return self.area() - other.area() def __lt__ (self,other): # redefines <, for sorting return self.cmp(other) < 0 # shape ## shape is called an "abstract class" because not all methods can be called: # the area function is only defined in subclasses. ## to do more with shapes, we need to know what kind of shape it is class circle(shape): # class circle is a SUBCLASS of class shape def __init__(self,x,y,radius): shape.__init__(self,x,y) # calls superclass constructor self.r = radius # initialize additional instance var # shape def area(self): # area of circle return self.r*self.r * pi def circumference(self): return 2*self.r*pi # circle class rectangle(shape): # another subclass of shape def __init__(self,x,y,w,h): shape.__init__(self,x,y) self.width = w self.height = h #init def area(self): return self.width * self.height def circumference(self): return 2*self.width + 2*self.height # rectangle class ellipse(shape): # as defined by rectangle def __init__(self,x,y,w,h): shape.__init__(self,x,y) self.width = w self.height = h # init def area(self): return pi*self.width*self.height/4 def circumference(self): # approximation a,b = self.width/2,self.height/2 return pi*(3*(a+b) - ((3*a+b)*(a+3*b))**0.5) # Ramanujan #ellipse def distance(x1,y1,x2,y2): dx,dy = x1-x2, y1-y2 return (dx*dx + dy*dy)**0.5 #distance class triangle(shape): def __init__(self,x1,y1,x2,y2,x3,y3): # triangle defined by three points ## what is x,y : let's set it to center of triangle. mx,my = (x1+x2)//2, (y1+y2)//2 # midpoint between x1,y1 and x2,y2 # center point is found 2/3 distance from x3,y3 to mx,my x = x3+(2*(mx-x3))//3 y = y3+(2*(my-y3))//3 shape.__init__(self,x,y) # calls superclass constructor here self.x1,self.y1 = x1,y1 self.x2,self.y2 = x2,y2 self.x3,self.y3 = x3,y3 #init # must override the superclass move method! def move(self,dx,dy): self.x += dx self.y += dy self.x1 += dx self.y1 += dy self.x2 += dx self.y2 += dy self.x3 += dx self.y3 += dy # new move def circumference(self): sideA = distance(self.x1,self.y1, self.x2,self.y2) sideB = distance(self.x2,self.y2, self.x3,self.y3) sideC = distance(self.x3,self.y3, self.x1,self.y1) return sideA + sideB + sideC # circumference def area(self): # find area of triangle using Heron's formula a = distance(self.x1,self.y1, self.x2,self.y2) b = distance(self.x2,self.y2, self.x3,self.y3) c = distance(self.x3,self.y3, self.x1,self.y1) s = (a+b+c)/2 return (s*(s-a)*(s-b)*(s-c))**0.5 # Heron's formula #triangle s1 = circle(4,5,6) s2 = rectangle(8,1,2,4) s3 = triangle(3,4,5,1,8,9) s4 = ellipse(9,9,2,4) Shapes = [s1,s2,s3,rectangle(20,10,5,5),s4] # compute total area totalarea = 0 for s in Shapes: totalarea += s.area() # which area() is being called? print(totalarea) # Look ma, no if's ### procedure to sort, can be applied to shapes