Lab 10: Interface segregation
Starter files: code.zip
Objectives
The objectives of this lab are:
Practice enforcing the interface segregation principle.
Create a proper immutable counterpart of a given class.
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:
Your mutable list should have an operation to return an immutable counterpart of itself.
Your immutable list should have an operation to return a mutable counterpart of itself.
Your immutable list should offer all the non-mutating operations that your mutable list offers.
It should not be possible to mutate an immutable list through the mutable counterpart it returns (see note about immutable vs unmodifiable lists above).
A user of
ListADT<T>
andListADTImpl<T>
should be able to use them exactly as they are. That is, there should be no public-facing changes to them.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).
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.Your guarantee of immutability should be stronger than “document clearly that some methods should not be called”.
The
map
function, which is part of theListADT
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.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
Create a new interface
CommonListADT<T>
.Move all the methods from
ListADT<T>
that do not mutate the list to this interface.Change the signature of the
map
method so that it returns aCommonListADT<T>
instead.Make
ListADT<T>
extendCommonListADT<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.
Create a new
ImmutableListADT<T>
interface that extends theCommonListADT<T>
interface.Create a new
MutableListADT<T>
interface that extends theListADT<T>
interface.Add a new method
MutableListADT<T> getMutableList()
to theImmutableListADT<T>
interface.Add a new method
ImmutableListADT<T> getImmutableList()
to theMutableListADT<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.
Add and implement a private method called
addBack
to theImmutableListADTImpl<T>
class.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
why we made this an object adapter instead of a class adapter. Could both options have given us an immutable list?
how you know the client cannot mutate the immutable list. Your answer may seem familiar from the previous part, but now you have an actual implementation to consider.
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:
why we made this a class adapter instead of an object adapter. Could both have worked out here?
why can we mutate the new mutable list without editing the original mutable list. A memory diagram showing references and aliases may help you understand why.