Showing posts with label Ballworld. Show all posts
Showing posts with label Ballworld. Show all posts

Friday, January 14, 2011

Ballworld, composition-based

Summary: The module uses the Ballworld program to demonstrate key ideas in abstract classes, polymorphism, inheritance and other coding techniques. In particular, this module will contrast a composition-based architecture with an inheritance-based architecture.





In this module we will explore what is gained by modifying the inheritance-based Ballworld system into a composition-based system.
In the inheritance-based Ballworld system, we were able to generate quite a bit of flexibility and extensibility. For instance, we could develop new kinds of balls and add them into the system without recompiling the rest of the code. This was accomplished by having the invariant ball creation and management code deal only with the abstract ball while the variant behaviors were encapsulated into the concrete subclasses.

The Problem with Inheritance

Inheritance seems to work quite well, but suppose we want to do more than just put different kinds of balls on the screen? What if we wanted to be able to change how a ball behaves,after it has been created? What if we want create balls that do a multiple of different behaviors, such as change color and radius? While working solutions using an inheritance-based system do exist, they are cumbersome, inefficient and most importantly, inconsistent with any sort of clear abstract model of what balls should be like.
The problem lies in the very nature of inheritance. When we attempted to separate the variant from the invariant behaviors, we overlooked a crucial aspect of inheritance. In our model, the superclass represented the invariant behaviors of a ball while the subclasses represented the variant behaviors. The separation seemed clear enough in the UML diagram, except that when one has an actual object instance, both the superclass and subclass behaviors are bound into a single entity. A ball object cannot change its variant updateState behavior because it is inextricably bound with to the invariant behaviors. A ball object cannot be composed of multiple updateState behaviors because that code cannot be isolated from the rest of the ball's code. If you want a curving behavior, you have to get it packaged in a whole ball object--you can't get just the behavior.
A clear sympton of this problem is the common code to call the superclass constructor found in all the subclasses' constructors. This tells us that the superclass is really right there in the subclass with everything else. The fact that the code is repeated from class to class says that it is invariant code in the middle of what we want to be variant code.
The inheritance-based model of Ballworld does not separate the variant and the invariant at the proper place. There is invariant code mixed together with the variant code.
That's why they can't be separated and the invariant behaviors are dragged along with the variant behaviors. This is what makes dynamically changing behaviors and multiply composed behaviors so difficult in this system.

Pizzas and Shapes

To understand what we can do to remedy the problems with our inheritance-based model, let's digress for a bit and consider a simple model of pizzas. Here, we have a pizza which has a price and has a shape. A shape, be it a circle, square, rectangle of triangle, is capable of determining its own area. A pizza, when requested to calculate its price per square inch, simply takes its price and divides it by the area of its shape. To obtain that area, the Pizza delegates to the IShape, since it is the shape that knows how to calculate its area, not the pizza.
Figure 1: A pizza has-a shape, which is able to calculate its area.
Pizzas and Shapes
Pizzas and Shapes (pizza.png)
Delegation is the handing of a calculation off to another object for it process. Here, the pizza is only interested in the result of the area calculation, not how it is performed.
To the pizza, the shape represents an abstract algorithm to calculate the area.
The Pizza and the IShape classes represent the invariant processes involved with calculating the price per square inch ration, while the concrete Circle, Square, Triangle andRectangle classes represent the variant area calculations for different shapes. What wee see from this example is that
objects can be used to represent pure behavior, not just tangible entities.
Interfaces are particularly useful here as they are expressly designed to represent pure, abstract behavior.

From Inheritance to Composition

Coming back to Ballworld, we see that the updateState method in ABall is an abstract algorithm to update the state of the ball. So, just as in the pizza example, we can represent this algorithm, and just this algorithm, as an object. We can say that a ball has an algorithm to update its state. Another wa of saying this is to say that the ball has a strategy to update its state. We can represent this by using composition. Instead of having an abstract method to update the state, we model a ball as having a reference to an IUpdateStrategy object. The code for update thus becomes

   public void update(Observable o, Object g)
   {
      _strategy.updateState(this);   // update this ball's state using the strategy
       location.translate (velocity.x, velocity.y);  // move the ball
      bounce();  // bounce the ball off the wall if necessary
      paint((Graphics) g); // paint the ball onto the container
   }
The ball hands a reference to itself, this, to the strategy so that the strategy knows which ball to update. The variant updating behaviors are now represented by concrete implementations of the IUpdateStrategy interface.
Figure 2: Ball is now a concrete class and its subclasses have been eliminated in favor of being composed with an abstract strategy.
Composition-based Ballworld
Composition-based Ballworld (uml.png)
(Note that the Randomizer class has been redesigned to eliminate its static methods. One new method has been added as well.)
There are a number of very important points to notice about this new formulation:
  • The modified ABall class now contains 100% concrete code and thus should not be abstract anymore.
    • ABall has been renamed to simply Ball.
    • Accessor methods for the strategy (getStrategy and setStrategy) have been added.
    • The Ball class is still 100% invariant code.
  • The CurveBall, StraightBall, etc. subclasses are no longer needed as their variant behaviors have been moved to the IUpdateStrategy subclasses.
    • Effectively what has happened is that the updateState method has been moved from the ABall subclasses and embodied into their own classes.
    • The IUpdateStrategy subclasses do not inherit anything from Ball, hence they do not contain any invariant code.
    • The strategies are thus 100% variant code.
  • The reference to the ball during the updating process has gone from a peristent communication link (implicitly, this), to a transient communication link (host).
  • This composition-based model divides the code exactly between the variant and invariant behaviors--this is the key to the power and flexibility it generates.
This new composition-based model of Ballworld is an example of the Strategy Design Pattern. The strategy design pattern allows us to isolate variant behaviors on a much finer level than simple inheritance models.

Composing Behaviors

So far, all of our redesigning has resulted in a system that behaves exactly as it did when we started. But what one finds very often in developing systems is that in order to make two steps forward, one must first make one step backwards in order to fundmentally change the direction in which they are going. So, even though it looks like our system has not progressed because it still does exactly the same thing, we are indeed in a very different position, architecturally. By freeing the variant behaviors from the invariant ones, we have generated a tremendous amount of flexibility.

Balls that change their strategies

Let's consider a the notion of a ball that changes its behavior. Since we have modeled a ball as having a strategy, we can simply say that in some manner, it is the ball's strategythat changes. We could say that the ball changes its strategy, but since the ball doesn't know which strategy it has to begin with, it really doesn't know one strategy from another. One could argue that it therefore can't know when or if it should ever change its strategy. Therefore, the ball cannot be coded to change its own strategy! So, whose baliwick is the changing of the strategy?
Since the changing of a strategy is a strategy for updating the ball, it is the strategy that determines the change. The strategy changes the strategy! Let's consider the following strategy:

 package ballworld;
 import java.awt.*;

 public class Change1Strategy implements IUpdateStrategy {

    private int i = 500;  // initial value for i
  
    public void updateState(Ball context) {
       if(i==0) context.setStrategy(new CurveStrategy());   // change strategy if i reaches zero
       else i--;  // not yet zero, so decrement i
    }
 }
This strategy acts just like a StraightStrategy for 500 updates and then it tells the ball (its context) to switch to a CurveStrategy. Once the CurveStrategy is installed, the ball becomes curving, without the need for any sort of conditionals to decide what it should do. The context ball fundamentally and permanently becomes curving.
EXERCISE 1
What would happen if you had two strategies like the one above, but instead of replacing themselves with CurveStrategy's , they instead instantiated each other?
A key notion above is that a strategy can contain another strategy. In the above example, the Change1Strategy could have easily pre-instantiated the CurveStrategyand saved it in a field for use when it was needed. But the does it matter exactly which concrete strategy is being used? If not, why not work at a higher abstraction level and let one strategy hold a reference to an abstract strategy? For instance, consider the following code:

 package ballworld;
 import java.awt.*;

 public class SwitcherStrategy implements IUpdateStrategy {
  
    private IUpdateStrategy _strategy = new StraightStrategy();
  
    public void updateState(Ball context) {
       _strategy.updateState(context);
    }
  
    public void setStrategy(IUpdateStrategy newStrategy) {
       _strategy = newStrategy;
    }
 }
This strategy doesn't look like it does much, but looks are deceiving. All the SwitcherStrategy does is to delegate the updateState method to the _strategy that it holds. This does not seem much in of itself, but consider the fact that the SwitcherStrategy also has a settor method for _strategy. This means that the strategy held can be changed at run time! More importantly, suppose a ball is instantiated with a SwitcherStrategy. The behavior of the ball would be that of whatever strategy is being held by the SwitcherStrategy since the switcher just delegates to the held strategy. If one were to have a reference to that SwitcherStrategy instance from somewhere else, one could then change the internal strategy. The ball is none the wiser because all it has is a reference to the SwitcherStrategy instance, which hasn't changed at all! However, since the held strategy is now different, the ball's behavior has completely changed! This is an example of the Decorator Design Pattern, where theSwitcherStrategy class is formally called the decorator and the held strategy is formally called the decoree. In theoretical terms, the decorator is what is known as anindirection layer, which is like a buffer between two enities that enables them to depend on each other but yet still be free to move and change with respect to each other. A very useful analogy for indirection layers is like the thin layer of oil that will enable two sheets of metal to slide easily past each other.

Balls with multiple, composite behaviors

Now that we can dynamically change a ball's behavior, let's tackle another problem:
EXERCISE 2
How can we have balls with multiple behaviors but yet not duplicate code for each one of those behaviors?
Let's start with a very straightforward solution:

 package ballworld;
 import java.awt.*;

 public class DoubleStrategy implements IUpdateStrategy {
  
    private IUpdateStrategy _s1 = new CurveStrategy();
      private IUpdateStrategy _s2 = new BreathingStrategy();
  
    public void updateState(Ball context) {
       _s1.updateState(context);
       _s2.updateState(context);
    }
   }
Ta da! No problem. The DoubleStrategy simply holds two strategies and delegates to each of them in turn when asked to updateState. So why stop here?

 package ballworld;
 import java.awt.*;

 public class TripleStrategy implements IUpdateStrategy {
  
    private IUpdateStrategy _s1 = new CurveStrategy();
      private IUpdateStrategy _s2 = new BreathingStrategy();
      private IUpdateStrategy _s3 = new BreathingStrategy();
 
    public void updateState(Ball context) {
       _s1.updateState(context);
       _s2.updateState(context);
       _s3.updateState(context);
    }
   }
We're on a roll now! We could go on and on, making as complex a strategy as we'd like, making a new class for each combination we want. But somewhere around the 439'th combination, we get mightly tired of writing classes. Isn't there an easier way?
Abstraction is the key here. We want to write code that represents that abstraction of multiple, composite strategies. Does what we were doing above depend on the particular concrete strategies that we were using? No? Then we should eliminate the concrete classes, raise the abstraction level and use the abstract superclass (interface) instead. For a combination of two behaviors, we end up with the following:

 package ballworld;
 import java.awt.*;

 public class MultiStrategy implements IUpdateStrategy {

    private IUpdateStrategy _s1;
    private IUpdateStrategy _s2;

    public MultiStrategy(IUpdateStrategy s1, IUpdateStrategy s2) {
       _s1 = s1;
       _s2 = s2;
    }
  
    public void updateState(Ball context) {
       _s1.updateState(context);
       _s2.updateState(context);
    }
 }
Notice how we have added a constructor that enables us to initialize the two abstract strategy fields. All we have to do is to construct a MultiStrategy object with the two desired strategies, and we're good to go!
EXERCISE 3
So if we want three behaviors, all we have to do is to make the same sort of thing but with 3 abstract strategy fields, right?
Thus, with just a Multistrategy we are capable of composing arbitrarily complex behaviors!

Composite Patterns

So what have we wrought here? Let's take a look at the UML diagram of our to most abstract strategies.
Figure 3: Note that the subclasses hold references to their own superclasses!
SwitcherStrategy and MultiStrategy
SwitcherStrategy and MultiStrategy (strategy.png)
The key to the power that lies in the SwitcherStrategy and the MultiStrategy lies in the fact that they hold references to their own superclass,IUpdateStrategy. This is what enables them to be create any behavior they want, including combinations of behaviors and dynamic modifications of behaviors. This self-referential class structure is known as the Composite Design Pattern (The Decorator pattern can be considered to be specialized form of the Composite pattern). The massive power, flexibility and extensiblility that this pattern generates warrants further, more formal study, which is where we're heading next. Stay tuned!

Ballworld, inheritance-based

Summary: The module uses the Ballworld program to demonstrate key ideas in abstract classes, polymorphism, inheritance and other coding techniques. This module will focus on an inheritance-based architecture.





In this module we will explore many OOP concepts by examining the program "Ballworld". Download the code for Ballworld here (Link Temporarily Disabled. Please contact the authors for the code.).
Figure 1: Note the union design pattern w.r.t. ABall and its subclasses.
UML class diagram of Ballworld
UML class diagram of Ballworld (uml.png)
To run Ballworld, load the files into DrJava and right-click the BallControl file. Select "Run Document's Main Method" to run the program. From the command line, go to the uppermost Ballworld directory and compile all the files in both the directories ("javac ballworld/*.java" and "javac command/*.java") and then run the program as such: "java ballworld.BallControl". The Make Ballbutton creates an instance of whatever ABall class is typed into the text field on the left. The "Clear All" button clears any balls off of the screen.
In a nutshell, the way Ballworld works is akin to a flip-book animation: The BallControl class contains a Timer that "ticks" every 50 milliseconds. Every time the timer ticks, the panel in the BallGUI upon which the balls are to be drawn is requested to repaint itself. When the panel repaints, it also tells the Dispatcher to notify every ABall in the system to update itself. When an ABall updates, it updates any internal values it has (its "state") such as its color, radius, and/or velocity. It then moves its position by an amount corresponding to its velocity and draws ("paints") itself onto the panel. Since this is happening 20 times a second, the balls appear to be moving and/or changing and thus the animation is acheived. The ILambdainterface3 enables the BallGUI to communicate in a generic, decoupled manner to the BallControl and the Randomizer class is a utility class that provides methods to produce various random values needed by the system. Much of the code in Ballworld is significantly more sophisticated than what has been covered in the course so far--it will be covered soon, don't worry!

Abstract Classes

First, we will focus on the union design pattern between ABall, WanderBall, CurveBall and StraightBall. In a union design pattern, we see that the superclass represents an abstraction of the union of its subclasses. For instance, a fruit is an abstraction of specific concrete classes such as apple and pear. A fruit embodies the common characteristics of its subclasses even if it doesn't completely describe the exact nature of those characteristics. In a fruit, there is a seed. However, the notion of "fruit" doesn't specify exactly the number, size, color or shape of its seed(s). It only specifies that it does indeed have a seed. Likewise, a fruit has the behavior of ripening. Apples, oranges, and bananas all ripen differently and at different rates. The abstract fruit notion does not specify the specific nature of the ripening behavior, just simply that it does have that behavior. In such, we see that we can never have a fruit that is not a specific class of fruit, such as an orange or grape.
Corresponding to the above notions, abstract classes in Java cannot be instantiated. Abstract classes are denoted by the abstract keyword in the class definition:

 public abstract class AFruit {...} 
By convention, the classnames of abstract classes always begin with "A".
In Ballworld, ABall is an abstract class representing a circular ball that has a number of properties: a color, a position, a velocity, etc. The abstract ball also has some defining behaviors, such as that all balls should paint a filled, colored circle when requested to display themselves on a graphics context (a panel). Likewise all balls know how to bounce off the walls of the container. These concrete behaviors are called "default behaviors" because all subclasses of ABall, such as StraightBall and CurveBall, automatically get these behaviors by default. One of the most common and useful reasons for using an abstract class is to be able to define the default behaviors for all the subclasses.

Abstract Methods

But what about the abstract behaviors that abstract classes exhibit? For instance the abstract "ripening" behavior of a fruit? At the abstraction level of a fruit, the exact implentation of ripening cannot be specified because it varies from one concrete subclass to another. In Java, this is represented by using the keyword abstract as part of the signature of a method which has no code body:

 public abstract class AFruit {
  // rest of the code
  
  public abstract void ripen();
 }
There is no code body because it cannot be specified at this abstraction level. All that the above code says is that the method does exist in all AFruit. The specific implmentation of method is left to the subclasses, where the method is declared identically except for the lack of the abstract keyword:

 public class Mango extends AFruit { 
  // rest of code
  
  public void ripen() {
   // code to ripen a mango goes here
  }
 }
 
 public class Tomato extends AFruit {
  // rest of code
  
  public void ripend() {
   // code to ripen a tomato goes here
  }
 }
The technical term for this process is called overriding. We say that the concrete methods in the subclasses override the abstract method in the superclass.
Note that if a class has an abstract method, the class itself must be declared abstract. This simply because the lack of code in the abstract method means that the class connot be instantiated, or perhaps more importantly, it says that in order for a class to represent abstract behavior, the class itsefl must represent an abstract notion.
Overriding is not limited to abstract methods. One can override any concrete method not declared with the final keyword. We will tend to avoid this technique however, as the changing of behavior as one changes abstraction levels leads to very unclear symantics of what the classes are actually doing.
In Ballworld we see the abstract method updateState. Abstract methods and classes are denoted in UML diagrams by italic lettering. This method is called by the update method as part of the invariant process of updating the condition of the ball every 50 milliseconds. The update method does 4 things:
  1. Update the state of the ball by calling the abstract updateState method.
  2. Move (translate) the position of the ball by adding the velocity to it.
  3. Check if the ball needs to bound off a wall.
  4. Paint the ball up on the screen.
This technique of calling an abstract method from inside of a concrete method is called the template method design pattern--which we will get to later in the course.
ABall.updateState() is abstract because at the abstraction level of ABall, one only knows that the ball will definitely do something with regards to modifying (perhaps) its internal field values(its "state"). Each subclass will do it differently however. The StraightBall will do nothing in its updateState method because a ball with a constant (unchanging) velocity will travel in a straight line. Remember, doing nothing is doing something! The CurveBall's updateState method uses sines and cosines to turn the velocity by a fixed (though randomly chosen) angle at every update event. You can imagine that other possible subclassses could do things such as randomly change the velocity or change the radius of the ball or change the color of the ball.
There is no code in the entire Ballworld system that explicitly references any of the concrete ABall subclasses. All of the code runs at the level of abstraction of an abstract ball. The differences in behavior of the various balls made on the screen using the different concrete subclasses is strictly due to polymorphism. New types of balls can be added to the system without recompiling any of the existing code. In fact, new types of balls can be added without even stopping the Ballworld program!

Abstract classes vs. Interfaces

Subclasses have a different relationship between interfaces and abstract superclasses. A subclass that implements an interface is saying simply that it "acts like" that specified by the interface. The class makes no statements however about fundamentally what it actually is. An actor implements a fiercesome alien from a distant plantet in one movie and a fickle feline in another. But an actor is actually neither. Just because the actor protrayed a interplanetary alien, doesn't mean that the actor fundamentally possessed all the abilities of such an alien. All it says is that in so far the context in which the actor was utilized as the alien, the actor did implement all the necessary behaviors of the alien.
A subclass is fundamentally an example of its superclass. A subclass automatically contains all the behaviors of its superclass because it fundamentally is the superclass. The subclass doesn't have to implement the behaviors of its superclass, it already has them. An actor is a human and by that right, automatically possesses all that which makes a human: one head, two arms, 10 toes, etc. Note that this is true even if the abstract class has 100% abstract methods--it still enforces a strict taxonomical hierarchy.
implements is about behaving, extends is about being.

Variant vs. Invariant Behaviors

A crucial observation is that the the Ballworld code that manages the GUI (BallGUI) and the ball management (BallControl, Dispatcher, etc.) only deal with the abstract ball,ABall. That is, they represent invariant behavior at the abstract ball level. The display, creation and managment of the balls is independent of the particular kinds of concrete balls that is being handled. The main Ballworld framework can thus handle any type of ABall, past, present and future.
StraightBall, CurveBall and WanderBall are thus concrete variants of ABall. They represent the variant behaviors of the system. Other than in their constructors (which will prove to be a significant point when this inheritance-based model is compared to a more advanced composition-based model), these concrete subclasses only code the abstract variant behavior associated with a ball, namely the updateState method. Inheritance gives any instantiation of these classes both the invariant behaviors from the ABall superclass plus the variant behaviors from the subclass.
The Ballworld code demonstrates the importance of the concept of separation of variant and invariant behaviors.
Clearly and cleanly separating the variant and invariant behaviors in a program is crucial for achieving flexible, extensible, robust and correct program execution.
Where and how to separate the variant and invariant behaviors is arguably the most important design consideration made in writing god software.

Java Syntax Issues

Packages

Packages are way that Java organizes related classes together. Packages are simply directories that contain the related code files. Each class file in a package directory should have the line package XXX; at its top, where the XXX matches with the directory. name. If neither public nor private (nor protected -- i.e. a blank specifier) is used to specify the visibility level of a class or method, then that method can be seen by other members of the package but not by those outside of the package. To use the public classes in a package, the import myPackage.*; syntax is used. This tells the Java compiler to allow all the public classes in the myPackage directory. Packages can be nested, though each level must be imported separately.

Static fields and methods

Static fields and methods, denoted by the static keyword in their declarations, are fields and methods that can be accessed at a class level, not just an object level. In general these are values or behaviors that one wishes for all instances of a class to have access to. These values and behaviors are necessarily independent of the state of any particular instance. Static fields and methods are often referred to as "class variables" and "class methods".
An examples of a class variables are Math.PI and Color.BLUE or Color.RED. These are universal values associated with math and color respectively and thus do not need an object instance to be viable. By convention, all static field names are in all capitol letters. A static field is referenced simply by writing the class name followied by a period and then by the field name. No instantiations are necessary.
The Randomizer class contains numerous static methods. This is because each of the methods to produce various random values is independent of each other and that the process in each method does not affect nor is affected by the state of the rest of the class. Essentially, this entails that the class contain no non-static fields. A class as such is referred to as being "stateless". Just like a static field, a static method is invoked in the same manner as the static fields: ClassName.staticMethodName(...) Classes with static methods are usually utility classes that are used to hold a set of related functional processes, e.g. Randomizer holds a collection of random value generators. Likewise, Math holds a combination of static values, such as PI and static methods such as sin() and cos().
There is one very special static method with the following exact signature:

 public static void main(String[] args) 
This method, found in BallControl, is the method that Java uses to start programs up. Since OO programs are systems of interacting objects, this static "main" method is used to create the first object(s) and get the program up and running. So when Java starts a program, it looks for this and only this method.

Calling methods of the superclass

When concrete methods or the constructor of a superclass are overriden, sometimes it is necessary or desirable to call the original superclass behavior from the subclass. A common reason for this is that the subclass's behavior is simple an addition to the superclass behavior. One does not want to replicate the superclass code, so a call to the superclass's original methods is required at some point in the subclasses overriding method. To accomplish this, Java uses the super keyword. super refers to the superclass instance, just as thisrefers to the class instance itself (the subclass here). Note that technically, super and this are the same object -- think of it as the difference between the id and the ego. (Does that mean that a coding mistake wiith respect to super and this is a Freudian slip?)
Suppose the superclass has a method called myMethod() which the subclass overides. For the subclass to call the superclass's myMethod, it simply needs to saysuper.myMethod(). Contrast this to the subclass calling its own myMethod: this.myMethod() (note: Java syntax rules allow for the this to be omitted).
To make a call to the superclass's constructor the subclass simply says super(...), supplying wahtever parameters the superclass constructor requires. This is a very common scenario as the the subclass almost always needs to superclass to initialize itself before it can perform any additional initializations. Thus the super(...) call must be the first line in the subclass's constructor. If the no-parameter constructor of the superclass is required, the call to super can be omitted as it will be automatically performed by the Java run-time engine. This of course presumes that the superclass's no-parameter constructor exists, which it does not if a parameterized constructor has been declared without explicitly declaring the no-parameter constructor.
Related Posts Plugin for WordPress, Blogger...

java

Popular java Topics