Skip to content

Lab 03

Previously we've been training basic encapsulation using private fields and recursion. In this lab we'll be placing basic encapsulation and simple inheritance into a more wholesome context. We'll create a simple model of a real-world objects, and use OO concepts to ensure our model remains a fair representation of reality.

In the context of this lab you'll be revising encapsulation, interface compliance, error handling, and managing object references.

Another brick in the wall

You'll be working with a set of prepared classes. Before you start coding, here's a little description of the context you'll be working with, and the individual classes involved:

At the center of this lab is a new class "Brick" that you'll have to implement. Bricks are basic objects, not too complex. Yet we can do a thing or two with them:

  • Bricks have a property: Their colour.
  • Bricks can be stacked: We can place exactly one brick atop of the other.

However, certain conditions apply:

  • Brick colours are immutable, i.e. once a brick has been created, it cannot change colour.
  • We can only add one brick on top of another brick. If there's already a brick above, we cannot add another brick.
  • We can only remove the brick above, if there are no further bricks. (Bricks are heavy, we can only deal with one brick at a time.)

Lastly, stacks of bricks must be visualized. Luckily there is already an implementation, which can conveniently print stacks of bricks.

Which concept seen in class ensures compatibility with your Brick implementation ?

Interfaces! As long as your implementation corresponds to the provided interface, the two components are guaranteed to be compatible.

The Brick interface

The brick interface is a formal specification of methods required for your Brick implementation. The interface is available here: Brick.java

Let's walk through the methods, one by one:

  • void addNext(Brick anotherBrick): This method allows placing a brick on top of another brick (make sure there's still space though!)
  • boolean hasNext(): This method tells us whether there already is a brick above our current Brick, or if the space above is still vacant.
  • boolean getNext(): This method provides us with a reference to the brick above. It must be existent though! We cannot access something that does not exist.
  • Brick getNext(): This method removes the brick residing directly on top of the current brick object. Same here - it must be existent. We cannot remove what does not exist.
  • void addAtop(Brick brick): This method is interesting. It adds a brick all to the top of an existing stack of bricks. That is, it walks up the stack, and adds the provided brick all to the top.
  • String toString(): Whoops, what happened here ? This method is not listed in the interface ? As we've seen in class all classes implicitly inherit a few default methods from the Object class. So no panic, we can request this method, even if it is not explicitly stated in the interface. We need it just for printing our beautiful bricks to screen.

Do not modify this interface

Never change a provided interface. Your solution has to adapt to the interface, not the other way round.

Implementation

Throughout this TP, you'll gradually implement the above methods. Here's a "template" you can use for implementing your solution:

package bricks;

/**
 * This class implements the Brick interface and must be coded by the student.
 */
public class BrickImpl implements Brick
{
  // Every brick has a colour.
  private Colour colour;

  // Some bricks have a brick above.
  // So the "next" field may be either "null", or a reference to the next brick.
  private Brick next;

  // Here's a sample constructor. Bricks just need a colour.
  public BrickImpl(Colour colour)
  {
    // TODO: implement
  }

  @Override
  public void addNext(Brick anotherBrick)
  {
    // TODO: implement
    return null;
  }

  @Override
  public String toString(){
    // This one's provided for free :)
    You'll need to add a field "colour" in your class though.
    The corresponding class is provided.
    return colour.colourize("[]");
  }

  @Override
  public boolean hasNext()
  {
    // TODO: implement
    return false;
  }

  @Override
  public Brick getNext()
  {
    // TODO: implement
    return null;
  }

  @Override
  public void removeNext()
  {
    // TODO: implement
  }

  @Override
  public void addAtop(Brick anotherBrick)
  {
    // TODO: implement
  }
}
How is this class linked to the Brick interface ?

The class definition does the job: public class BrickImpl implements Brick (implements keyword).

Provided code

Here's a list of provided code, so you do not need to worry about visualizations:

  • Brick - The provided interface. Do not tamper, implement.
  • Launcher.java - This class will call your code.
  • Colour.java - This class is provided for convenience. It helps print to the console in colours.
  • BrickPrinter - Another helper class provided for convenience. It prints one of your stack of Bricks. When provided with one Brick, it prints the Brick and all Bricks above.
  • BrickException - A custom exception. Throw it, when your class is asked to do something illegal, e.g. to "remove something that does not exist".

In the next sections, I'll give you a bit more context on the provided code.

Launcher

To launch your code, use this code block, or download the Launcher class

package bricks;

public class Launcher
{

  /**
   * Main method. Creates various stacks and tests printing.
   *
   * @param args runtime arguments (not required).
   */
  public static void main(String[] args)
  {
    // Start with a first minimal stack of bricks
    Brick baseBrick = new BrickImpl(Colour.RED);
    BrickPrinter.printBricks(baseBrick);
    System.out.println();

    // Add a few atop and check we can still print the stack:
    baseBrick.addNext(new BrickImpl(Colour.RED));
    baseBrick.getNext().addNext(new BrickImpl(Colour.BLUE));
    BrickPrinter.printBricks(baseBrick);
    System.out.println();

    // Add something using the addAtop method
    baseBrick.addAtop(new BrickImpl(Colour.GREEN));
    BrickPrinter.printBricks(baseBrick);
    System.out.println();

    // Now we have a stack of bricks, so we'd expect the Brick to refuse
    // removing the second (you can only remove top bricks)
    baseBrick.removeNext();
    BrickPrinter.printBricks(baseBrick);
    System.out.println();
  }
}

Colour

  • This enum class provides some predefined colours.
  • No changes are necessary to this class.
  • You can download it here: Colour.java

BrickPrinter

  • The BrickPrinter is just a little helper class.
  • No changes are necessary to this class.
  • You can download it here: BrickPrinter.java

BrickException

In the interface description, we've seen that some functions may fail (removing a non existant brick, etc).

  • Whenever something shady happens, your implementation should throw an exception.
  • That's as simple as calling: throw new BrickException("A little explanation");
  • The code for the custom exception is available here: BrickException.java

Step by step

  • Create a new project in IntelliJ
  • Download the provided classes
  • Launch the program (it will fail)
  • Implement your BrickImpl.java class, method by method, until the entire launcher succeeds. (With exception to the end of course, where we want to see an exception.)

Ask for help

Don't go to ChatGPT, but ask for help. The teaching assistant is literally there to help you, if you feel stuck.

Example

The provided launcher starts with:

    // Start with a first minimal stack of bricks
    Brick baseBrick = new BrickImpl(Colour.RED);
    BrickPrinter.printBricks(baseBrick);
    System.out.println();

For this segment to work, you need to implement your BrickImpl constructor.

  • The colour (provided as argument) must be stored in the class.
  • The next reference must be initialized.

Work your way through the launcher, until all methods are implemented.

Save your code!

Maybe you're wondering why this lab unit was all about bricks. Let's take a step back and think about what stacks of bricks actually are: They are just structured objects with certain rules: You can only add to the top, and you can only take form the top. Does that sound like a bit of code that might be useful for a Skyjo implementation ? ;)