Lab 10: Interface segregation
Objectives
1 Introduction
2 What to do
2.1 Part 1:   Segregating the observation methods from the mutator methods
2.2 Part 2:   Create mutable and immutable list interfaces
2.3 Part 3:   Implement the immutable list
2.4 Part 4:   Implement the mutable list
8.9

Lab 10: Interface segregation

Starter files: code.zip

Objectives

The objectives of this lab are:

1 Introduction

Many applications require lists of data that cannot be changed (e.g. one cannot add to the list, remove anything from the list or change the sequence of the list). For example, one can provide a list of customers, without the ability to change the list in any way. Effectively one can only observe the data in these lists.

In this lab, you must enhance the mutable ListADT classes and interfaces with the ability for such a list to return an immutable counterpart of itself. This immutable list contains the same data in the same sequence as the original list, but it cannot be changed. Conversely you must implement the immutable list such that it can return a mutable counterpart of itself. Again, this mutable list contains the same data in the same sequence as the original list, but it can be mutated.

It is important to note that immutable lists are different from unmodifiable lists (Java has unmodifiable lists, but not immutable). The difference is that while an unmodifiable list cannot be modified directly, it can be modified by mutating the underlying mutable list. That is, unmodifiable lists merely create a wrapper around a regular list. In contrast, it is not possible to mutate an immutable list.

2 What to do

The ListADT classes and interfaces have been provided to you. You may rely on their correctness (i.e. the implementation works correctly and has been tested, you do not need to test it).

Your design must obey the following constraints:

  1. Your mutable list should have an operation to return an immutable counterpart of itself.

  2. Your immutable list should have an operation to return a mutable counterpart of itself.

  3. Your immutable list should offer all the non-mutating operations that your mutable list offers.

  4. It should not be possible to mutate an immutable list through the mutable counterpart it returns (see note about immutable vs unmodifiable lists above).

  5. A user of ListADT<T> and ListADTImpl<T> should be able to use them exactly as they are. That is, there should be no public-facing changes to them.

  6. Because this is a generic implementation, immutability extends only to the list structure and not to the data contained in the list. It may be possible to mutate the data in the list (because there is no easy way to guarantee that this will not happen without assuming something about the data).

  7. Your mutable lists must use the ListADT way of implementation. That is, you are not allowed to start implementing a list from scratch just to support this enhanced functionality.

  8. Your guarantee of immutability should be stronger than “document clearly that some methods should not be called”.

  9. The map function, which is part of the ListADT implementation, should return a list of the appropriate kind. That is, the map of an immutable list should return an immutable list, while that of a mutable list should return a mutable list.

  10. The enhancement (and this entire lab) should be on top of the nodes. That is, you should not modify the nodes at all.

2.1 Part 1: Segregating the observation methods from the mutator methods

  1. Create a new interface CommonListADT<T>.

  2. Move all the methods from ListADT<T> that do not mutate the list to this interface.

  3. Change the signature of the map method so that it returns a CommonListADT<T> instead.

  4. Make ListADT<T> extend CommonListADT<T>.

Note that this change merely refactors the ListADT<T> interface. While it does change the return type of the map method, the actual ListADTImpl continues to return a ListADT<T> object. Thus from the perspective of its clients, the ListADT<T> interface (and its ListADTImpl<T> implementation) are practically unchanged.

2.2 Part 2: Create mutable and immutable list interfaces

The new CommonListADT<T> interface is almost the interface we want for an immutable list. The only thing it is missing is a method to return a mutable counterpart of itself. Similarly the provided ListADT<T> is missing a method that returns an immutable counterpart of itself.

  1. Create a new ImmutableListADT<T> interface that extends the CommonListADT<T> interface.

  2. Create a new MutableListADT<T> interface that extends the ListADT<T> interface.

  3. Add a new method MutableListADT<T> getMutableList() to the ImmutableListADT<T> interface.

  4. Add a new method ImmutableListADT<T> getImmutableList() to the MutableListADT<T> interface.

Discuss with the person next to you why the ImmutableListADT<T> is indeed immutable.

2.3 Part 3: Implement the immutable list

We now implement the ImmutableListADT<T> interface. Since an implementation is already provided, we will reuse it.

Implement the ImmutableListADT<T> interface as a ImmutableListADTImpl<T> class. This class should be implemented as an object adapter for an object of the provided ListADTImpl<T> class. Note that this object is not provided through the constructor, because we want to create an immutable list, not an unmodifiable one. The problematic method is the map method.

We realize that there is no way to actually construct an immutable list that has elements in it! We use the builder pattern for this.

  1. Add and implement a private method called addBack to the ImmutableListADTImpl<T> class.

  2. Add an inner builder class to this class. This builder should have a single method to add an element to the back of this list, along with the build method.

Now implement the map method so that it returns an ImmutableListADTImpl object by looping through this list and creating a mapped list.

Note you cannot implement the getMutableList() method yet because you do not have a MutableListADT implementation.

However, we now have an immutable list representation. We should make sure that the interface methods work as intended. We also should make sure the client cannot accidentally mutate the list in any way from the exposed methods.

With the person next to you, discuss

Write tests for this implementation, and show them to the TA before moving on.

2.4 Part 4: Implement the mutable list

Create a MutableListADTImpl<T> class that implements MutableListADT<T> that is a class adapter over ListADTImpl<T>. The map method implementation should be mostly as given to you, except it will return a MutableListADTImpl<T> object.

The only method that it does not inherit is the new getImmutableList() method. Implement this method by using the ImmutableListADTImpl<T> class, specifically its builder.

Once you have this done, go back to ImmutableListADTImpl<T> and implement getMutableList.

Write tests for this class.

Finally, write a test that puts everything together: create a mutable list, populate it, then create its immutable counterpart. Now get the mutable counterpart of this immutable list and add some more elements to this counterpart. Note that doing so should not mutate the original mutable list, nor the immutable list!

With the person next to you, discuss and make sure you both understand the following: