Phil Tayco Slide version 1.0 Created Nov 6, 2017 Polymorphism Phil Tayco Slide version 1.0 Created Nov 6, 2017
More Class Design In the previous discussion, we looked at the relationship of classes in an inheritance hierarchy using Account as a superclass and SavingsAccount as a subclass In that example, instantiations of either the Account and SavingsAccount classes can be done at any time – this design shows objects can be created at any level of the inheritance hierarchy Account objects can have properties that store concrete and meaningful data – an id, a balance and a date created make sense to store for an Account Some class designs, though, may have properties that are harder to define
High Level Classes Consider normal everyday geometric objects -Rectangles, Triangles, Circles and Pentagons If we designed these as classes, we can go through the normal process: Rectangles have length and width Triangles have 3 sides Circles have a radius Pentagons have 5 vertices Notice these properties all differ depending on the class – this is perfectly normal When we ask what we can do with these classes though, there are similarities All of them can calculate an area All of them can calculate a perimeter (also known as the circumference for a circle)
High Level Classes The common behaviors imply a potential form of code reuse Each one of these objects can have their area and perimeter calculated The formulas for these calculations, however, are different and depend on their individual properties: Area of a Rectangle is length * width Perimeter of a triangle is the sum of 3 sides Area of a circle is 3.14 * radius * radius While their implementation differs, what they have in common is the behavior for calculating an area and a perimeter Behaviors in OO programming is through the function signature – thus all these classes could be sharing the same method call
High Level Classes Individually, these classes can simply create their own method for these calculations and by coincidence, have the same signature public double calculateArea()… They will each internally have their own code and individual objects can be created as needed The common behavior, though, can be leveraged for other purposes that can be quite useful Imagine the need to execute behavior at a more general level: Calculate area of all Rectangles, Triangles and Circles created Determine which object has the largest perimeter Calculate the average area of all objects
High Level Classes Traditionally, solutions could be implemented with separate arrays for specific types of objects Rectangle[] rectangles = new Rectangle[10]; Circle[] circles = new Circle[10]; Triangle[] triangles = new Triangle[10]; Information would be needed from each array to address these general problems and can be done double sum = 0.0; for (int c = 0; c < 10; c++) { sum += circle[c].calculateArea(); sum += rectangles[c].calculateArea(); }
High Level Classes There are a number of challenges with this approach: “10” for the arrays may not be correct as this assumes all arrays have instantiated each element If you change “10” to “numberOfCircles”, then you would need “numberOfRectangles” as well This would require using multiple loops What if we have 10 Rectangles and only 2 Circles? Multiple arrays lead to more potentially unused memory What about Triangles and Pentagons? What if other geometry classes are introduced? To address these challenges in an OO way, we look at the similarities in the classes and their relationships to each other We already saw they share common behaviors in calculating area and perimeter For class relationships, each one of these classes “is a” type of something more general…
High Level Classes Conceptually, a Rectangle, Circle and Pentagon are each a type of “Shape” We can start with the standard approach to class design: public class Shape The challenge here is identifying properties about what all Shapes have As seen with the Rectangle, Circle, and Triangle, their properties differ Generalizing them would be a challenge because “length” and “radius” only have meaning for specific types
High Level Classes A typical first attempt at addressing this is to bring all the properties up to the superclass for subclasses to choose to use as needed public class Shape { protected double length; protected double width; protected double radius; } If Circle extends Shape, it would not need length and width and could leave them as 0 Calculate area and perimeter would be overridden as needed so this can work
High Level Classes Code maintenance is still a challenge – as new subclasses are introduced, more properties may be needed at the Shape level As more properties get introduced, that also means more properties not needed for each subclass Conceptually, these properties also do not apply – a Shape may not have a “length” or “radius” (some Shapes do, but not all) A more general fitting set of properties might be an array of points, but this gets abnormally complex for simple objects and calculations What this leads to is a class that conceptually have difficult properties to define Such high level classes may not have properties and may not need to be instantiated
Abstract Classes Abstract classes are part of OO languages that are designed as follows: They cannot be instantiated They are expected to have subclasses inherit from them They usually do not have properties, but when they do, they are expected to be inherited and used or redefined in the subclass They usually have methods that are often overridden public abstract class Shape { } In C++, this is known as a pure virtual class Let’s address each of these design points
Abstract Classes Abstract classes cannot be instantiated Given abstract class Shape, the following generates a compiler error Shape s = new Shape(); You can, however, declare a reference to a Shape Shape s; But such objects are intended to be instantiated right? How can such a reference be useful?
Abstract Classes Abstract classes are expected to have subclasses Shape subclasses like Rectangle and Circle and other can be created at any time public class Circle extends Shape Note that Circle is not abstract. Therefore, it can be instantiated Circle c = new Circle(); Remember the “=“ assignment rule: The types must match – specifically, the type on the right must match the type on the left Given that rule, the following can also be done
Abstract Classes Shape s = new Circle(); By rule, the Circle type on the right must match the Shape type on the left, so at first glance, this appears incorrect Remember the relationship between Circle and Shape – the Circle is a type of Shape! As such, the Circle type on the right does match the Shape type on the left because a Circle is a Shape The reverse is not true Circle c = new Shape(); A Shape is not a type of Circle (nor could Shape be instantiated anyway)
Abstract Classes Abstract classes usually do not have properties and usually have methods that are often overridden With Shape, we designed it not to have any properties As discussed, the subclasses share a common method with calculate area (and perimeter) We can update Shape to then contain these methods that the subclasses can use public abstract class Shape { public double calculateArea() }
Abstract Classes These methods in Shape, though, cannot really be defined - how do you define calculating the area of a shape? Where does calculating area and perimeter make sense? In the subclasses! Since Shape is abstract and expected to be subclassed, we let the subclasses define how these methods should be coded The method in Shape simply establishes the behavior but is too high level to define. As such, the methods are also considered abstract: public abstract class Shape { public abstract double calculateArea(); }
Abstract Classes Abstract methods have special meaning with abstract classes and subclasses: Abstract methods force classes to be abstract – if a method in a class is abstract, the class must be abstract Since abstract classes are expected to be subclassed, the subclasses must override any abstract method it inherits – if the method is missing in the subclass, it will lead to a compile error public class Circle extends Shape { private double radius; public double calculateArea() return 3.14 * radius * radius; }
Abstract Classes Once all abstract methods in the superclass are overridden in a subclass, the relationship between the 2 classes are minimally set This gives programs that create Circle and Shape objects some options The easy example is direct use of a Circle object Circle c = new Circle(5.0); System.out.println(c.calculateArea()); That could be done with a Circle regardless of whether it inherits from Shape or not However, this can also be done with a Shape reference instantiated as a Circle Shape s = new Circle(5.0); System.out.println(s.calculateArea());
Abstract Classes Notice that for the Shape reference s calling the calculateArea method does not generate a compile error This is because calculateArea is defined at the Shape level as an abstract method This is a clever way of programming in that the abstract/concrete infrastructure allows us to program at higher levels and execute implementation based on the subclass type By saying “s.calculateArea()”, we are saying, whatever subclass of Shape the object s is referring to, use the calculateArea method defined within it This may seem like extra work that can be addressed with directly creating Circle objects, but now recall the general questions discussed earlier
General Programming Previously, we had 3 separate arrays per shape Now, we can create one array of Shapes: Shape shapes = new Shape[15]; This creates an array of 15 Shapes. Notice these are not 15 Shape instantiated objects This is now an array of 15 Shape references ready to be instantiated at any point in your code shapes[0] = new Circle(5.0); shapes[1] = new Rectangle(3.5, 2.1); shapes[2] = new Rectangle(4, 2); int numberOfShapes = 3;
General Programming Visually, you have an array of 15 references with the first 3 elements pointing to 3 concrete objects – the first is a Circle and the next 2 are Rectangle objects – all other elements are null shapes 0x1100ab20 0x241b3cd0 0x241b3ce0 0x241b3cf0 1 2 3… 14 Circle radius = 5.0 calculateArea() Rectangle length = 3.5 width = 2.1 calculateArea() Rectangle length = 4 width = 2 calculateArea()
General Programming The elements of the shapes array can only be instantiated with subclasses of Shape Because of the abstract code rules of the superclass and subclasses, this approach is safe to use (it is impossible to have a Shape element be a class other than a subclass of Shape) We can now write readable code at higher class levels and do things like show the areas of all shapes for (int c = 0; c < numberOfShapes; c++) System.out.println(shapes[c].calculateArea());
General Programming The code translates to “go through all the instantiated shapes and call calculate area” shapes[c] returns a Shape type. Since Shape is abstract, the object returned is guaranteed to be a subclass of Shape shapes[c].calculateArea() is allowed because it is defined in the Shape class Because it is abstract, the appropriate calculateArea method will be called based on the instantiated type that is required to be there Consider now how new subclass types of Shape are handled Adding a Triangle class requires defining calculateArea Adding a new Triangle to the shapes array integrates smoothly The code for calculating areas of all shapes does not change!
General Programming With this, instantiating subclass objects and adding them to general reference data structures can happen at run time This technique of programming at general class references to run-time instantiated subclass objects is called “polymorphism” Polymorphism allows general code to execute using code defined based on the subclass object It is a form of extensibility that allows for any number of subclasses to be defined specific to its needs and executed as needed at higher levels Many examples can be drawn from this: Payroll can have multiple types of Employees that can generate pay specific to their type (Salary, Hourly, etc.) Graphic windows can have multiple types of drawable objects that can be drawn Customers having multiple types of Accounts that can report balances and information
Polymorphism Specifics More detail on polymorphism and abstract classes A class does not need to be abstract to do polymorphism – Shape could be a regular class and the main program can still use it to polymorphically call calculateArea. An abstract class does not have to have abstract methods. Abstraction is only to enforce no objects of that class to be instantiated An abstract class can still have properties and constructors – these will get inherited in the subclasses and can be used or overridden as needed Subclasses of abstract classes that inherit abstract methods can still remain abstract Consider a “TwoDimShape” class inheriting from “Shape” – this class could also be abstract keeping calculateArea as an abstract method At the very least, calculateArea must be present in the subclass. Either it is defined as abstract or it contains code for a concrete implementation
OO Summary We have now learned several techniques with systematic design processes for creating classes and performing OO programming When looking at requirements, we can identify potential classes in the problem descriptions and contexts With each class, we identify properties and methods by asking what does that type have and what can be done With each property, we identify the data type for it that we want to use. This can lead to data types of other classes we’ve designed (Composition) Some classes are related to each other such that one type is a type of another. This leads to potential designs setting up for code reuse through inheritance Inheritance hierarchies allow us to program at higher class levels promoting code extensibility through polymorphism Combinations of composition, inheritance and arrays can lead to complex, powerful foundational work such that as requirements evolve, code maintenance efficiency is strong Understanding all this comes with practice which will be our next activities before learning more concepts
Exercise 7 Using the Shape inheritance hierarchy and subclasses discussed as a model, create a set of classes executed polymorphically as follows: Create an abstract class “ThreeDimShape”. The class has 2 abstract methods Calculate Volume Calculate Surface Area Create 3 subclasses for “Sphere”, “Cone” and “RectangularPrism” which inherit from “ThreeDimShape” Identify the appropriate properties for each subclass and implement constructors, set/get and toString methods Override the calculate volume and surface area methods in each subclass as appropriate
Exercise 7 Create a test program that creates an array of up to 10 ThreeDimShapes Write code (hardcode or user input driven) that creates at least 2 objects of each subclass type and adds them into the array (total 6 objects) Write code that shows that shows the properties, volumes and surface areas of each object in the array using one loop demonstrating polymorphism Use that same loop to help determine which object has the largest volume and what the average surface area is of all objects