final practice Flashcards

(43 cards)

1
Q

Why is it technically possible to represent a playing card using six Boolean values? What is the impact of this change of the maintainers of class Card? What is the impact of this change on the users of class Card?

A

Ultimately this reduces to an integer-to-binary base conversion problem. We need six boolean values (i.e., bits) because ceil(log2(52))=6. To make things a bit more interesting, we can split the problem into mapping the rank and the suit to bits separately, and create a mini-decision tree for the suit (red vs. black, high vs. low), and apply the standard base conversion algorithm for the rank.

The impact of this design is unnecessary complexity. Assuming they don’t need to look at the source code, there is no impact on the users of the class, because the new implementation respects the original interface.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
2
Q

Rewrite class Card so that it is internally represented as six Boolean values.

A

package chapter2;

/**
 * Implementation of a playing card. This class yields immutable objects.
 * This version is a facetious implementation with 6 Boolean values.
 */
public class Card2
{
	private boolean aIsRed = false;
	private boolean aIsHighSuit = false;
	private boolean aRank1 = false;
	private boolean aRank2 = false;
	private boolean aRank3 = false;
	private boolean aRank4 = false;
	/**
	 * How to properly write automated tests such as this one 
	 * is the topic of Chapter 5.
	 */
	public static void main(String[] args)
	{
		for( Rank rank : Rank.values() )
		{
			for( Suit suit : Suit.values() )
			{
				Card2 card = new Card2(rank, suit);
				assert card.getRank() == rank;
				assert card.getSuit() == suit;
				System.out.println(card);
			}
		}
	}
	/**
	 * Creates a new card object.
	 * 
	 * @param pRank The rank of the card.
	 * @param pSuit The suit of the card.
	 * @pre pRank != null
	 * @pre pSuit != null
	 */
	public Card2(Rank pRank, Suit pSuit)
	{
		assert pRank != null && pSuit != null;
		fromSuit(pSuit);
		fromRank(pRank);
	}
	private void fromSuit(Suit pSuit)
	{
		if( pSuit == Suit.HEARTS || pSuit == Suit.DIAMONDS )
		{
			aIsRed = true;
		}
		if( pSuit == Suit.HEARTS || pSuit == Suit.SPADES )
		{
			aIsHighSuit = true;
		}
	}
	private void fromRank(Rank pRank)
	{
		int value = pRank.ordinal();
		aRank1 = value % 2 == 1;
		value /= 2;
		aRank2 = value % 2 == 1;
		value /= 2;
		aRank3 = value % 2 == 1;
		value /= 2;
		aRank4 = value % 2 == 1;
		value /= 2;
	}
	/**
	 * @return The rank of the card.
	 */
	public Rank getRank()
	{
		int value = 0;
		if( aRank4 == true )
		{
			value += 8;
		}
		if( aRank3 == true )
		{
			value += 4;
		}
		if( aRank2 == true )
		{
			value += 2;
		}
		if( aRank1 == true )
		{
			value += 1;
		}
		return Rank.values()[value];
	}
	/**
	 * @return The suit of the card.
	 */
	public Suit getSuit()
	{
		if( aIsRed )
		{
			if( aIsHighSuit )
			{
				return Suit.HEARTS;
			}
			else
			{
				return Suit.DIAMONDS;
			}
		}
		else
		{
			if( aIsHighSuit )
			{
				return Suit.SPADES;
			}
			else
			{
				return Suit.CLUBS;
			}
		}
	}
	@Override
	public String toString()
	{
		return String.format("%s of %s", getRank(), getSuit());
	}
}
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
3
Q

Design and implement a well-encapsulated abstraction to represent a Handof cards in a player’s hand as a Java class. A Hand should be able to contain between 0 and N cards, where N is a a parameterizable upper bound that will depend on the card game being played (e.g., 5 for Draw Poker, 13 for Bridge, etc.). Implement the following services on a Hand: add(Card), remove(Card), contains(Card), isEmpty(), size(), and isFull(). Find a way to provide access to the cards in the hand. Ensure that all the rules of encapsulation seen in Chapter 2 are respected. Assume there are no duplicate cards and that there only ever exists only a single instance that represents a given card in a running program (more on this in Chapter 4).

Make it possible to compare two Handobjects using the Comparable Compare hands by increasing number of cards in the hand. Write a small program to test your class. You do not need to handle the case where the argument is null.

Extend the code of Handto make it possible to compare two Hand objects using the Comparator Implement two different hand comparison strategies, by increasing or decreasing number of cards in the hand. Define static factory methods in the Hand class to return anonymous instances of comparators for the different comparison strategies. You do not need to handle the case where the argument is null.

Add a static factory method to class Handthat takes an Rank as parameter and returns an instance of a class that can compare hands in increasing number of cards of that rank in the hand. Clients should use this method to compare hands by number of aces, or number kings, or number of fours, etc. For example, if the client chooses to compare hands by number of aces, a hand with one ace should come before a hand with two aces. If two hands have the same number of aces, they should be considered equal and their order does not matter.

A

package chapter3;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;

/**
 * A collection of cards in a player's hand. Basic version for Exercise 1.
 */
public class Hand implements Iterable, Comparable
{
	private final List aCards = new ArrayList<>();
	private final int aMaxCards;
	/**
	 * Creates a new, empty hand, which can hold
	 * a maximum of pMaxCards.
	 * 
	 * @param pMaxCards The maximum number of cards allowed in this hand.
	 * @pre pMaxCards > 0;
	 */
	public Hand(int pMaxCards)
	{
		assert pMaxCards > 0;
		aMaxCards = pMaxCards;
	}
	/**
	 * Add pCards to the hand.
	 * @param pCard The card to add.
	 * @pre !isFull()
	 * @pre pCard != null;
	 */
	public void add(Card pCard)
	{
		assert pCard != null;
		assert !isFull();
		aCards.add(pCard);
	}
	/**
	 * @return True if the number of cards in the hand
	 * is the maximum number of cards allowable, as specified
	 * in the constructor.
	 */
	public boolean isFull()
	{
		return aCards.size() == aMaxCards;
	}
	/**
	 * @return True if there are no cards in this hand.
	 */
	public boolean isEmpty()
	{
		return aCards.size() == 0;
	}
	/**
	 * Removes pCards if it is in the hand. If it is not in the
	 * hand, does nothing. 
	 * 
	 * @param pCard The card to remove.
	 * @pre pCards != null;
	 */
	public void remove(Card pCard)
	{
		assert pCard != null;
		aCards.remove(pCard);
	}
	/**
	 * @param pCard A card to check for containment.
	 * @return True if pCard is a card in this hand.
	 * @pre pCard != null
	 */
	public boolean contains(Card pCard)
	{
		assert pCard != null;
		return aCards.contains(pCard);
	}
	@Override
	public Iterator iterator()
	{
		return aCards.iterator();
	}
	@Override
	public int compareTo(Hand pHand)
	{
		return aCards.size() - pHand.aCards.size();
	}
	public static Comparator createAscendingComparator()
	{
		return new Comparator() {
		@Override
		public int compare(Hand pHand1, Hand pHand2)
		{
			return pHand1.aCards.size() - pHand2.aCards.size();
		}};
}
	public static Comparator createDescendingComparator()
	{
		return new Comparator() {
		@Override
		public int compare(Hand pHand1, Hand pHand2)
		{
			return pHand2.aCards.size() - pHand1.aCards.size();
		}};
}
	/**
	 * @return The number of cards currently in the hand.
	 */
	public int size()
	{
		return aCards.size();
	}
/**
 * Creates a comparator that compares hands in terms of ascending number
 * of cards of rank pRank in the hand.
 * 
 * @param pRank The rank to test against.
 * @return A new Comparator instance that can compare by number
 * of cards of the specified rank.
 */
public static Comparator createByRankComparator(Rank pRank)
{
	return new Comparator()
	{
		@Override
		public int compare(Hand pHand1, Hand pHand2)
		{
			return countCards(pHand1, pRank) - countCards(pHand2, pRank);
		}
			private int countCards(Hand pHand, Rank pRank)
			{
				int total = 0;
				for( Card card : pHand)
				{
					if( card.getRank() == pRank )
					{
						total++;
					}
				}
				return total;
			}
		};
	}
	/**
	 * This is the driver program.
	 * @param args
	 */
	public static void main(String[] args)
	{
		Hand hand1 = new Hand(5);
		Hand hand2 = new Hand(5);
		Deck deck = new Deck();
		deck.shuffle();
		hand1.add(deck.draw());
		hand2.add(deck.draw());
		hand2.add(deck.draw());
		System.out.println(hand1.compareTo(hand2));
		System.out.println(hand2.compareTo(hand1));
		System.out.println(Hand.createAscendingComparator().compare(hand1, hand2));
		System.out.println(Hand.createDescendingComparator().compare(hand1, hand2));
	}
}
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
4
Q

Considering the version of class Card (Links to an external site.)seen in Chapter 3. How many concrete states form the life cycle of objects of the class?

A

A single one because the class yields immutable objects. Even though, as a whole, objects of class Card can represent 52 distinct values, a given object will only represent a single one throughout its lifetime.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
5
Q

Does it make sense to model the abstract state space of a Java interface? Why or why not?

A

No because methods of a Java interface are implicitly abstract and cannot have implementations. A Java abstract class can have instance methods that implements a default behavior.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
6
Q

Change the version of class Card (Links to an external site.)seen in Chapter 3 to support a “white” and a “black” joker. Use optional types to return the rank and suit of cards. When compared with other cards, the white joker should come after any card except the black joker, and the black joker should come after any cards including the other joker. Modify the Deck (Links to an external site.) class to include the two jokers in the shuffle.

A

There are various solutions. Here is one possibility: Card4.java, Deck4.java. This solution hides the Joker enumerated type from the clients, but has a non-intuitive way to construct jokers that could be easily improved with factory methods.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
7
Q

Change Chapter 3’s version of class Card (Links to an external site.)to ensure the uniqueness of its instances through the use of the Flyweight Design Pattern using a pre-initialization strategy.

A

Try modifying it to use a Map to store the flyweights.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
8
Q

Change the code written in Exercise 9 so that card flyweights are lazily created. Using currentTimeMillis() (Links to an external site.), compare the time needed to obtain all 52 cards for both approaches. What do you conclude?

A

The adapted version that uses a bidimensional array for the flyweight store can be found here.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
9
Q

Consider interface Show (Links to an external site.)and the two implementing classes Movie (Links to an external site.) and Concert (Links to an external site.). Apply the Composite design pattern to provide support for a kind of show that is a combination of between two and five shows, inclusively. Write a client program that creates a show that consists of a concert followed by two movies, where the two movied are, together, represented by a single Show

A

The following is a sample Composite and Client (first block). Other approaches are possible to specify and enforce the cardinality constraint for initializing the composite show, e.g., design by contract or overloaded constructors.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
10
Q

Change the code of java (Links to an external site.)so that it shows a hand of 13 cards, which each card overlaid on another, face down. Once the user clicks the button, the cards should be flipped so that they are face-side up. Every subsequent button click should reveal a new, random, hand, also face-side up. Use the Composite and Decorator patterns applied to the Icon interface to solve the problem.

A

The first steps are to create helper classes CompositeIcon and ShiftedIcon. Then a method to create the desired icon becomes relatively simple. Assuming SHIFT_X and SHIFT_Y denote int values that represent a number of pixels.

private Icon createHandIcon(Card[] pHand, boolean pHidden)
{
	CompositeIcon result = new CompositeIcon();
	for( int i = 0; i < pHand.length; i++ )
	{
		result.addIcon(new ShiftedIcon(pHidden?CardImages.getBack():CardImages.getCard(pHand[i]), SHIFT_X, SHIFT_Y));
	}
	return result;
}
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
11
Q

We add the following requirement to the system created and documented as part of Exercises 1-5: We need a way to obtain all the shows in a composite show so that a client can “unpack” a composite. Implement this requirement using an iterator in a way that does not change the Show interface.

A

The solution entails declaring CompositeShow to implement Iterable:

public class CompositeShow implements Show, Iterable
This requires implementing the iterator() method in CompositeShow, which is straightforward:
@Override
public Iterator iterator()
{
   return aShows.iterator();
}
The main benefit of declaring only CompositeShow to be iterable is that we avoid having to have this behavior defined for (and implemented by) classes for which it makes no sense, namely leaves such as Concert or Movie. The disadvantage is that it requires client code that works instances of Show to explicitly check whether an instance can be unpacked or not:
Show show = ...;
if( show instanceof CompositeShow )
{
   for( Show subshow : show )
   { /* ... */ }
}
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
12
Q

Implement a variant of the feature described in Exercise 7, but this time by modifying the Show interface.

A

To add a method iterator() to the interface of Show our best bet is to declare Show to extend Iterable so that we also benefit from the subtyping relationship this introduces:

public interface Show extends Iterable
The advantage of this solution is that client code can be more polymorphic:

Show show = …;
for( Show subshow : show )
{ /* … */ } // Not executed if an empty iterator is returned.
The disadvantage of this approach is that an implementation of iterator() must also be supplied for classes that have nothing to unpack. However, with Java 8 it is a relatively minor concern because we can declare a default method that returns an empty iterator in the Show interface:

@Override
default Iterator iterator()
{
   return Collections.emptyIterator();
}
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
13
Q

Create a new class DoubleBill that represents a sequence of exactly two movies, but that is not a Decorator. Make sure your implementation respects the requirement expressed in Exercise 8.

A

For DoubleBill to work with Movie instances specifically (as opposed to Show instances), and thereby not be a constructor, it is simply a matter of declaring the two aggregated objects to be of type Movie:

/**
 * Represents a show that consists of the screening of two movies
 * in sequence.
 */
public class DoubleBill implements Show
{
   private Movie aMovie1;
   private Movie aMovie2;
   /**
    * @param pMovie1 The first movie.
    * @param pMovie2 The second movie.
    */
   public DoubleBill(Movie pMovie1, Movie pMovie2)
   {
      aMovie1 = pMovie1;
      aMovie2 = pMovie2;
   }
   @Override
   public String description() 
   { 
      return String.format("%s and %s", aMovie1.description(), aMovie2.description()); 
   }
   @Override
   public int runningTime() 
   { 
      return aMovie1.runningTime() + aMovie2.runningTime(); 
   }
}
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
14
Q

Implement a copy constructor for Concert, Movie, and DoubleBill, in this order. Assume all Show object need to be copied deeply (which includes making copies of the object stored in fields of any type except primitive types and String). Then implement a copy constructor for IntroducedShow. Why is the solution problematic?

A

The copy constructor for Movie and Concert consist of trivial field initialization statements. Here is the one for Movie:

public Movie(Movie pMovie)
{
   aTitle = pMovie.aTitle;
   aYear = pMovie.aYear;
   aRunningTime = pMovie.aRunningTime;
}
Here is the one for Concert, showing a different way of accomplishing the same thing:
public Concert(Concert pConcert)
{
   this(pConcert.aTitle, pConcert.aPerformer, pConcert.aRunningTime);
}
The copy constructor for DoubleBill needs to make a copy of the underlying movies to fulfill the deep-copy requirement. This can be accomplished by using the just-implemented copy constructor for Movie.
public DoubleBill(DoubleBill pDoubleBill)
{
   aMovie1 = new Movie(pDoubleBill.aMovie1);
   aMovie2 = new Movie(pDoubleBill.aMovie2);
}
The problem for IntroducedShow is that it aggregates an instance of the interface type Show. As a consequence of the polymorphism, the actual type of the Show object aggregated may only be known at run-time, so it is not possible to use a copy constructor in the source code without introducing a battery of inelegant and unsafe checks.
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
15
Q

Add support for polymorphic copyinginto the Show type hierarchy. Add a copy() method to the Show interface with the semantics that it does a deep copy of the object as defined for Exercise 10.

A

For the “leaf” classes (Movie, Concert, and DoubleBill) the simplest is probably to return a new object using the copy constructor. For example, for Movie:

@Override
public Show copy()
{
   return new Movie(this);
}
For classes that polymorphically aggregate one or more Show objects, these objects must also be copied:
@Override
public Show copy()
{
   return new IntroducedShow(aSpeaker, aSpeechTime, aShow.copy());
}
@Override
public Show copy()
{
   CompositeShow copy = new CompositeShow();
   for( Show show : aShows )
   {
      copy.aShows.add(show.copy());
   }
   return copy;
}
One Java feature that will be introduced in more detail in Chapter 7 but that is worth mentioning here is that, in this case, it is perfectly legal to declare a return type for an implementing method that is a subtype of the interface method. This feature is called covariant return types, and it means that method copy() can declare to return the actual type being returned. For example:
@Override
public Movie copy()
{
   return new Movie(this);
}
The major benefit of this is that client code that holds a reference to a Movie can copy that movie and assign the result to a variable of type Movie without casting:

Movie movie = …;
Movie copy = movie.copy();

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
16
Q

Apply the Commanddesign pattern so that is is possible to clear a program, add shows to it, and remove shows from it using Command Write a sample client code that adds two movies to a program, removes one, then clears the program (from the remaining movie). Implement commands using anonymous classes (which will require you to write factory methods for them).

A

We need a Command interface:

interface Command
{
	void execute();
}
and within class Program three instance methods to act as command factories:
public Command createAddCommand(Show pShow, Day pDay)
{
   return new Command() {
      @Override
      public void execute()
      {
         add(pShow, pDay);				
      }
   };
}
public Command createRemoveCommand(Day pDay)
{
   return new Command() 
   {
      @Override
      public void execute()
      {
         remove(pDay);				
      }
   };
}
public Command createClearCommand()
{
   return new Command() 
   {
      @Override
      public void execute()
      {
         clear();				
      }
   };
}
Executing commands can now be done through command objects, e.g.:
Program program = new Program();
program.createAddCommand(new Movie("Title",2000,120), MONDAY).execute();
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
17
Q

Extend the design created in Exercise 14 to allow commands to be undoable. Extend the client code of Exercise 14 to undo all commands executed, and verify that after each command can be properly undone.

A

The Command interface now needs an undo() method:

interface Command
{
	void execute();
	void undo();
}
Consequently all the command factories need an implementation of undo. The one for undoing additions is fairly straightforward:
public Command createAddCommand(Show pShow, Day pDay)
{
   return new Command() 
   {
      @Override
      public void execute()
      {
         add(pShow, pDay);				
      }
      @Override
      public void undo()
      {
         remove(pDay);				
      }
   };
}
For undoing removal, it's slightly more tricky as we need to keep a reference to the show that was removed (so that we can restore it). We can do with by declaring a field in our anonymous class:
public Command createRemoveCommand(Day pDay)
{
   return new Command() 
   {
      Show show = aShows.get(pDay);
      @Override
      public void execute()
      {
         show = aShows.get(pDay);
         remove(pDay);				
      }
      @Override
      public void undo()
      {
         add(show, pDay);
      }
   };
}
For clearing the program, things a more involved, since it requires making a copy of the map of shows and restoring the map in Program using the values in the copy.
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
18
Q

Build a CommandProcessorthat is an abstraction that allows clients to execute commands, store executed commands, and undo the last executed command. Ensure your design respects the Law of Demeter.

A
public class CommandProcessor
{
   private final List aCommands = new ArrayList<>();
   public void consume(Command pCommand)
   {
      pCommand.execute();
      aCommands.add(pCommand);
   }
   public void undoLast()
   {
      assert !aCommands.isEmpty();
      Command command = aCommands.remove(aCommands.size()-1);
      command.undo();
   }
}
19
Q

Extends the CommandProcessor so that is supports redoing commands (that is, executing undone commands).

A

We need another stack of undone commands:

public class CommandProcessor implements CommandProcessor
{
   private final List aExecutedCommands = new ArrayList<>();
   private final List aUndoneCommands = new ArrayList<>();
   @Override
   public void consume(Command pCommand)
   {
      pCommand.execute();
      aExecutedCommands.add(pCommand);
   }
   @Override
   public void undoLast()
   {
      assert !aExecutedCommands.isEmpty();
      Command command = aExecutedCommands.remove(aExecutedCommands.size()-1);
      command.undo();
      aUndoneCommands.add(command);
   }
   public void redoLast()
   {
      assert !aUndoneCommands.isEmpty();
      Command command = aUndoneCommands.remove(aUndoneCommands.size()-1);
      consume(command);
      aExecutedCommands.add(command);
   }
}
20
Q

Modify your design so that the operations of a CommandProcessor are encapsulated within a Program. In other words, client code should be able to once again call add remove and clear on an instance of Program to perform these actions, but the actions would be stored as commands within a program and be undoable through an undoLast() method added to the interface of Program. How can you avoid pushing Program towards a God class responsible for two different things: managing a program and managing a stack of commands?

A

We can reuse our CommandProcessor and aggregate an object of this type within Program. The command factory methods can then be made private, and Program’s interface methods can then internally create a command and request that the processor consume it. The process is illustrated for method clear(). To decouple class Program from the implementation of a command processor, we can extract its interface, implement it, and inject that dependency through the constructor of Program.

21
Q

Consider the following implementation of class Deck. Apply the Observer design pattern to make the class observable. Assume we have two types of concrete observers: a DrawLogger with prints out a description of any card drawn, and a SizeStatus observer that prints out the size of the deck whenever it changes. Design and implement all required classes and interfaces, and write a small piece of client code to demonstrate the operation of the pattern. Use the push data-flow strategy. The concrete observers can be implemented as simple println statements.

A

The challenge in this question was that SizeStatus requires the size of the ObservableDeck, so it’s tempting to push this information through the callbacks. However, an important principle for applying the Observer pattern is that the callbacks should reflect state changes on the model, and the data passed to callbacks should be consistent with this. The anti-pattern in this case is to overfit the design of the Observer interface to what a specific concrete observer might need. So in this case I followed the hard line and designed my three callbacks to map as directly as possible to the state changing methods of the model, and built a workaround in the SizeStatus, namely to compute the current size of the deck based solely on information received through the callbacks (and the assumption that the deck has 52 cards).

22
Q

Change the design of your observable Deck from exercise 1 to use the pull data-flow strategy, using the interface segregation principle to strengthen the constraint that the observers should not change the state of the Deck object.

A

Here the challenge lay elsewhere, namely that normally a deck does not store cards drawn. With the pull model, this potentially useful information would become irremediably unavailable to the observers, so it becomes necessary to keep it. Ultimately, it might be a good idea to combine data pushing and pulling for an Observable deck.

23
Q

Change the design of your observable Deckfrom exercise 1 so that all the observer-supporting code is in a superclass of deck. What are the advantages and disadvantages of this decision?

A

The idea of factoring out some functionality into a super class is for that functionality to be reusable across different subclasses. For this reason, the general observable behavior needs to be general enough to work on multiple applications of the Observer patterns. To take it to the limit, I wrote a solution that uses the very general (but not generic!) support offered by java.util.Observable.

ObservableDeck3.java

Some of the advantages of this solution are that the code of ObservableDeck3 is now decluttered from any observer-related code expect for the notification calls. In fact, it might even make sense to call it simply Deck. It’s also not necessary to define a new Observer interface, because that is in the library.

The disadvantages are numerous:

If we also want an instance of Deck that is not observable, we need to duplicate a lot of the class's code;
Because Java is single-inheritance, it is not possible to have a design where ObservableDeck is a subclass of a class besides Observable;
We cannot design individual callbacks specific to our situation, we have to rely on a single general one offered by Observer and use an event type instead;
The code we write to use java.util.Observer is brittle because of the downcasts required.
24
Q

Change the design of the previous exercise to include an AbstractVisitor Move the traversal code to this class, and change your PrintVisitorso it extends AbstractVisitor. Draw a sequence diagram for the visit of a directory with one file and one symbolic link, and make sure to see the difference with the diagram created as part of exercise 12.

A

For this version we can return to our simple abstract visitor:

interface Visitor
{
   void visitFile(File pFile);
   void visitDirectory(Directory pDirectory);
   void visitSymbolicLink(SymbolicLink pLink);
}
We will implement default behavior in an AbstractVisitor. Strictly speaking the class does not need to be declared abstract because it's possible and not harmful to instantiate it, but the declaration helps further clarify our intent and it's also not harmful to declare it abstract.
public abstract class AbstractVisitor implements Visitor
{
   @Override
   public void visitFile(File pFile)
   {}
   @Override
   public void visitDirectory(Directory pDirectory)
   {
      for( Node node : pDirectory )
      {
         node.accept(this);
      }
   }

@Override
public void visitSymbolicLink(SymbolicLink pLink)
{}
}
Because the graph traversal code is in the visitor, we can remove it from Directory.accept, which now looks like the other accept methods:

public void accept(Visitor pVisitor)
{
   pVisitor.visitDirectory(this);
}
Finally, our PrintVisitor can be written as a subclass of AbstractVisitor:
public class PrintVisitor extends AbstractVisitor
{
   private static final String TAB = "   ";

private StringBuilder aIndent = new StringBuilder();

private void indent()
{
aIndent.append(TAB);
}

private void unindent()
{
aIndent.delete(aIndent.length()-TAB.length(), aIndent.length());
}

   @Override
   public void visitFile(File pFile)
   {
      System.out.println(aIndent.toString() + pFile.name());
   }
   @Override
   public void visitDirectory(Directory pDirectory)
   {
      System.out.println(aIndent.toString() + pDirectory.name());
      indent();
      super.visitDirectory(pDirectory);
      unindent();
   }

@Override
public void visitSymbolicLink(SymbolicLink pLink)
{
System.out.println(aIndent.toString() + pLink.name());
}
}

25
Consider the sample class Node (Links to an external site.)class hierarchy to support a file system. Apply the Visitor pattern to support the traversal of object graphs created from this class hierarchy. Implement the traversal code in the Node hierarchy, then write client code to try out your visitor support. Write a PrintVisitor to pretty-print all the nodes in a sample file tree. Try indenting the names of the nodes based on their depth in the tree. Draw a sequence diagram for the visit of a directory with one file and one symbolic link.
A minimal application of the Visitor pattern requires a Visitor interface with a visit method for each concrete node of interest: ``` interface Visitor { void visitFile(File pFile); void visitDirectory(Directory pDirectory); void visitSymbolicLink(SymbolicLink pLink); } We also need to add an accept method to the root of our target type hierarchy: ``` ``` public interface Node { String name(); void accept(Visitor pVisitor); } The implementation of accept for leaf nodes simply consists in a call to the appropriate visit method: ``` ``` class File extends AbstractNode { /* ... */ ``` ``` public void accept(Visitor pVisitor) { pVisitor.visitFile(this); } } ``` ``` class SymbolicLink extends AbstractNode { /* ... */ ``` ``` public void accept(Visitor pVisitor) { pVisitor.visitSymbolicLink(this); } } To implement a non-indenting PrintVisitor is simply a matter of printing the name of a node whenever one is encountered: ``` ``` public class PrintVisitor implements Visitor { @Override public void visitFile(File pFile) { System.out.println(pFile.name()); } ``` @Override public void visitDirectory(Directory pDirectory) { System.out.println(pDirectory.name()); } @Override public void visitSymbolicLink(SymbolicLink pLink) { System.out.println(pLink.name()); } } To use the visitor, we create an instance of it and pass it to the accept method of a node: ``` Directory root = ...; root.accept(new PrintVisitor()); To add indentation support to PrintVisitor is a bit tricky, because the traversal of the node tree is triggered from the target class hierarchy (as opposed to the visitor), so implementations of Visitor do not have the flexibility to add instructions before and after the children of a node are traversed without additional support. This support can be added using pre- and post- visit methods for aggregate nodes instead of a single visit method. We can implement this feature by adding such methods for Directory nodes to the visitor interface: ``` ``` interface Visitor { void visitFile(File pFile); void preVisitDirectory(Directory pDirectory); void postVisitDirectory(Directory pDirectory); void visitSymbolicLink(SymbolicLink pLink); } The implementation of accept for Directory now becomes: ``` ``` public void accept(Visitor pVisitor) { pVisitor.preVisitDirectory(this); for( Node node : this) { node.accept(pVisitor); } pVisitor.postVisitDirectory(this); } With this structure, it is now possible to implement an indenting PrintVisitor: ``` ``` public class PrintVisitor implements Visitor { private static final String TAB = " "; ``` private StringBuilder aIndent = new StringBuilder(); private void indent() { aIndent.append(TAB); } private void unindent() { aIndent.delete(aIndent.length()-TAB.length(), aIndent.length()); } ``` @Override public void visitFile(File pFile) { System.out.println(aIndent.toString() + pFile.name()); } ``` @Override public void preVisitDirectory(Directory pDirectory) { System.out.println(aIndent.toString() + pDirectory.name()); indent(); } ``` @Override public void postVisitDirectory(Directory pDirectory) { unindent(); } ``` @Override public void visitSymbolicLink(SymbolicLink pLink) { System.out.println(aIndent.toString() + pLink.name()); } }
26
Consider a list of movies (Links to an external site.). Write code to create a new ArrayListof all the movies, then use method removeIf of ArrayList to remove all movies produced before 1960. First, use an anonymous class as the explicit argument to removeIf. Once this work, replace the anonymous class with a lambda expression written as a block of statements, and with type parameter. Finally, transform your lambda expression once again so that it is defined as an expression, has no type parameter or parentheses.
With an anonymous class: ``` ArrayList movies = new ArrayList<>(Movies.movies()); movies.removeIf( new Predicate() { @Override public boolean test(Movie pMovie) { return pMovie.year() < 1960; } }); As a lambda expression: ``` movies.removeIf( movie -> movie.year() < 1960 );
27
Add a method isOld(): booleanto class Movie, and use a reference to this method in the code of the previous exercise.
If we add method isOld to class Movie: ``` public boolean isOld() { return aYear < 1960; } then the expression can use a method reference: ``` movies.removeIf(Movie::isOld);
28
Use the method forEachto print the description of all movies (Links to an external site.) to System.out. First, use an anonymous class. Then, write a lamda expression. Then, write a static method print(Movie): void that encapsulates the printing behavior, and use this method in the lambda expression. Then, replace the lambda expression with a method reference. Finally, notice that method println has a type signature that is applicable to the argument of forEach. Use the reference System.out::println in the final version of the exercise.
With an anonymous class: ``` Movies.movies().forEach(new Consumer() { @Override public void accept(Movie pMovie) { System.out.println(pMovie); } }); With a lambda expression: ``` ``` Movies.movies().forEach(movie -> System.out.println(movie)); With a reference to a static method, assumed to be in class Exercise3: ``` Movies.movies().forEach(Exercise3::print); ``` private static void print(Movie pMovie) { System.out.println(pMovie); } And with a reference to System.out.println(Object): ``` Movies.movies().forEach(System.out::println);
29
Use method sort (Links to an external site.)to sort the movies (Links to an external site.) in order of increasing running time, using a lambda expression to define the required comparator. Move this lambda expression to a static factory method static createByTimeComparator(): Comparator and call this method as the argument to sort. Notice how this code compiles even though the type argument of the collection (Movie) and the type argument of the comparator (Show) don't match exactly.
List movies = Movies.movies(); movies.sort((movie1, movie2) -> movie1.time() - movie2.time()); If we move the lambda expression to a static factory: private static Comparator createByTimeComparator() { return (show1, show2) -> show1.time() - show2.time(); } the client code becomes: movies.sort(createByTimeComparator());
30
Transform the static comparator factory from Exercise 5 into a simple static method declaration compareByTime(Show, Show):int. Then, change the code of Exercise 4 to use this method implementation instead.
We transform the factory into a simple comparison method: private static int compareByTime(Show pShow1, Show pShow2) { return pShow1.time() - pShow2.time(); } We can then refer to it wherever a function type (Show,Show)->int is expected: movies.sort(Exercise5::compareByTime);
31
Use the methods of class Comparatorto create a comparator that compares movies by running time, then by title.
Assuming we have the static import: import static java.util.Comparator.comparing; the solution is: List movies = Movies.movies(); movies.sort(comparing(Movie::time).thenComparing(Movie::title));
32
Turn class Movieinto a Flyweight class with lazily-instantiated flyweights where the flyweight store is a Map and the key is the title (assumed to be unique for this exercise). Use computeIfAbsent with a lambda expression in the implementation of your flyweight object accessor method.
As usual with the Flyweight pattern the first step is to make the constructor(s) private. Then we need a static flyweight store: private static final Map aMovies = new HashMap<>(); and a static accessor method: ``` public static Movie get(String pTitle, int pYear, int pTime) { return aMovies.computeIfAbsent(pTitle, title -> new Movie(pTitle, pYear, pTime)); } In this simple exercise there's no way to get the year and time for a movie, so we have to provide all the information required to create an object (title, year, time) to the accessor method. ```
33
Write a stream-based expression that prints the running time of all movies in the database in the format: ``HH:mm` (hours, minutes). Except for the terminal operation, use only mapping operations and method references. You can define helper methods as necessary.
I created a helper function to turn a number of minutes into the requires output: private static String toHHMM(int pMinutes) { assert pMinutes >=0; return String.format("%d:%02d", pMinutes/60, pMinutes%60); } The solution is then: Movies.movies().stream() .map(Movie::time) .map(Exercise8::toHHMM) .forEach(System.out::println);
34
Write a stream-based expression that evaluates to a list that contains the three longest movies from before the year 2000, in reverse alphabetical order of title.
To make the code more readable I import the following static members: import static java.util.Comparator.comparing; import static java.util.stream.Collectors.toList; The solution: List mySelection = Movies.movies().stream() .filter(movie -> movie.year() < 2000) .sorted(comparing(Movie::time).reversed()) .limit(3) .sorted(comparing(Movie::title).reversed()) .collect(toList());
35
Write a stream-based expression to create a map that group movies by decade. For example, all the movies produced in the years 1950-1959 should be accessible using the key 50s, etc.
This exercise is easier to organize with a helper function that returns the decade a Movie was produced in, for example: private static String decade(Movie pMovie) { return String.format("%02ds", ((pMovie.year()-1900)%100)/10*10); } Assuming Collectors.groupingBy is statically imported, we have: Map> result = Movies.movies().stream() .collect(groupingBy(Exercise14::decade));
36
Change the code of Concert so that the field aPerformer is private. What are possible solutions for accessing this information?
A solution that would make proper use of inheritance would be to access aPerformer through a getter method, either public or protected.
37
Consider the Show (Links to an external site.) interface and the two implementing classes Movie (Links to an external site.) and Concert (Links to an external site.). Create a class SponsoredConcert that is a subtype of Concert with two additional pieces of data: a sponsor name (String) and a sponsor time (int) that represents the number of minutes the sponsor is allocated to advertise as part of the show. A call to description() on an instance of SponsoredConcert should include the title and performer of the concert, along with the name of the sponsor. A call to time() on an instance of SponsoredConcert should return the time of the concert plus the time allocated to the sponsor.
``` public class SponsoredConcert extends Concert { private String aSponsor; private int aSponsorTime; ``` ``` public SponsoredConcert(String pTitle, String pPerformer, int pTime, String pSponsorName, int pSponsorTime) { super(pTitle, pPerformer, pTime); aSponsor = pSponsorName; aSponsorTime = pSponsorTime; } ``` ``` @Override public String description() { return String.format("%s by %s sponsored by %s", title(), aPerformer, aSponsor); } ``` ``` @Override public int time() { return super.time() + aSponsorTime; } } ```
38
Create an abstract class AbstractShow that groups all the fields that Concert and Movie have in common. Refactor your class hierarchy to use this abstract class with as tight encapsulation as possible.
Here is AbstractShow: ``` public abstract class AbstractShow implements Show { private String aTitle; private int aTime; ``` ``` protected AbstractShow(String pTitle, int pTime) { aTitle = pTitle; aTime = pTime; } ``` public String title() { return aTitle; } public void setTitle(String pTitle) { aTitle = pTitle; } public int time() { return aTime; } ``` public void setTime(int pTime) { aTime = pTime; } } And here is the refactored Concert as an example. Class Movie is conceptually similar: ``` ``` public class Concert extends AbstractShow { protected String aPerformer; ``` ``` public Concert(String pTitle, String pPerformer, int pTime) { super(pTitle, pTime); aPerformer = pPerformer; } ``` ``` @Override public String description() { return String.format("%s by %s", title(), aPerformer); } } ```
39
We want to ensure that calling method description() on an instance of any type of Show returns a string with the following format: $TITLE$ [...] ($TIME$ minutes) where $TITLE is a placeholder for the title of the show, and $TIME is a placeholder for the total running time of the show. Implement this functionality by applying the Template Method design pattern.
We add the follow template method in AbstractShow: ``` @Override public final String description() { return String.format("%s: %s (%d minutes", title(), extra(), time()); } ``` protected abstract String extra(); The template method need to get additional information from subclasses. This can be added to the design through the use of a call to an abstract method extra() that will return the extra information. Sample implementation of extra() in Concert: ``` @Override protected String extra() { return "by " + aPerformer; } and in SponsoredConcert: ``` ``` @Override public String extra() { return super.extra() + " sponsored by " + aSponsor; } ```
40
Consider the class AbstractShow created as part of the above exercises. Add a method setTitle(String) in AbstractShow to set the title of the show. Because the title of a movie does not change after the movie is released, override setTitle to throw an UnsupportedOperationException (Links to an external site.). Similarly, implement a method setTime in AbstractShow with an input precondition of > 0, and override this method in Movie to have the precondition > 10. Does class Movie respect the Liskov Substitution Principle? Support your answer with a piece of demonstration client code.
Both changes are examples of violation of the Liskov Substitution Principle. Adding an additional (checked) exception forces the clients of the supertype to catch or propagate more exception types. Adding a stricter precondition forces the clients of the supertype to do additional input validation before providing an argument to the function. Because of the impact on the client code, the type Movie is not substitutable for another concrete subtype of Show.
41
Assume class AbstractShow provides a method setTime(int minutes). Add an additional method setTime(int hours, int minutes) that sets the time of the show as hours * 60 + minutes. Is this a case of overloading or overriding? Now remove the implementation of setTime(int hours, int minutes) and add it to class Movie instead. Is this a case of overloading or overriding? Does it make a difference whether we place this method in AbstractShow or Movie?
This is a case of overloading, independently of where we locate the method. Although the name of the method is the same, its signature (name and parameter types) is different. It matters where we place the method: if it is located in AbstractShow, we can call it on a variable of type AbstractShow or any of its subtypes, whereas if it is located in Movie, we can only call it on a variable of type Movie (or a subtype of Movie).
42
We wish to add a recommendation feature to our Show type hierarchy, where Show instances can hold a reference to a "recommended" show (if a person likes a given show). For this purpose, add a field Show aRecommended to class AbstractShow, together with public methods Show setRecommended(Show) and Show getRecommended(). For movies, we wish to constrain recommendations to movies only, so that movies can only hold recommendations to other movies. Override methods setRecommended and getRecommended in class Movie to only take Movie as parameter and return Movie, respectively. Does this design respect the Liskov Substitution Principle? Support your answer with a piece of demonstration client code.
In this case what happens is that the version setRecommended(Movie) in class Movie overloads setRecommended(Show) in class AbstractShow, so in a round-about way it does not violate the Liskov Substitution Principle (LSP). However, there is also no dynamic binding for this method, as the target overloaded version is selected at compile-time based on the type of the argument. In the following code, setRecommended(Movie) will be selected because the type of movie1 is Movie. If we change this type to Show, then setRecommended(Show) will be selected. ``` Movie movie1 = new Movie("Metropolis", 1927, 153); Movie movie2 = new Movie("The Good, the Bad, and the Ugly", 1966, 178); movie2.setRecommeded(movie1); The case of getRecommended() is different. It is possible to declare getRecommended(): Movie in Movie as a proper overriding version of AbstractShow.getRecommended(). More specific (or covariant) return types are allowed in Java (version 5 and later), and their use respects the LSP because it's always possible to assign a reference to a more specific type where a more general one is expected: ``` ``` // a Movie is returned, which can be assigned to a variable of type Show Show recommended = movie2.getRecommended(); ```
43
Make the Show class hierarchy Cloneable so that it is possible to clone instances of Concert, Movie, or SponsoredConcert.
We need to change Show to extend Cloneable and declare a clone() method: ``` public interface Show extends Cloneable { /* ... */ Show clone(); } We can then implement clone() in AbstractShow: ``` ``` public AbstractShow clone() { try { return (AbstractShow) super.clone(); } catch(CloneNotSupportedException e) { return null; } } Because Movie and Concert only require shallow copies (all their fields are primitive or String), they can simply inherit this version because the call to Object.clone() will copy all fields, no matter what the run-time type is. However, we can override clone() to declare a covariant return type and save the client code from having to downcast. For example, in class Movie: ``` ``` public Movie clone() { return (Movie) super.clone(); } For subtypes of Show that require a deeper copy (e.g., aggregates like CompositeShow from Exercise 6.1), it will be necessary to make a copy of the mutable structures in the clone() method. ```