With this organizational structure of our projects and source code from last time in mind, let’s now focus on the different design paradigms for what we actually implement.

The main design paradigms that most development falls under revolve around the notion of state.

Question

What does it mean for a program to maintain state?

Remark

Crucially: Some programmatic tasks are best accomplished by maintaining state, while others do not need it.

The distinction between whether a task demands state or not distinguishes Functional Programming from Object-Oriented Programming:

Gist

Functional

Organizes flow of a program as a stateless sequence of function calls, without side-effects between each.

Object-Oriented

Organizes flow of a program as a stateful sequence of commands to objects, preserving state between each.


Examples

Functional

Calls to the Math.pow(a, b) function, which returns a^b

Math.pow(3, 2) => 9.0
Math.pow(2, 3) => 8.0

Object-Oriented

Designing a video game player that has some health, which can be doubled with the doubleHealth() method.

player.health => 10
player.doubleHealth(); // player.health now 20
player.doubleHealth(); // player.health now 40

Explanation

Functional

Calls to Math.pow do not affect each other, no state is carried over from one call to next.

Object-Oriented

Commands to the player object in the form of doubleHealth() depend on its current state in player.health;


Java Implementation

Functional

Methods and properties of the class possess the static modifier, demonstrating that they belong to the class.

Object-Oriented

Methods and properties of the class that are NOT tagged as static belong to individual objects / instances of the class.

So, we can see that some tasks demand the maintenance of state while others do not, and a crucial part of the study of data structures includes how state is managed in the cleanest, most efficient way (also, for data that is far more complex than a single int like player.health!).

Remark

Remark: this is a very scant overview of these paradigms, which you’ll revisit in great detail in your Programming Languages class.

Some last notes of mention on the above:

  • Most large tasks will involve a mixture of functional and object-oriented tools, but in development of those tools, you will generally be working in one style or the other at any given time.
  • This course (CMSI 2120) will focus largely on Object-Oriented techniques as they are so central to how we organize data in state. The next course, CMSI 2130, will have a heavier focus on the Functional approach.

Much of this course will require us to define our own data structures from first-principles (so that we understand how they work, algorithmically).

Definition

For this purpose, we will of course be creating our own Classes using Object-Oriented Design principles to represent our data structures.

Question

What is a Class in Java?

We can think of Classes as blueprints from which we might create specific, concrete instances / objects.

Definition

Objects of a class are specific instances of the blueprint, each with their own state (properties) but with behaviors common to the Class.

Remark

If you think of Human being a class with certain expectations of behaviors (eat, sleep, etc.) and properties (name, age, etc.), then Andrew would be a particular object of that Class (Andrew has his own name and age, but shares common behaviors with other humans). By analogy:

Classes : Objects :: Cookie Cutters : Cookies.

Definition

This is what it means to practice Object Oriented Programming (OOP): to define classes of objects, and use instances of those objects to track and manipulate a program’s state.

Example

You might remember these in Python when you created classes like the following:

# Python class -- defined the blueprint for Person
class Person:
 
    # Attributes were unique to each Person instance,
    # and initialized by the constructor:
    def __init__(self, name):
        self.name = name
 
    # Methods define what commands each Person instance
    # can be given, like:
    def introduction (self):
        print("My name is " + self.name)
 
# Outside of the class, instances can be made:
dave = Person("Dave")
andy = Person("Andy")
 
# ...and methods called upon them
dave.introduction();
andy.introduction();

Example

Draw a pictorial representation of a single Person class “cookie cutter” with multiple Person “cookies”.

…and no, that’s not some vague reference to Soilent Green.


Designing Java Classes

Toolkit

Designing classes in the OOP paradigm involves several common class components.

We’ll look at their definitions first and then implement them ourselves in an example.

Class Components

Most classes (in any language) have several key components:

Definition

Component 1: Fields (AKA data members) are values that every class object will independently maintain, i.e., might have different values between each object of the class.

Toolkit

Fields are typically the first thing declared in a Class with the syntax, and (as usual in Java) are typed / declared just like variables:

public class ClassName {
 
    <modifiers> type1 field1;
    <modifiers> type2 field2;
    // ...
 
}

Toolkit

Each object maintains its own non-static fields, but shares all static fields with other members of the class.

Debug

This is why static constants are OK, but static fields are almost always the wrong approach to solving a problem — they are risky and can lead to unexpected behavior.

If fields provide the state of an object, then we also need a means of making them do things!

Definition

Component 2: Non-Static Methods are functions that are called upon objects, declared just like static methods but lacking the static keyword.

Remark

Generally, methods are commands or requests to objects, sometimes modifying the object’s fields as a result.

We’ve seen a couple of methods in Java’s String class already like .length() and .charAt(index).

Debug

Important: reminder that fields and methods which belong to each object of a class DO NOT have the static keyword attached to them; we only use static when a method or field belongs to the Class itself, not an instance of that Class.

For review: this is what distinguishes functional (static) from object-oriented (non-static) implementations.

Definition

Component 3: Constructors are methods that share the class name (and no return type specified), that describe how to instantiate the fields of a new object of that class.

Toolkit

The syntax for declaring a constructor (a class may have multiple constructors) is:

<modifiers> ClassName (parameters) {
    // What should happen when a new object of this
    // type is created using the "new" keyword
}

Toolkit

Similar to Python’s self keyword for referencing the instance calling a method (including constructors), Java uses the this keyword.

Toolkit

Constructors are called whenever we instantiate a new object, which follows the general format:

// Instantiating a new object of class Type
Type obj = new Type(arg1, arg2, ...);
 
// e.g. a Person instance whose constructor expects
// only their name and age
Person yenrof = new Person("Yenrof", 29);

These are the three major Java class components that we’ll practice shortly and throughout this course!


“Motivating” Example: Forneymon

I’m glad you’re all here because I’m designing an all new battle game that in no way, shape, or form resembles a certain Nintendo franchise…

Naturally, I’m calling it Forneymon, where we have different mythical pets of different types locked in psuedo-humane combat for our amusement!

I’m starting off slow and hoping we can develop the concept a bit in this class… here’s the gist:

  • I have two types of Forneymon right now: the Burnymon, which singes its opponents with the fire of 5 suns, and Dampymon, which annoys its opponents by getting them wet.
  • Both have a name that its trainer err… owner has given it.
  • Presently, Forneymon only have one behavior: taking damage of a certain type (e.g. burny damage or dampy damage).
  • Dampymon take 5 BONUS damage from burny attacks and start with 25 health, but Burnymon start with 15 health.
  • When attempting to “print out” (i.e., toString) a particular Forneymon, they will repeat their name 2 times (with a space in between).

Let’s try to design these two Forneymon classes… in class!

Animation credit to LMU’s own Niccolo Menotti!

Graphic credit to LMU’s own Niccolo Menotti, Aiden Srouji, and Aiden Dionisio!

Interfaces

Many times, you’ll be implementing classes that have to adhere to some guidelines set by someone else (cough professor or boss cough)

In any event, these guidelines are often given not only in plain-English descriptions (like above), but also in the programmatic-contract form of an interface.

Remark

Interfaces are like the buttons on our calculator analogy: they’re what define the ways that a user interacts with a class’ objects!

Definition

In Java, an interface is an outline of an implementing class’ methods that defines how a user interacts with it.

  • Interfaces define only the method signatures of public methods that a user is expected to employ when using your class.
  • Classes that implement (i.e., provide the method bodies for those methods) an interface must define at least the specified methods (though they can have others as well) in order to successfully compile. This ensures that other parts of the code that expect definitions for certain methods are satisfied.
  • Multiple classes may implement the same interface, allowing for different data structures (the implementing class) to implement the same data type (the interface).

Toolkit

The syntax for declaring an interface:

public interface InterfaceName {
    // Method Signatures here
}

In other words, interfaces are ways to establish a contract between your class’ implementation and the guidelines expected of it.

Just how your class implements these methods is up to you, but the signatures posed by the interface bind you to that contract.

Toolkit

Interfaces are defined in their own source files and typically list only method signatures of the class’ expected behavior.

Example

We’ll start by illustrating how a minimal interface might look for the Forneymon described above.

package forneymon;
 
public interface MinForneymon {
 
    // Method signatures alone!
    public int takeDamage (int dmg, String type);
    public String toString ();
 
}

Toolkit

Any classes that implement an interface must use the implements keyword in their class definition.

Example

We’ll start by scaffolding our Burnymon class as implementing the MinForneymon interface.

package forneymon;
 
/**
 *  Burnymon singe their opponents with the fire of 5 suns.
 *  - Start with 15 health
 *  - Deal burny damage
 */
public class Burnymon implements MinForneymon {
 
    public int takeDamage (int dmg, String type) {
        throw new UnsupportedOperationException();
    }
 
    public String toString () {
        throw new UnsupportedOperationException();
    }
 
}

Toolkit

Note: it is common practice to throw new UnsupportedOperationException(); before you have implemented a particular method, but which you know is required by an interface.

Example

Try to compile your code after removing the takeDamage method above — you’ll get an error because you broke your contract with the interface!

Question

Hey, how come we didn’t get a compilation error if we didn’t include the toString implementation?

Now we’re ready to start implementing our Burnymon class!

Implementing a Class

Question

I know my Burnymon has health and a name; how do I add these to my class definition?

Apropos, all Forneymon will have some name and some amount of health, but two different Forneymon might have different names or different amounts of health.

Conventionally, fields are defined at the top of a class definition.

Question

I need to specify how to create new Burneymon objects. What should I add to my class definition?

Conventionally, constructors are defined after fields in a class definition.

Question

I’ve noted that my Forneymon can take and deal damage of a particular type. How do I add behaviors to my objects?

Conventionally, methods are defined after constructors in a class definition.

Great! Now we have a nice looking Burnymon class:

package forneymon;
 
/**
 *  Burnymon singe their opponents with the fire of 5 suns.
 *  - Start with 15 health
 *  - Deal burny damage
 */
public class Burnymon implements MinForneymon {
 
    int health;
    String name;
 
    Burnymon (String n) {
        this.health = 15;
        this.name = n;
    }
 
    public int takeDamage (int dmg, String type) {
        this.health -= dmg;
        return this.health;
    }
 
    public String toString () {
        return this.name + " " + this.name;
    }
 
}

In a moment, we’ll look at a more principled approach to testing, but for now, let’s try out our new Burnymon.

package forneymon;
 
public class BattleTest {
 
    public static void main(String[] args) {
        Burnymon emberlizard = new Burnymon("Dave");
        System.out.println(emberlizard.health);
        emberlizard.takeDamage(5, "dampy");
        System.out.println(emberlizard.health);
 
        // [!] Check out what happens when we just put the
        // Burnymon where a string is expected! Hmm...
        System.out.println(emberlizard);
    }
 
}

Class Design: Best Practices

So you know how to make a class now — fantastic! …but does it follow good practices?

Let’s take a second and observe good style and development habits that will save you time, tears, and possibly employment in the future.

Good documentation

Many programmers believe that good documentation entails adding comments to every. single. line. of. code.

This is a silly (and hyperbolic) sentiment.

Definition

Good documentation clarifies only that which good naming could not make obvious.

This means that if you have named your functions and variables well, then your code and its functionality will be obvious and require little commenting.

That said, you should add comments when a particular piece of code has non-obvious functionality.

At the very least, you should:

Definition

Add a class-level comment explaining the purpose and capabilities of a class. Use the /** ... */ block comment for this.

Definition

Add method-level comments explaining the purpose, inputs, and outputs of methods. Use the /** ... */ block comment for this.

For methods, it is integral that you at least document:

  1. The purpose / summary of the method (a brief description)
  2. A description of each parameter
  3. A description of the returned value (for non-void methods).

Toolkit

The nice bonus about using an IDE: most will stub any documentation expected for a given function, e.g.:

/**
 * Deals the specified amount of damage to this Forneymon
 * as moderated by the damage type.
 * @param dmg The amount of damage to be taken
 * @param type The type of the taken damage, e.g., "burny"
 * @return The remaining health of this Forneymon
 */
public int takeDamage (int dmg, String type) {
    // ...
}

Toolkit

Curious about those little @param, @return line leadings? They are used when generating javadocs for a project, which can be generated at the command line or with the assistance of an IDE.

If you’ve never seen javadocs generated for a project, you should definitely try it!

Question

Why is it important to practice good documentation?

Field Access Restriction

Notice that we put no access modifiers on our Burnymon’s fields. Was this a good idea?

Let’s do a small test, modifying our BattleTest.java.

package forneymon;
 
public class BattleTest {
 
    public static void main(String[] args) {
        Burnymon emberlizard = new Burnymon("Dave");
        emberlizard.takeDamage(5, "dampy");
        emberlizard.health = 1000; // HAHA!
        System.out.println(emberlizard.health);
    }
 
}

Hacks! Cheats! Someone has evaded our fool-proof security to modify emberlizard’s health to an absurd amount!

Definition

A good practice in class design is to restrict access to fields so that they cannot be modified or read except through the methods that we define.

Toolkit

We can use the Java access modifiers to make fields private when we don’t want users of our class to be able to access the fields directly.

By tagging fields as private, the fields can only be accessed directly (i.e., through the dot access operator) in that class’ methods.

Let’s make that change now:

...
private int health;
private String name;
...

Now, if we try to re-compile BattleTest.java, we get an error! Huzzah! Hackers stopped cold.

Suppose we want to allow users to be able to read, but not modify a class’ fields.

Toolkit

We add simple methods to our class called “getters” (to read a field) and “setters” (to set the value of a field).

This is as simple as adding the following to our updated Burnymon class:

...
public int getHealth ()  { return this.health; }
public String getName () { return this.name; }
...

Now, whenever we want to read the health and name outside of class methods, we can just use the getHealth and getName methods — without fear of anything being modified outside of our control!

Debug

Warning: getters are often more stylistically appropriate than setters. Setters can easily violate good OOP design principles; often, it is better to provide some command / method to an object that sets some field appropriately.

This is precisely why it is better to have a takeDamage method than a setHealth; the prior makes it easier for us, the designers, to ensure that the class’s user does not improperly tamper with its state (e.g., by stupidly setting the health to 1000000).

Unit Testing

Since we’re practicing good software engineering, we shouldn’t be satisfied with only the BattleTest.java specified above — let’s make some unit tests!

Here’s an example of a JUnit test file for our Burnymon class:

package forneymon;
 
import static org.junit.Assert.*;
import org.junit.Test;
 
public class BurnymonTests {
 
    @Test
    public void testTakeDamage() {
        Burnymon burny = new Burnymon("Dave");
        assertEquals(15, burny.getHealth());
        burny.takeDamage(5, "dampy");
        assertEquals(10, burny.getHealth());
    }
 
    @Test
    public void testToString() {
        Burnymon burny = new Burnymon("Dave");
        assertEquals("Dave Dave", burny.toString());
    }
 
}

Follow these best practices and you will find unbridled success!

Next week, we’ll continue the discussion about classes and move into the exciting world of inheritance.

After that, we’ll start learning about our first data structures!