/*******************************************************************************
* 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;
/**
* A visitor that checks whether a card is contained in a card source
* structure.
*/
public class ChecksContainment
{
private final Card aCard;
private boolean aResult = false;
public ChecksContainmentVisitor(Card pCard)
{
aCard = pCard;
}
public void reset()
{
aResult = false;
}
@Override
public visitDeck(Deck pDeck)
{
for( Card card : pDeck)
{
if( card.equals(aCard))
{
aResult = true;
break;
}
}
}
@Override
public void visitCardSequence(CardSequence pCardSequence)
{
for( int i = 0; i < pCardSequence.size(); i++ )
{
if( pCardSequence.get(i).equals(aCard))
{
aResult = true;
break;
}
}
}
@Override
public void visitCompositeCardSource(CompositeCardSource pCompositeCardSource)
{
if( !aResult)
{
(pCompositeCardSource);
}
}
public boolean
{
return aResult;
}
}
The set of visit
methods implements the actual operation
performed by the concrete visitor. There is one method per
object type.
The set of visit
methods implements the actual operation
performed by the concrete visitor. There is one method per
object type.
The result of the operation is stored in a field,
accessible by the clients through a getter (the contains()
method
at the end of this class). This is necessary because the visitor
interface declares the visit
methods with a `void return type.
But why not change the return type to boolean
in the interface,
and return the result directly? This would make the interface specific
to operations that only compute boolean outputs, which is unnecessarily restrictive.
The result of the operation is stored in a field,
accessible by the clients through a getter (the contains()
method
at the end of this class). This is necessary because the visitor
interface declares the visit
methods with a `void return type.
But why not change the return type to boolean
in the interface,
and return the result directly? This would make the interface specific
to operations that only compute boolean outputs, which is unnecessarily restrictive.
Visitors can (and often have to) be stateful.
Typically, the input and output of a method are passed through its arguments and return value. A simple workaround is to store the inputs and outputs in fields of the concrete visitor. Constructors or setter methods can be used to provide the input before the operation, and getter methods can be used to get the output after.
Here, ChecksContainmentVisitor
uses two fields:
aCard
is an input for the computation. It stores which card the visitor is looking for.aResult
is the output of the computation. It stores whether the card was found in the CardSource
.Visitors can (and often have to) be stateful.
Typically, the input and output of a method are passed through its arguments and return value. A simple workaround is to store the inputs and outputs in fields of the concrete visitor. Constructors or setter methods can be used to provide the input before the operation, and getter methods can be used to get the output after.
Here, ChecksContainmentVisitor
uses two fields:
aCard
is an input for the computation. It stores which card the visitor is looking for.aResult
is the output of the computation. It stores whether the card was found in the CardSource
.The Visitor
interface should support arbitrary operations. This means that
the accept
and visit
methods shouldn't declare parameter types and a return type
that are specific to only one operation. This is why typical data flow strategies (i.e.,
passing information through method arguments and return value) is more complex with
visitors.
An alternative to storing values in the concrete visitor fields is to use generic types. This solution is outside the scope of the book, but the class GenericVisitorSample shows a sample implementation.
The Visitor
interface should support arbitrary operations. This means that
the accept
and visit
methods shouldn't declare parameter types and a return type
that are specific to only one operation. This is why typical data flow strategies (i.e.,
passing information through method arguments and return value) is more complex with
visitors.
An alternative to storing values in the concrete visitor fields is to use generic types. This solution is outside the scope of the book, but the class GenericVisitorSample shows a sample implementation.
The Visitor design pattern typically requires:
visit
methods in the interface, one for each type of object to visit;accept
method in each class that can be visited (i.e., each class included in the object hierarchy).The Visitor design pattern typically requires:
visit
methods in the interface, one for each type of object to visit;accept
method in each class that can be visited (i.e., each class included in the object hierarchy).When using the Visitor design pattern, it is common to provide an
abstract class. The abstract class can implement empty visit
methods
so that concrete visitors only need to override the methods relevant to
a specific computation. The abstract class can also hold the default
traversal code (if it is not already in the visitable classes).
When using the Visitor design pattern, it is common to provide an
abstract class. The abstract class can implement empty visit
methods
so that concrete visitors only need to override the methods relevant to
a specific computation. The abstract class can also hold the default
traversal code (if it is not already in the visitable classes).
The equals
method implements an equivalence relation
on non-null object references:
x
, x.equals(x)
should return
true
.
x
and y
, x.equals(y)
should return true
if and only if
y.equals(x)
returns true
.
x
, y
, and z
, if
x.equals(y)
returns true
and
y.equals(z)
returns true
, then
x.equals(z)
should return true
.
x
and y
, multiple invocations of
x.equals(y)
consistently return true
or consistently return false
, provided no
information used in equals
comparisons on the
objects is modified.
x
,
x.equals(null)
should return false
.
An equivalence relation partitions the elements it operates on into equivalence classes; all the members of an equivalence class are equal to each other. Members of an equivalence class are substitutable for each other, at least for some purposes.
hashCode
method whenever this method is overridden, so as to maintain the
general contract for the hashCode
method, which states
that equal objects must have equal hash codes.equals
method for class Object
implements
the most discriminating possible equivalence relation on objects;
that is, for any non-null reference values x
and
y
, this method returns true
if and only
if x
and y
refer to the same object
(x == y
has the value true
).
In other words, under the reference equality equivalence
relation, each equivalence class only has a single element.obj
- the reference object with which to compare.true
if this object is the same as the obj
argument; false
otherwise.In this implementation of the Visitor design pattern, concrete visitors
are responsible for handling the traversal of the object hierarchy. Instead
of duplicating this traversal code in every concrete visitors, the parent
class AbstractCardSourceVisitor
implements a default traversal strategy.
Subclasses just need to make a super call to reuse this strategy.
In this implementation of the Visitor design pattern, concrete visitors
are responsible for handling the traversal of the object hierarchy. Instead
of duplicating this traversal code in every concrete visitors, the parent
class AbstractCardSourceVisitor
implements a default traversal strategy.
Subclasses just need to make a super call to reuse this strategy.
The contains()
method returns the result of the operation, i.e., whether the
CardSource
contains the target card. This method would be called after the
visitor traverses the object hierarchy. A typical usage scenario of this concrete
visitor would look like:
Card card = ...
CardSource root = ...
ChecksContainmentVisitor visitor = new ChecksContainmentVisitor(card);
root.accept(visitor);
boolean containsCard = visitor.contains();
The contains()
method returns the result of the operation, i.e., whether the
CardSource
contains the target card. This method would be called after the
visitor traverses the object hierarchy. A typical usage scenario of this concrete
visitor would look like:
Card card = ...
CardSource root = ...
ChecksContainmentVisitor visitor = new ChecksContainmentVisitor(card);
root.accept(visitor);
boolean containsCard = visitor.contains();
If aResult
is true
, this means that the target card was already found
by the visitor. In this case, there is no need to look further in other
CardSource
objects (the visitor doesn't count
how many times the card appears). This check is not strictly necessary.
If the condition wasn't there, the visitor would traverse more objects,
but the result wouldn't change.
If aResult
is true
, this means that the target card was already found
by the visitor. In this case, there is no need to look further in other
CardSource
objects (the visitor doesn't count
how many times the card appears). This check is not strictly necessary.
If the condition wasn't there, the visitor would traverse more objects,
but the result wouldn't change.
The is appropriate in a context where we want to support an open-ended set of arbitrary operations on an object hierarchy. It can help avoid anti-patterns such as speculative generality and bloated class interfaces.
If the set of operations is small and meaningful to the abstraction of the
object hierarchy (e.g., removing a card from a CardSource
), then it's reasonable
to simply add a method directly to the class interface.
The is appropriate in a context where we want to support an open-ended set of arbitrary operations on an object hierarchy. It can help avoid anti-patterns such as speculative generality and bloated class interfaces.
If the set of operations is small and meaningful to the abstraction of the
object hierarchy (e.g., removing a card from a CardSource
), then it's reasonable
to simply add a method directly to the class interface.