370 likes | 387 Views
Computer Science 340 Software Design & Testing. Inheritance. Class reuse. Two forms of class reuse: Class inheritance Object composition. Class Inheritance. This class is like that class except for these differences … Specialize superclass behavior by overriding methods
E N D
Computer Science 340Software Design & Testing Inheritance
Class reuse • Two forms of class reuse: • Class inheritance • Object composition
Class Inheritance • This class is like that class except for these differences … • Specialize superclass behavior by overriding methods • Totally replace a superclass operation • Add pre/post processing before/after superclass operation • Extend superclass by adding new variables/operations
Class Inheritance • Inheritance establishes a subtyping relationship with between subclass and superclass, thus enabling polymorphism • Polymorphism = Subtyping + Dynamic method binding • Subclass instances may be used anywhere superclass instances are expected • Liskov Substitution Principle
Inheritance-based reuse example • Microsoft Foundation Classes (MFC) • Visual Studio generates code for a functional Windows application • MFC base classes provide a lot of standard Windows functionality, and define specific integration points for app-specific code • Subclasses provide app-specific functionality
Object Composition • Client class creates an internal instance of existing class and invokes its functionality through regular method calls • Generally results in looser coupling than the inheritance relationship • With inheritance, changes to A are more likely to break B than with composition • No subtyping relationship is established between the two classes, thus preventing polymorphism • Extra levels of indirection in method calls can reduce efficiency, but this usually isn’t a problem
Composition-based reuse example • Order Shipper
Choosing between Composition and Inheritance • What type of relationship is being modeled? • B “has-a” A => composition • B “uses-a” A => composition • B “is-a” A => inheritance (usually)
Choosing between Composition and Inheritance • “Favor object composition over class inheritance.” [Design Patterns, pg. 20] • Composition is: • More flexible than inheritance • Leads to lower coupling than inheritance • Allows control over which delegate features are exposed, and what the API looks like
Choosing between Composition and Inheritance • Inheritance: • Supports polymorphism, while composition does not • Is easier if you want to expose many of the superclass’ features • Although Eclipse has a handy “Generate Delegate Methods” option for exposing delegate features if you’re using composition
Choosing between Composition and Inheritance • Dynamic “is-a” relationships should be implemented using composition • Inheritance represents a static relationship between two classes that cannot be changed at runtime • Composition relationships can be changed at runtime (more flexible than inheritance)
Choosing between Composition and Inheritance • Dynamic “is-a” relationships should be implemented using composition • Example: Vocations • This inheritance-based design is inflexible because once a person has been created, their vocation cannot change
Choosing between Composition and Inheritance • Dynamic “is-a” relationships should be implemented using composition • Example: Vocations • This design uses a combination of composition and inheritance to allow a person’s vocation to change over their lifetime
Choosing between Composition and Inheritance • Another example of a dynamic “is-a” relationship
Choosing between Composition and Inheritance • Multi-valued “is-a” relationships should also be implemented using composition • i.e., each instance can be a member of multiple subclasses • Example: Test Tracker users can have multiple roles
Choosing between Composition and Inheritance • Multi-valued “is-a” relationships should also be implemented using composition • Could create a subclass for every possible combination • Doesn’t scale, and won’t work if relationship is both multi-valued and dynamic
Choosing between Composition and Inheritance • Multi-valued “is-a” relationships should also be implemented using composition • Example: Use a combination of composition and inheritance
Specialization: a set-based perspective • A class is a “set of instances” • A class’ instances share some common properties and operations A
Specialization: a set-based perspective • A “specialization” of a class is a subset of its instances that have even more properties and operations in common A X
Specialization: a set-based perspective • Example: Dog is a specialization of Animal • In addition to all Animal features, Dogs also have a bark operation • Example: Rectangle is a specialization of Polygon • In addition to all Polygon features, Rectangles also have a diagonal property Animal Dog
Specialization: a set-based perspective • Specialization is frequently implemented using inheritance A Z X Y
Specialization: a set-based perspective • If instances can move between specializations at runtime, composition must be used because inheritance relationships are static (i.e., dynamic “is-a” relationships) A Z X Y
Specialization: a set-based perspective • If instances can be members of multiple specializations at the same time, composition should be used (i.e., multi-valued “is-a” relationships) A Z X Y
Inheritance-based reuse • Inheritance is a more tightly coupled relationship than composition • Inheritance is sometimes called “white box reuse”, and composition is called “black box reuse” • To reduce this coupling, the “subclass interface” between a base class and its subclasses should be carefully designed • Some programmers, when they realize they need to subclass an existing class, make all the “private” features “protected”, make all the methods “virtual”, and say, “There, now it’s a base class!”. • This approach results in extremely high coupling between a base class and its subclasses, and results in a fragile base class (i.e., a base class that is difficult to change without breaking its subclasses) • Information hiding is still a good practice, even between super- and sub-classes
Designing the subclass interface Client Client • Subclasses need to: • Access base class features in ways not available through the public interface • Specialize base class behavior Public Interface Base Class Subclass Interface Sub Class
Designing the subclass interface Client Client • Base classes should keep their variables private when possible • Base classes should provide protected methods that allow subclasses to access or modify their state only in necessary, controlled ways • Make a variable protected only if there is a very good reason to do so (other than being in a hurry) Public Interface Base Class Subclass Interface Sub Class
Designing the subclass interface Client Client • Keep methods private when subclasses don’t need to access or override them • Base classes should define polymorphic methods for aspects of their behavior that subclasses can specialize • Virtual methods in C++ • All methods in Java • Make a method polymorphic only if you expect subclasses to specialize it • Specifically prevent specialization of methods that subclasses should not override • Non-virtual methods in C++ • “final” methods in Java Public Interface Base Class Subclass Interface Sub Class
Designing the subclass interface Client Client • Keeping the subclass interface as simple as possible has two positive outcomes: • There is more freedom to change the internal implementation of the base class without breaking subclasses (i.e., base classes are less fragile) • It is easier for subclass authors to understand how to specialize the base class (i.e., they have less freedom, but more guidance) Public Interface Base Class Subclass Interface Sub Class
Subclass interface examples • Logger class • Test Tracker Base Panel class • see Javadocs for a description of BasePanel’s subclass interface
Patterns for designing the subclass interface • Template Method pattern • Factory Method pattern
Template Method pattern • Common Wisdom: Code that is duplicated in multiple places should be centralized in one place (i.e., avoid duplication) • Composition: Put common code in a method on a class to which multiple clients will delegate • Inheritance: Put the common code in a method on a super-class, and make the clients sub-classes (i.e., clients inherit common code) • What if an algorithm is duplicated in several places, but the copies are SIMILAR rather than IDENTICAL? • Use the Template Method pattern • Put the common algorithm in a super-class • Clients inherit common code from super-class • Some steps of the algorithm are delegated to subclasses through polymorphic method calls • Subclasses customize the algorithm by implementing the delegated steps
/** * An abstract class that is common to several games in * which players play against the others, but only one is * playing at a given time. */ abstractclass Game { protectedint playersCount; abstractvoid initializeGame(); abstractvoid makePlay(int player); abstractboolean endOfGame(); abstractvoid printWinner(); /* A template method : */ publicfinalvoid playOneGame(int playersCount) { this.playersCount = playersCount; initializeGame(); int j = 0; while (!endOfGame()) { makePlay(j); j = (j + 1) % playersCount; } printWinner(); } } //Now we can extend this class in order //to implement actual games: class Monopoly extends Game { /* Implementation of necessary concrete methods */ void initializeGame() { // Initialize players // Initialize money } void makePlay(int player) { // Process one turn of player } boolean endOfGame() { // Return true if game is over // according to Monopoly rules } void printWinner() { // Display who won } /* Specific declarations for the Monopoly game. */ // ... }
Factory Method pattern • Factory Method pattern • A super-class contains useful functionality that can be inherited by sub-classes • The super-class needs to instantiate an object to do its work, but it doesn’t know the concrete class of the object it needs, so it can’t call new • Instantiation of the object is delegated to sub-classes, which do know which concrete class to instantiate
interface Vehicle{ public void drive(); public void clean(); } class Car implements Vehicle{ @Override public void drive(){ System.out.println("Driving a car..."); } @Override public void clean(){ System.out.println("Cleaning a car..."); } } class Bus implements Vehicle{ @Override public void drive(){ System.out.println("Driving a Bus..."); } @Override public void clean(){ System.out.println("Cleaning a Bus..."); } } abstract class VehicleDriver{ public abstract Vehicle getVehicle(); public void driveVehicle(){ getVehicle().drive(); } public void cleanVehicle(){ getVehicle().clean(); } } class CarDriver extends VehicleDriver{ @Override public Vehicle getVehicle(){ return new Car(); } } class BusDriver extends VehicleDriver{ @Override public Vehicle getVehicle(){ return new Bus(); } }
public class FactoryMethodPattern { public static void main(String[] args) { handleVehicle(new CarDriver()); handleVehicle(new BusDriver()); } static void handleVehicle(VehicleDriver2 vDriver){ System.out.println("Handling a new vehicle."); vDriver.driveVehicle(); vDriver.cleanVehicle(); } } Handling a new vehicle. Driving a car... Cleaning a car... Handling a new vehicle. Driving a Bus... Cleaning a Bus...
publicclass MazeGame { public MazeGame() { Room room1 = makeRoom(); Room room2 = makeRoom(); room1.connect(room2); this.addRoom(room1); this.addRoom(room2); } protected Room makeRoom() { returnnew OrdinaryRoom(); } } publicclass MagicMazeGame extends MazeGame { @Override protected Room makeRoom() { returnnew MagicRoom(); } }