Everything-You-Always-Wanted-To-Know-About-Objects by akgame

VIEWS: 18 PAGES: 26

More Info
									Object-Oriented Design




                         9-Nov-11
        Objects have behaviors
   In old style programming, you had:
       data, which was completely passive
       functions, which could manipulate any data
   In O-O programming, an object contains both data
    and methods that manipulate that data
       An object is active, not passive; it does things
       An object is responsible for its own data
            It can protect that data (by marking it private)
            It can expose that data to other objects (by not marking it, or by
             marking it public)
            Data should not be exposed without good reason
        Objects have state
   An object contains data
       The data represent the state of the object
       Data can also describe the relationship of the object to other
        objects
   Example: a checkingAccount might have
       A balance (the internal state of the account)
       An owner (some object representing a person)
       An accountNumber (used as an ID number)
   An object should always protect its state
       Wouldn’t it be nice to just add a few million dollars to your
        checking account balance?
     Classes
   A class describes a set of objects
   The objects are called instances of the class
   A class describes:
       Fields that hold the data for each object
            These data represent the state of the particular object
       Constructors that tell how to create a new object of this
        class
       Methods that describe the actions the object can
        perform
        Example: a “Rabbit” object
   You could create an object representing a rabbit
       It would have data:
            How hungry it is
            How healthy it is
            Where it is
       And methods:
            eat, run, dig, hide
        One way to feed a rabbit
   class Rabbit {
       int health = 100; // percent
       int food = 0; // range: 0 (starved) to 10 (full)
    }

   Somewhere outside the Rabbit class...
    bugsBunny = new Rabbit();
    bugsBunny.food = bugsBunny.food + 1;
       What this code is doing is “reaching inside the Rabbit”
        (ewww!) to put food into its stomach
       This is not a good Object-Oriented approach!
        A better way to feed a rabbit
   class Rabbit {
       int health = 100; // percent
       private int food = 0; // range: 0 to 10

        public void eat() {
          food = food + 1;
        }
    }

   Somewhere outside the Rabbit class...
    bugsBunny = new Rabbit();
    bugsBunny.eat();
   This is a proper Object-Oriented approach!
        Object-Oriented design
   Here are basic precepts of O-O design:
       Decide what kinds of objects you are going to have
            One way to do this is to write an English description of the program, then
             look for nouns in that description
       Determine how to represent the state of each object
            When you create objects, always create them in a valid state
            Make sure each method is guaranteed to leave its object in a valid state
       Determine the responsibilities of each kind of object
            In other words, what can it do? What are its actions (methods)?
       Make each object responsible for its own state
            The only way some other object can modify the state of this object is by
             calling some method of this object
            One way to ensure this is by making all fields of the object private
       Don’t Repeat Yourself (the DRY principle)
            Every piece of data should have a single representation, in a single place
            Don’t duplicate code--put it where it can be used by more than one object
          Where no man has gone before...
    Lately I’ve been working on a tiny (very tiny!) version of the old
     “Star Trek” computer game
    I’m going to use this as an example of Object-Oriented design
    +-----------+-----------+-----------+
    |
    |
                |
                | *
                        *   |
                          E | *   *
                                        |
                                        |
                                               E represents the Enterprise
    |
    | B
                |
              * |
                    *
                    K
                          K | *
                            |
                                    B * |
                                        |      The K are Klingon ships
    |   * *     |         * |       * * |
    +-----------+-----------+-----------+      The B are starbases
    |           |     *     |           |
    |       *   |           |   K       |
    |           |           |   K   *   |
                                               Asterisks are stars
    |     K     |         * |         * |
    |   K *     |       *   |     K     |      The galaxy is divided into
    +-----------+-----------+-----------+
    |           |       *   |           |       “quadrants”
    |           |           |           |
    |
    | B
            *   |
                |
                          K |
                            | *
                                        |
                                      * |
                                               The goal is to eliminate all
    |     *   K |           |           |
    +-----------+-----------+-----------+
                                                the Klingons (before they
                                                eliminate you!)
        Choosing the objects
   This is a little more realistic than the “Rabbit” example,
    but it’s still fairly easy to see what the objects are
   The Enterprise is an object (of type “Enterprise”)
   The Klingon ships, the starbases, and the stars are three
    more types of object
   There are some less obvious objects
       The Galaxy is an object
            But should it be composed of Quadrant objects?
            I didn’t do it that way, and I’m still not sure if I made the right decision
       Locations in the galaxy are objects
       “Empty space” locations are also objects
        The Enterprise
   You, as captain of the Enterprise, play the game by giving
    commands to the Enterprise object
   The Enterprise has state:
       How much energy it has
       How many photon torpedoes it has
       Its location in space
   The Enterprise has actions it can take:
       It can move about in space, thus changing its location
       It can shoot photon torpedoes, thus reducing their number
       It can “take a hit” from a Klingon, thus reducing its energy
       It can shoot phaser beams, which (in this simple game) don’t use energy
   The Enterprise is a good example of something that is readily
    modeled by an object
        The Klingons
   Each Klingon is an object (an instance of the Klingon
    class), and has state:
       How much energy it has
       How many photon torpedoes it has
       Its location in space
   Each Klingon has actions it can take:
       It can shoot photon torpedoes
       It can “take a hit” from the Enterprise
       It can shoot phaser beams
       It cannot, however, move about in space
   There’s really not much difference between the Klingon
    ships and the Enterprise
        The Starship class
   Remember the DRY principle--you don’t want duplicate code in
    both the Enterprise and the Klingon classes
   To prevent code duplication, I made the Enterprise class and the
    Klingon class both subclasses of a Starship class
       The Enterprise and the Klingons inherit from Starship:
            The fields energy, photonCount, and location
            The methods photonTorpedo, phaser, and takeHit
       The Enterprise has its own move method
   However, I never create a “plain” Starship object--I only create
    Enterprise and Klingon objects
       If I wanted to enforce this, I could make Starship an abstract class (just by
        putting the word abstract in front of class Starship)
        Starbases
   When the Enterprise “docks” (moves adjacent to a Starbase), its
    energy and torpedoes are replenished
   You could make a case that restocking the Enterprise is
    something that should be done by the starbase, not by the
    Enterprise itself
   However...
       In general, an object should manage its own state (fields)
       We cannot easily make the state of the Enterprise available to Starbases
        while still hiding the state from other objects
       Since the Enterprise is responsible for moving itself, it is in the best
        position to know when it has moved adjacent to a Starbase
   My solution is to have a restock method in the Enterprise
    object, but only the Enterprise itself calls this method
       Such a method should probably be private
        The galaxy
   The Galaxy is essentially--almost entirely--an array of objects
    (the Enterprise, Klingons, etc.)
       The most important responsibility of the Galaxy is to print itself, so that
        the player can see what’s where
       Since it’s just an array, should we bother with making it into a class with
        the array as (almost) its only field?
            Yes! The Galaxy should be an object, since there is no other obvious place to
             put the printing responsibility
   Once you’ve made a Galaxy object, it quickly acquires other
    responsibilities
       Some object has to create all the other objects and place them in the
        Galaxy; the obvious place to do this is in the initialization code of the
        Galaxy itself
       The Galaxy has to maintain its own array--moving and removing objects
            When the Enterprise moves, it updates its own Location, then tells the Galaxy
             where it has moved, so the Galaxy can keep track of it
            This violates the DRY principle (but I don’t have a better solution)
          Starbases, stars, and empty space
   Starbases and stars don’t do much--so why are they objects?
   The Galaxy is an array of objects, which it needs to print
   Instead of a lot of if statements such as
        if (galaxyArray[row][column] instanceof Klingon)
            System.out.print("K");
        else if ...
    we can provide each class with a toString() method, and just say
        System.out.print(galaxyArray[row][column]);
   This is also the reason for the EmptySpace class
        We could just leave those locations in the galaxy “empty” (null), but...
             Then our program would have to do a lot of checking for null
             We expose ourselves to a lot of NullPointerException problems
   Using a “null object” in this way is a handy trick to know
        “Cheating” with boring objects
   Stars, Starbases, and “empty space” objects have no
    responsibilities other than providing a toString() method
   Why do we need more than one of each?
       Every Star will print as *, and that’s all it does
       A Star could have a Location, but that’s not needed in my program
       In my code, I create one Star object and put it (actually, references to it) in
        multiple places in the Galaxy
       Remember, when you create an object with new, what you get back is
        actually a reference to the object
   There are two reasons for doing this:
       It’s more efficient (but this is hardly ever an actual issue!)
       It was less work to do it this way (the real reason)
   Starbases and “empty space” objects are handled the same way
        Locations
   A Location object is just a pair of numbers: row and column
       Starships have a Location
       It’s okay to have tiny classes like this
       Again, though, they sometimes get extra responsibilities
   In my little Star Trek game, I need to know:
       Whether two ships are in the same quadrant (so they can shoot at each
        other)
       How far apart two ships are (phaser fire attenuates with distance)
       For various reasons, I need to be able to translate between “galaxy
        coordinates” and “quadrant coordinates”
   The Location class is the right place for utility methods such as
    these
        Oh, and one more class...
   In Java, execution begins with the method
       public static void main(String[] args)
   Where should this method be?
   Since it’s not appropriate for any of the other classes, I made a
    StarTrek class
   The main method creates the Galaxy, then calls a playGame
    method (also in the StarTrek class)
   The playGame method:
       Prints an introduction to the game
       Gets “commands” from the user and calls the corresponding Enterprise
        methods
       Tells the Galaxy to print itself each time the Enterprise moves
       Calls an attack method for each Klingon in the same quadrant
       Tests if the game is over, and prints an appropriate message
         Static variables
   One way for the game to end is for all the Klingons to be destroyed
   We can keep a count of how many Klingons there are (call it howMany)
   Where should we keep this count?
        The Galaxy is an obvious place, but...
        Klingons know when they are created and when they are destroyed, so keeping a
         count of them is most easily done in the Klingon class
        However...It doesn’t seem right to have each Klingon keep its own count of how
         many Klingons are left
        Besides, this would violate the DRY principle
   Solution: Make this variable a static variable (synonym: class variable) of
    the Klingon class itself
        This means there is only one of it
        It’s easily accessible by individual Klingon objects
        It’s accessible from outside by saying (Klingon.howMany)
             Note the use of the Klingon class above, not an individual Klingon object
        Static methods (getters and setters)
   Here’s an easy way for the Enterprise to cheat:
       Klingon.howMany = 0;
   Of course, you wouldn’t write this in the game, right?
       But still, it’s good O-O practice for every class to protect itself from
        accidental or malicious modification
   Here’s how to prevent this cheat:
       Make the howMany variable private
       Provide a getter method (in the Klingon class) for it:
          static int getHowMany() { return howMany; }
       Don’t provide a setter method for it:
          static void setHowMany(int n) { howMany = n; }
   The getter method can easily be used from another class:
      if (Klingon.getHowMany() == 0) { ... }
        What have we learned?
   The main points:
       Objects have state, which they should protect and keep valid at all times
       Objects have responsibilities, which include updating their own states and
        asking other states to update theirs
       The DRY (Don’t repeat yourself) principle: Avoid redundant data and
        duplicate code
   Other points:
       Inheritance (subclassing) is a good way to avoid duplicate code
       Abstract classes can be subclassed but can’t themselves be instantiated
       It’s okay to have small objects (but they may grow)
       “Null objects” are sometimes useful to avoid frequent tests for null
       Static variables and methods belong to the class itself, not to its objects,
        thus avoiding redundant (and possibly inconsistent) data
       Getter and setter methods can be used to protect an object’s data
        Refactoring
   I chose this program partly because it was pretty easy to see
    what the objects and their responsibilities should be
       You aren’t always this lucky!
       The first design is seldom the best design
       Good programmers are always willing to refactor
   Refactoring is changing the structure of a program without
    changing its behavior. For example,
       Renaming a method when you change what it does
       Moving a method to a more appropriate class
       Extracting part of a method and making it into a new method
   You should refactor when:
       You see a better way of doing things
       You are about to add a feature, but the current design makes it hard
       You can be sure your refactoring doesn’t break something!
          Testing
   Conventional wisdom: If it ain’t broke, don’t fix it!
   Refactoring: If you can make it better, fix it!
   When you change a program, you run the risk of breaking it, in unpredictable
    ways (hence the conventional wisdom)
   You can reduce the risk, and “make the world safe for refactoring,” if you
    have a good test suite
   Testing can be a lot of work, and most older programmers test their programs
    manually, and not very thoroughly
   The new testing framework, JUnit, that greatly eases the burden of testing
        Proper use of JUnit actually reduces the time it takes to produce a working
         program, and a much smaller percentage of that time is spent debugging
        Programs produced with JUnit have fewer bugs
        JUnit does take discipline at first (until you emotionally grasp the benefits)
        Unfortunately, JUnit is not part of any standard Java curriculum
        Agile Programming
   In “real life,” the majority of programming projects fail!
   IMNSHO, the main reason is that they are too ambitious
       Instead of starting small and building up, they start with a “grand plan”
        that never gets realized
   “Agile Programming” says: Do the simplest thing that can
    possibly work.
       That is, start with the smallest and simplest program that can do some of
        what’s needed
       Make sure the program is correct (with JUnit testing)
       Never add features until all bugs have been fixed
   There’s a lot more to Agile Programming than this (such as,
    Refactor whenever you see a better way), but that’s beyond the
    scope of this talk
The End


“Don't ask for the information you need to do the
work; ask the object that has the information to do the
work for you.”
                                       --Allen Holub

								
To top