final practice Flashcards
(43 cards)
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?
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.
Rewrite class Card so that it is internally represented as six Boolean values.
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()); } }
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.
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)); } }
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 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.
Does it make sense to model the abstract state space of a Java interface? Why or why not?
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.
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.
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.
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.
Try modifying it to use a Map to store the flyweights.
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?
The adapted version that uses a bidimensional array for the flyweight store can be found here.
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
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.
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.
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; }
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.
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 ) { /* ... */ } }
Implement a variant of the feature described in Exercise 7, but this time by modifying the Show interface.
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(); }
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.
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(); } }
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?
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.
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.
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();
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).
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();
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.
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.
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.
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(); } }
Extends the CommandProcessor so that is supports redoing commands (that is, executing undone commands).
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); } }
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?
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.
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.
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).
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.
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.
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?
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.
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.
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());
}
}