/*******************************************************************************
* Companion code for the book "Introduction to Software Design with Java",
* 2nd edition by Martin P. Robillard.
*
* Copyright (C) 2022 by Martin P. Robillard
*
* This code is licensed under a Creative Commons
* Attribution-NonCommercial-NoDerivatives 4.0 International License.
*
* See http://creativecommons.org/licenses/by-nc-nd/4.0/
*
*******************************************************************************/
package e2.chapter8;
import java.util.ArrayList;
import java.util.List;
import javafx.application.Application;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.Slider;
import javafx.scene.control.TextField;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
/**
* Class to demonstrate the Observer design pattern
* in Java. Note that there are many ways to apply the
* Observer design pattern, and that this is just one of them.
* This code requires JavaFX to run.
*/
public class LuckyNumber extends {
public static final int WIDTH = 200;
private static final int GAP = 10;
private static final int MARGIN = 20;
/**
* Launches the application.
* @param pArgs This program takes no argument.
*/
public static void main(String[] pArgs) {
(pArgs);
}
@Override
public void start(Stage pPrimaryStage) {
Model = new Model();
GridPane root = createPane(); // The root of the GUI component graph
root.add(new SliderPanel(model), 0, 0, 1, 1);
root.add(new IntegerPanel(model), 0, 1, 1, 1);
root.add(new TextPanel(model), 0, 2, 1, 1);
pPrimaryStage.setTitle("Lucky Number");
pPrimaryStage.setResizable(false);
pPrimaryStage.setScene(new Scene(root));
pPrimaryStage.show();
}
/*
* Helper method to hide the details of creating
* a nice looking grid.
*/
private static GridPane createPane() {
GridPane root = new GridPane();
root.setHgap(GAP);
root.setVgap(GAP);
root.setPadding(new Insets(MARGIN));
return root;
}
}
/**
* Concrete observer that displays the model
* data in a slider.
*/
class SliderPanel extends implements Observer {
private Slider aSlider = createSlider();
private Model aModel;
/**
* @param pModel The model observed by this panel.
*/
SliderPanel(Model pModel)
{
aModel = pModel;
aModel.addObserver(this);
aSlider.setValue(aModel.getNumber());
getChildren().add();
aSlider.valueProperty().addListener(new ChangeListener<Number>() {
public void changed(ObservableValue<? extends Number> pValue, Number pOld, Number pNew) {
aModel.setNumber(pNew.intValue());
}
});
}
@Override
public void (int pNumber) {
aSlider.setValue(pNumber);
}
private static Slider createSlider() {
// CSOFF:
Slider slider = new Slider(1, 10, 5);
slider.setShowTickMarks(true);
slider.setShowTickLabels(true);
slider.setMinWidth(LuckyNumber.WIDTH);
slider.setMajorTickUnit(1);
slider.setBlockIncrement(1);
slider.setMinorTickCount(0);
slider.setSnapToTicks(true);
// CSON:
return slider;
}
}
/**
* Concrete observer that displays the model data
* as a integer in a text field.
*/
class IntegerPanel extends Parent implements Observer, <ActionEvent> {
private TextField aText = new TextField();
private Model aModel;
/**
* @param pModel The model observed by this panel.
*/
IntegerPanel(Model pModel) {
aModel = pModel;
aModel.addObserver(this);
aText.setMinWidth(LuckyNumber.WIDTH);
aText.setText(Integer.valueOf(aModel.getNumber()).toString());
getChildren().add(aText);
aText.setOnAction(this);
}
@Override
public void (ActionEvent pEvent) {
int lInteger = 1;
try {
lInteger = Integer.parseInt(aText.getText());
}
catch(NumberFormatException pException ) {
// Just ignore. We'll use 1 instead.
}
aModel.setNumber(lInteger);
}
@Override
public void newNumber(int pNumber) {
aText.setText(Integer.valueOf(pNumber).toString());
}
}
/**
* Concrete observer that displays the model data
* as a written-out number in a text field.
*/
class TextPanel extends HBox implements Observer {
private static final String[] LABELS = {"Zero", "One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "Nine", "Ten"};
private TextField aText = new TextField();
private Model aModel;
/**
* @param pModel The model observed by this panel.
*/
TextPanel(Model pModel) {
aModel = pModel;
aModel.addObserver(this);
aText.setMinWidth(LuckyNumber.WIDTH);
aText.setText(LABELS[aModel.getNumber()]);
getChildren().add(aText);
aText.setOnAction(new EventHandler<ActionEvent>() {
@Override
public void handle(ActionEvent pEvent) {
int lIndex = 0;
for( int i = 0; i < LABELS.length; i++) {
if(aText.getText().equalsIgnoreCase(LABELS[i])) {
lIndex = i;
break;
}
}
aModel.setNumber(lIndex);
}
});
}
@Override
public void newNumber(int pNumber) {
aText.setText(LABELS[pNumber]);
}
}
/**
* The observable object.
*/
class Model {
private static final int DEFAULT = 5;
private static final int MAX = 10;
private List<Observer> aObservers = new ArrayList<>();
private int aNumber = DEFAULT;
public void addObserver(Observer pObserver) {
assert pObserver != null;
aObservers.add(pObserver);
}
void notifyObservers() {
for(Observer observer : aObservers) {
observer.newNumber(aNumber);
}
}
public void setNumber(int pNumber) {
if( pNumber < 0 ) {
aNumber = 0;
}
else if( pNumber > MAX ) {
aNumber = MAX;
}
else {
aNumber = pNumber;
}
notifyObservers();
}
public int getNumber() {
return aNumber;
}
}
/**
* Abstract observer role for the model.
*/
interface Observer {
void newNumber(int pNumber);
}
Lists that support this operation may place limitations on what elements may be added to this list. In particular, some lists will refuse to add null elements, and others will impose restrictions on the type of elements that may be added. List classes should clearly specify in their documentation any restrictions on what elements may be added.
add
in interface Collection<E>
e
- element to be appended to this listtrue
(as specified by Collection.add(E)
)UnsupportedOperationException
- if the add
operation
is not supported by this listClassCastException
- if the class of the specified element
prevents it from being added to this listNullPointerException
- if the specified element is null and this
list does not permit null elementsIllegalArgumentException
- if some property of this element
prevents it from being added to this listOr no declared constructor, in which case Java automatically creates a public no-arg constructor for the class.
Or no declared constructor, in which case Java automatically creates a public no-arg constructor for the class.
Unlike sets, lists typically allow duplicate elements. More formally,
lists typically allow pairs of elements e1
and e2
such that e1.equals(e2)
, and they typically allow multiple
null elements if they allow null elements at all. It is not inconceivable
that someone might wish to implement a list that prohibits duplicates, by
throwing runtime exceptions when the user attempts to insert them, but we
expect this usage to be rare.
The List
interface places additional stipulations, beyond those
specified in the Collection
interface, on the contracts of the
iterator
, add
, remove
, equals
, and
hashCode
methods. Declarations for other inherited methods are
also included here for convenience.
The List
interface provides four methods for positional (indexed)
access to list elements. Lists (like Java arrays) are zero based. Note
that these operations may execute in time proportional to the index value
for some implementations (the LinkedList
class, for
example). Thus, iterating over the elements in a list is typically
preferable to indexing through it if the caller does not know the
implementation.
The List
interface provides a special iterator, called a
ListIterator
, that allows element insertion and replacement, and
bidirectional access in addition to the normal operations that the
Iterator
interface provides. A method is provided to obtain a
list iterator that starts at a specified position in the list.
The List
interface provides two methods to search for a specified
object. From a performance standpoint, these methods should be used with
caution. In many implementations they will perform costly linear
searches.
The List
interface provides two methods to efficiently insert and
remove multiple elements at an arbitrary point in the list.
Note: While it is permissible for lists to contain themselves as elements,
extreme caution is advised: the equals
and hashCode
methods are no longer well defined on such a list.
Some list implementations have restrictions on the elements that
they may contain. For example, some implementations prohibit null elements,
and some have restrictions on the types of their elements. Attempting to
add an ineligible element throws an unchecked exception, typically
NullPointerException
or ClassCastException
. Attempting
to query the presence of an ineligible element may throw an exception,
or it may simply return false; some implementations will exhibit the former
behavior and some will exhibit the latter. More generally, attempting an
operation on an ineligible element whose completion would not result in
the insertion of an ineligible element into the list may throw an
exception or it may succeed, at the option of the implementation.
Such exceptions are marked as "optional" in the specification for this
interface.
The List.of
and
List.copyOf
static factory methods
provide a convenient way to create unmodifiable lists. The List
instances created by these methods have the following characteristics:
UnsupportedOperationException
to be thrown.
However, if the contained elements are themselves mutable,
this may cause the List's contents to appear to change.
null
elements. Attempts to create them with
null
elements result in NullPointerException
.
subList
views implement the
RandomAccess
interface.
This interface is a member of the Java Collections Framework.
String
object representing this
Integer
's value. The value is converted to signed
decimal representation and returned as a string, exactly as if
the integer value were given as an argument to the toString(int)
method.String
object representing this
Integer
's value. The value is converted to signed
decimal representation and returned as a string, exactly as if
the integer value were given as an argument to the toString(int)
method.To launch the JavaFX GUI application framework, it is necessary to
public
class thatApplication
class andTo launch the JavaFX GUI application framework, it is necessary to
public
class thatApplication
class andIf the notification helper method is private, client cannot independently trigger observer notification: this must instead be programmed directly into the state-changing methods. By making the method public, we give more flexibility to the clients, at the cost of a more complex interface.
If the notification helper method is private, client cannot independently trigger observer notification: this must instead be programmed directly into the state-changing methods. By making the method public, we give more flexibility to the clients, at the cost of a more complex interface.
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.Integer
instance representing the specified
int
value. If a new Integer
instance is not
required, this method should generally be used in preference to
the constructor Integer(int)
, as this method is likely
to yield significantly better space and time performance by
caching frequently requested values.
This method will always cache values in the range -128 to 127,
inclusive, and may cache other values outside of this range.Integer
instance representing the specified
int
value. If a new Integer
instance is not
required, this method should generally be used in preference to
the constructor Integer(int)
, as this method is likely
to yield significantly better space and time performance by
caching frequently requested values.
This method will always cache values in the range -128 to 127,
inclusive, and may cache other values outside of this range.i
- an int
value.Integer
instance representing i
.This code is an example of self-registration as an observer.
When the object is constructed, it registers itself (via the
this
keyword), as an observer of the model passed as
argument to the constructor.
This code is an example of self-registration as an observer.
When the object is constructed, it registers itself (via the
this
keyword), as an observer of the model passed as
argument to the constructor.
Using advanced reflection! Note that launch
is static, and
called in the static main
method, so there isn't any implicit
argument. To know which class to instantiate, the framework
looks at the stack trace at the start of the launch
method,
and identifies the method that called launch
. It then constructs
an instance of the class where that method (usually main
) is
located.
Using advanced reflection! Note that launch
is static, and
called in the static main
method, so there isn't any implicit
argument. To know which class to instantiate, the framework
looks at the stack trace at the start of the launch
method,
and identifies the method that called launch
. It then constructs
an instance of the class where that method (usually main
) is
located.
The model does not need to be stored in a field of the application class because a reference to it is injected directly into the components of the JavaFX scene graph.
The model does not need to be stored in a field of the application class because a reference to it is injected directly into the components of the JavaFX scene graph.
The call to the launch
method (inherited from the
Application
class), and
then call the method start(Stage)
. Calling the start
method, and the Application
subclass before that, is all done by the framework.
The call to the launch
method (inherited from the
Application
class), and
then call the method start(Stage)
. Calling the start
method, and the Application
subclass before that, is all done by the framework.
Before the JavaFX framework is started (i.e., before calling
the launch method), it's not possible to construct many of the
widgets. For this reason, the GUI elements are constructed in the
start(Stage)
method, rather than, e.g., in static fields.
Before the JavaFX framework is started (i.e., before calling
the launch method), it's not possible to construct many of the
widgets. For this reason, the GUI elements are constructed in the
start(Stage)
method, rather than, e.g., in static fields.
Integer
class wraps a value of the primitive type
int
in an object. An object of type Integer
contains a single field whose type is int
.
Integer
class wraps a value of the primitive type
int
in an object. An object of type Integer
contains a single field whose type is int
.
In addition, this class provides several methods for converting
an int
to a String
and a String
to an
int
, as well as other constants and methods useful when
dealing with an int
.
This is a value-based class; programmers should treat instances that are equal as interchangeable and should not use instances for synchronization, or unpredictable behavior may occur. For example, in a future release, synchronization may fail.
Implementation note: The implementations of the "bit twiddling"
methods (such as highestOneBit
and
numberOfTrailingZeros
) are
based on material from Henry S. Warren, Jr.'s Hacker's
Delight, (Addison Wesley, 2002).
This means there are 2 observer-subject relations at play here, that shouldn't be confused:
SliderPanel
is an observer of Model
ChangeListener
is an observer of the value of aSlider
This means there are 2 observer-subject relations at play here, that shouldn't be confused:
SliderPanel
is an observer of Model
ChangeListener
is an observer of the value of aSlider
Adds the slider GUI component as a child node of this
node. This action is possible because getChildren()
is
inherited from the supertype (Parent
).
Adds the slider GUI component as a child node of this
node. This action is possible because getChildren()
is
inherited from the supertype (Parent
).
Adds a listener (meaning a concrete observer) to a different type of model, this time the value property of the slider. This property is observable so we can add observers to it. It is of the Observer pattern in this code. The callback takes three argument, the value reference, the old value, and the newly set value.
Adds a listener (meaning a concrete observer) to a different type of model, this time the value property of the slider. This property is observable so we can add observers to it. It is of the Observer pattern in this code. The callback takes three argument, the value reference, the old value, and the newly set value.
We can use anonymous classes to declare observers.
This anonymous class declaration provides the implementation
of the EventHandler
abstract observer. Contrast this
approach with the one used for IntegerPanel
.
We can use anonymous classes to declare observers.
This anonymous class declaration provides the implementation
of the EventHandler
abstract observer. Contrast this
approach with the one used for IntegerPanel
.
It's important not to have any null references
in the list of observers, otherwise any notification
will cause a NullPointerException
.
It's important not to have any null references
in the list of observers, otherwise any notification
will cause a NullPointerException
.
This statement illustrates how the object is both an observer and a controller. Here, in response to a GUI event, we control the model, which then sends further notifications to observers. It's important to be careful about call sequences in such situations to avoid .
This statement illustrates how the object is both an observer and a controller. Here, in response to a GUI event, we control the model, which then sends further notifications to observers. It's important to be careful about call sequences in such situations to avoid .
Chapter 8, insight #7
The model needs to notify observers when it changes its state, but when to issue that notification is a design decision
Chapter 8, insight #7
The model needs to notify observers when it changes its state, but when to issue that notification is a design decision
Here, the callback method handle
calls
setNumber
on aModel
, which then calls the other
callback method newNumber
, which changes the text of
aText
. Because that last operation (changing the value of
aText
) doesn't cause TextField
to notify the observer,
the chain of calls stops here. If this wasn't the case (e.g.,
TextField
notifies the observer after every change to the
value), then we would have an infinite recursion.
Here, the callback method handle
calls
setNumber
on aModel
, which then calls the other
callback method newNumber
, which changes the text of
aText
. Because that last operation (changing the value of
aText
) doesn't cause TextField
to notify the observer,
the chain of calls stops here. If this wasn't the case (e.g.,
TextField
notifies the observer after every change to the
value), then we would have an infinite recursion.
Chapter 8, insight #2
Consider separating the code responsible for storing data from the code responsible for viewing this data from the code responsible for changing the data (the Model--View--Controller decomposition)
Chapter 8, insight #2
Consider separating the code responsible for storing data from the code responsible for viewing this data from the code responsible for changing the data (the Model--View--Controller decomposition)
This statement illustrates how containers can serve as event
handlers for their own children components. An alternative
design is illustrated in class TextPanel
.
This statement illustrates how containers can serve as event
handlers for their own children components. An alternative
design is illustrated in class TextPanel
.
This method is the callback from EventHandler
, called
by TextField aText
.
Chapter 8, insight #9
Callback methods can be thought of as events to support a type of event-based programming. In this case, models are the event source and observers are the event handlers
This method is the callback from EventHandler
, called
by TextField aText
.
Chapter 8, insight #9
Callback methods can be thought of as events to support a type of event-based programming. In this case, models are the event source and observers are the event handlers
This is the implementation of Observer's callback method.
Chapter 8, insight #9
Callback methods can be thought of as events to support a type of event-based programming. In this case, models are the event source and observers are the event handlers
This is the implementation of Observer's callback method.
Chapter 8, insight #9
Callback methods can be thought of as events to support a type of event-based programming. In this case, models are the event source and observers are the event handlers
Chapter 8, insight #14
The component graph must be instantiated before the user interface becomes visible. In JavaFX this instantiation is triggered in the start
method of the application class
Chapter 8, insight #14
The component graph must be instantiated before the user interface becomes visible. In JavaFX this instantiation is triggered in the start
method of the application class
Extending Parent
allows the SliderPanel
to become a Node
and
be inserted in the scene graph (subtyping) and well as
inherit the ability to contain children nodes (code reuse).
Chapter 8, insight #15
You can inherit from component classes of the GUI framework to create custom graphical components that can be added to an application's GUI component graph
Extending Parent
allows the SliderPanel
to become a Node
and
be inserted in the scene graph (subtyping) and well as
inherit the ability to contain children nodes (code reuse).
Chapter 8, insight #15
You can inherit from component classes of the GUI framework to create custom graphical components that can be added to an application's GUI component graph
This class is an observer for two models. By implementing interface
Observer
, it can observer an instance of Model
. By implementing
EventHandler
, it can observe any object that accepts this type of observer.
In our design, this is an instance of class TextField
used to enter
the lucky number in numeric form.
This class is an observer for two models. By implementing interface
Observer
, it can observer an instance of Model
. By implementing
EventHandler
, it can observe any object that accepts this type of observer.
In our design, this is an instance of class TextField
used to enter
the lucky number in numeric form.
List
interface. Implements
all optional list operations, and permits all elements, including
null
. In addition to implementing the List
interface,
this class provides methods to manipulate the size of the array that is
used internally to store the list. (This class is roughly equivalent to
Vector
, except that it is unsynchronized.)
List
interface. Implements
all optional list operations, and permits all elements, including
null
. In addition to implementing the List
interface,
this class provides methods to manipulate the size of the array that is
used internally to store the list. (This class is roughly equivalent to
Vector
, except that it is unsynchronized.)
The size
, isEmpty
, get
, set
,
iterator
, and listIterator
operations run in constant
time. The add
operation runs in amortized constant time,
that is, adding n elements requires O(n) time. All of the other operations
run in linear time (roughly speaking). The constant factor is low compared
to that for the LinkedList
implementation.
Each ArrayList
instance has a capacity. The capacity is
the size of the array used to store the elements in the list. It is always
at least as large as the list size. As elements are added to an ArrayList,
its capacity grows automatically. The details of the growth policy are not
specified beyond the fact that adding an element has constant amortized
time cost.
An application can increase the capacity of an ArrayList
instance
before adding a large number of elements using the ensureCapacity
operation. This may reduce the amount of incremental reallocation.
Note that this implementation is not synchronized.
If multiple threads access an ArrayList
instance concurrently,
and at least one of the threads modifies the list structurally, it
must be synchronized externally. (A structural modification is
any operation that adds or deletes one or more elements, or explicitly
resizes the backing array; merely setting the value of an element is not
a structural modification.) This is typically accomplished by
synchronizing on some object that naturally encapsulates the list.
If no such object exists, the list should be "wrapped" using the
Collections.synchronizedList
method. This is best done at creation time, to prevent accidental
unsynchronized access to the list:
List list = Collections.synchronizedList(new ArrayList(...));
The iterators returned by this class's iterator
and
listIterator
methods are fail-fast:
if the list is structurally modified at any time after the iterator is
created, in any way except through the iterator's own
remove
or
add
methods, the iterator will throw a
ConcurrentModificationException
. Thus, in the face of
concurrent modification, the iterator fails quickly and cleanly, rather
than risking arbitrary, non-deterministic behavior at an undetermined
time in the future.
Note that the fail-fast behavior of an iterator cannot be guaranteed
as it is, generally speaking, impossible to make any hard guarantees in the
presence of unsynchronized concurrent modification. Fail-fast iterators
throw ConcurrentModificationException
on a best-effort basis.
Therefore, it would be wrong to write a program that depended on this
exception for its correctness: the fail-fast behavior of iterators
should be used only to detect bugs.
This class is a member of the Java Collections Framework.