/**
* Copyright (C) 2022 by Martin Robillard. See https://codesample.info/about.html
*/
package e2.chapter4;
/**
* Implementation of a playing card. This class yields immutable objects.
* This version of the class shows an application of the Flyweight design
* pattern where the flyweight store is pre-initialized.
*/
public class Card {
/*
* Implements the flyweight store as a bidimensional array. The
* first dimension indexes the suits by the ordinal value of their enumerated type,
* and the second dimension, the ranks. For example, to retrieve the two of clubs,
* we access CARDS[Suit.CLUBS.ordinal()][Rank.TWO.ordinal()].
*/
private static final Card[][] = new Card[Suit.values().length][Rank.values().length];
private final Rank aRank;
private final Suit aSuit;
// Initialization of the flyweight store
{
for( Suit suit : Suit.values() ) {
for( Rank rank : Rank.values() ) {
CARDS[suit.ordinal()][rank.ordinal()] = new Card(rank, suit);
}
}
}
// Private constructor
Card( Rank pRank, Suit pSuit) {
aRank = pRank;
aSuit = pSuit;
}
/**
* @param pRank The rank of the requested card.
* @param pSuit The suit of the requested card.
* @return The unique Card instance with pRank and pSuit
* @pre pRank != null && pSuit != null
*/
public static Card (Rank pRank, Suit pSuit) {
assert pRank != null && pSuit != null;
return CARDS[pSuit.ordinal()][pRank.ordinal()];
}
/**
* @return The rank of the card.
*/
public Rank getRank() {
return aRank;
}
/**
* @return The suit of the card.
*/
public Suit getSuit() {
return aSuit;
}
@Override
public String toString() {
return String.format("%s of %s", aRank, aSuit);
}
}
This implementation of the flyweight store is just one alternative among many.
Dictionary-based approaches are also possible. In particular, I would consider
a two-level map using EnumMap
to be a good choice. A design based on EnumMap
is more robust than an array
because, as opposed to arrays, it is less easy to make an indexing error.
This implementation of the flyweight store is just one alternative among many.
Dictionary-based approaches are also possible. In particular, I would consider
a two-level map using EnumMap
to be a good choice. A design based on EnumMap
is more robust than an array
because, as opposed to arrays, it is less easy to make an indexing error.
In Java, a static initialization block executes once, when the class is first loaded into the running application. It is typically used to initialized static fields with code that does not need to be used anywhere else. If the code can be reused, it is better to put it in a static private method.
In Java, a static initialization block executes once, when the class is first loaded into the running application. It is typically used to initialized static fields with code that does not need to be used anywhere else. If the code can be reused, it is better to put it in a static private method.
In this application of the Flyweight pattern, I chose a greedy initialization of the flyweight objects. Once the class
is loaded into the JVM, all the Card
instances are created at once. An alternative to this scheme is lazy initialization,
where cards are initialized on-demand in the . With this alternative, the static initialization block would
be removed.
In this application of the Flyweight pattern, I chose a greedy initialization of the flyweight objects. Once the class
is loaded into the JVM, all the Card
instances are created at once. An alternative to this scheme is lazy initialization,
where cards are initialized on-demand in the . With this alternative, the static initialization block would
be removed.
In the Flyweight pattern, the method that returns the unique flyweight object requested. In this class it's the get
method.
In the Flyweight pattern, the method that returns the unique flyweight object requested. In this class it's the get
method.
Declaring the constructor to be private forces client code to obtain instances of the class using the access method.
Declaring the constructor to be private forces client code to obtain instances of the class using the access method.
This is the access method for this flyweight class. Because the application of the Flyweight pattern uses greedy initialization, the method just returns an instance which we know already exists in the store. When the lazy initialization variant is used, this method would need to as well. Note that the access method must be a static method.
This is the access method for this flyweight class. Because the application of the Flyweight pattern uses greedy initialization, the method just returns an instance which we know already exists in the store. When the lazy initialization variant is used, this method would need to as well. Note that the access method must be a static method.
The result might look something like this (remember that arrays are initialized
with null
values):
public static Card get(Rank pRank, Suit pSuit)
{
assert pRank != null && pSuit != null;
Card card = CARDS[pSuit.ordinal()][pRank.ordinal()];
if (card == null)
{
card = new Card(pRank, pSuit);
// don't forget to store the new object!
CARDS[pSuit.ordinal()][pRank.ordinal()] = card;
}
return card;
}
The result might look something like this (remember that arrays are initialized
with null
values):
public static Card get(Rank pRank, Suit pSuit)
{
assert pRank != null && pSuit != null;
Card card = CARDS[pSuit.ordinal()][pRank.ordinal()];
if (card == null)
{
card = new Card(pRank, pSuit);
// don't forget to store the new object!
CARDS[pSuit.ordinal()][pRank.ordinal()] = card;
}
return card;
}
Chapter 4, insight #4
Consider declaring instance variables final
whenever possible
Chapter 4, insight #4
Consider declaring instance variables final
whenever possible
EnumSet
and EnumMap
.
String
class represents character strings. All string literals in Java programs, such as "abc"
, are implemented as instances of this class.
String
class represents character strings. All string literals in Java programs, such as "abc"
, are implemented as instances of this class.
Strings are constant; their values cannot be changed after they are created. String buffers support mutable strings. Because String objects are immutable they can be shared. For example:
String str = "abc";
is equivalent to:
char data[] = {'a', 'b', 'c'}; String str = new String(data);
Here are some more examples of how strings can be used:
System.out.println("abc"); String cde = "cde"; System.out.println("abc" + cde); String c = "abc".substring(2, 3); String d = cde.substring(1, 2);
The class String
includes methods for examining individual characters of the sequence, for comparing strings, for searching strings, for extracting substrings, and for creating a copy of a string with all characters translated to uppercase or to lowercase. Case mapping is based on the Unicode Standard version specified by the Character
class.
The Java language provides special support for the string concatenation operator ( + ), and for conversion of other objects to strings. For additional information on string concatenation and conversion, see The Java Language Specification.
Unless otherwise noted, passing a null
argument to a constructor or method in this class will cause a NullPointerException
to be thrown.
A String
represents a string in the UTF-16 format in which supplementary characters are represented by surrogate pairs (see the section Unicode Character Representations in the Character
class for more information). Index values refer to char
code units, so a supplementary character uses two positions in a String
.
The String
class provides methods for dealing with Unicode code points (i.e., characters), in addition to those for dealing with Unicode code units (i.e., char
values).
Unless otherwise noted, methods for comparing Strings do not take locale into account. The Collator
class provides methods for finer-grain, locale-sensitive String comparison.
javac
compiler may implement the operator with StringBuffer
, StringBuilder
, or java.lang.invoke.StringConcatFactory
depending on the JDK version. The implementation of string conversion is typically through the method toString
, defined by Object
and inherited by all classes in Java.
The locale always used is the one returned by Locale.getDefault(Locale.Category)
with FORMAT
category specified.
format
- A format string
args
- Arguments referenced by the format specifiers in the format string. If there are more arguments than format specifiers, the extra arguments are ignored. The number of arguments is variable and may be zero. The maximum number of arguments is limited by the maximum dimension of a Java array as defined by The Java Virtual Machine Specification. The behaviour on a null
argument depends on the conversion.
IllegalFormatException
- If a format string contains an illegal syntax, a format specifier that is incompatible with the given arguments, insufficient arguments given the format string, or other illegal conditions. For specification of all possible formatting errors, see the Details section of the formatter class specification.