The Iterator pattern provides a way to access the elements of an aggregate object sequentially without exposing its underlying structure. It decouples the traversal logic from the aggregate, allowing the traversal to vary independently.
// Vehicle Interface
interface Vehicle {
String getBrand();
String getModel();
}
// Concrete Vehicle Class: Car
class Car implements Vehicle {
private String brand;
private String model;
public Car(String brand, String model) {
this.brand = brand;
this.model = model;
}
public String getBrand() {
return brand;
}
public String getModel() {
return model;
}
}
// Concrete Vehicle Class: Truck
class Truck implements Vehicle {
private String brand;
private String model;
public Truck(String brand, String model) {
this.brand = brand;
this.model = model;
}
public String getBrand() {
return brand;
}
public String getModel() {
return model;
}
}
// Aggregate Interface
interface VehicleCollection {
Iterator<Vehicle> createIterator();
}
// Concrete Aggregate
class VehicleList implements VehicleCollection {
private List<Vehicle> vehicles;
public VehicleList() {
vehicles = new ArrayList<>();
}
public void addVehicle(Vehicle vehicle) {
vehicles.add(vehicle);
}
public void removeVehicle(Vehicle vehicle) {
vehicles.remove(vehicle);
}
public List<String> getAllVehicles() {
return getFilteredVehicles(vehicle -> true);
}
public List<String> getFilteredVehicles(Predicate<Vehicle> condition) {
List<String> vehicleList = new ArrayList<>();
Iterator<Vehicle> iterator = createIterator();
while (iterator.hasNext()) {
Vehicle vehicle = iterator.next();
if (condition.test(vehicle)) {
vehicleList.add(vehicle.getBrand());
}
}
return vehicleList;
}
@Override
public Iterator<Vehicle> createIterator() {
return vehicles.iterator();
}
}
// class RestrictedVehicleIterator implements Iterator<Vehicle> ...
// See Summary below
public class Main {
public static void main(String[] args) {
VehicleList vehicleList = new VehicleList();
vehicleList.addVehicle(new Car("Toyota", "Corolla"));
vehicleList.addVehicle(new Truck("Ford", "F-150"));
vehicleList.addVehicle(new Car("Honda", "Civic"));
vehicleList.addVehicle(new Car("Toyota", "Camry"));
List<String> allVehicles = vehicleList.getAllVehicles();
System.out.println(allVehicles); // [Toyota, Ford, Honda, Toyota]
List<String> vehicles = vehicleList.getFilteredVehicles(vehicle -> vehicle.getBrand().equals("Toyota"));
System.out.println(vehicles); // [Toyota, Toyota]
// Iterate over the Car Vehicles and perform some operation
Iterator<Vehicle> iterator = vehicleList.createIterator();
System.out.println(iterator.getClass()); // class java.util.ArrayList$Itr
while (iterator.hasNext()) {
Vehicle vehicle = iterator.next();
System.out.println("Brand: " + vehicle.getBrand() + ", Model: " + vehicle.getModel());
}
// Brand: Toyota, Model: Corolla
// Brand: Ford, Model: F-150
// Brand: Honda, Model: Civic
// Brand: Toyota, Model: Camry
}
}
In this example, the Vehicle
interface defines the contract for a vehicle, specifying the getBrand()
and getModel()
methods. The Car
and Truck
classes implement the Vehicle
interface and provide their own implementations of these methods.
The VehicleList
class (An Aggregate Class) represents the collection of vehicles. It internally uses a List<Vehicle>
to store the vehicles. The VehicleList
class now implements the Iterable<Vehicle>
interface, which enables it to be used in enhanced for loops and with iterators. The iterator()
method returns an iterator over the vehicles in the list.
In the Main
class, we create a VehicleList
object and add some Car
and Truck
instances to it. We can then iterate over the vehicles using getAllVehicles
and getFilteredVehicles
methods or an Iterator<Vehicle>
class with a while loop, and for each vehicle, we can access its brand and model using the getBrand()
and getModel()
methods defined in the Vehicle
interface.
Using the interface-based approach allows us to define common behavior and properties for different types of vehicles while keeping the flexibility to add new vehicle types in the future.
Use the Iterator pattern when your collection has a complex data structure under the hood, but you want to hide its complexity from clients (either for convenience or security reasons).
- The
VehicleList
class represents a collection of vehicles, and its underlying data structure may be complex. However, clients interacting with theVehicleList
don't need to be aware of this complexity. Instead, they can simply use theIterator
provided by theVehicleList
to access and traverse the elements of the collection.- By using the Iterator pattern, the complexity of the underlying data structure is encapsulated within the
VehicleList
class. Clients don't need to understand or directly manipulate the internal representation of the collection. They rely on theIterator
interface, which provides a uniform and convenient way to iterate over the elements regardless of the underlying implementation. - This abstraction offers convenience to clients because they can easily iterate over the collection using a standard set of methods provided by the
Iterator
interface, such ashasNext()
andnext()
. They don't need to worry about managing indices or dealing with specific collection implementations. - Moreover, the Iterator pattern can also provide security benefits. It allows you to control the access and visibility of elements within the collection. The
VehicleList
can decide which elements to expose through its iterator, allowing clients to iterate over a filtered or restricted set of elements based on certain criteria. This ensures that clients only access the elements they are allowed to without exposing the entire internal structure of the collection.- In the
VehicleList
, thegetFilteredVehicles(Predicate<Vehicle> condition)
method is implemented to restrict the iteration based on a Lambda Expression, however this is an unorthordox approach to the traditional Iterator pattern implementation. If you really want a more fine-grained control approach over theIterator<Vehicle>
interface, you could derive an Iterator Class.
- In the
- By using the Iterator pattern, the complexity of the underlying data structure is encapsulated within the
Iterator Class:
// Concrete Iterator
class RestrictedVehicleIterator implements Iterator<Vehicle> {
private List<Vehicle> vehicles;
private int currentPosition;
public RestrictedVehicleIterator(List<Vehicle> vehicles) {
this.vehicles = vehicles;
this.currentPosition = 0;
}
@Override
public boolean hasNext() {
while (currentPosition < vehicles.size()) {
Vehicle vehicle = vehicles.get(currentPosition);
if (vehicle instanceof Truck) {
return true;
}
currentPosition++;
}
return false;
}
@Override
public Vehicle next() {
Vehicle vehicle = vehicles.get(currentPosition);
currentPosition++;
return vehicle;
}
}
To use the RestrictedVehicleIterator
, you can modify the createIterator
method in the VehicleList
class as follows:
@Override
public Iterator<Vehicle> createIterator() {
return new RestrictedVehicleIterator(vehicles);
}
With this modification, the createIterator
method returns an instance of the RestrictedVehicleIterator
, which iterates over Truck objects only.
// Main class
Iterator<Vehicle> iterator = vehicleList.createIterator();
while (iterator.hasNext()) {
Vehicle vehicle = iterator.next();
System.out.println("Brand: " + vehicle.getBrand() + ", Model: " + vehicle.getModel());
}
// Output: Brand: Ford, Model: F-150
If you simply want to iterate through vehicles through the VehicleCollection
interface, then implementing methods like getFilteredVehicles
and getAllVehicles
is totally manageable. However, when ConcreteAggregate Class (like VehicleList
) explodes in number, implementing ConcreateIterator
classes in order for multiple ConcreteAggregate Class to reuse the iteration logic is better.
Use the pattern to reduce duplication of the traversal code across your app.
Use the Iterator when you want your code to be able to traverse different data structures or when types of these structures are unknown beforehand.
- The pattern provides a couple of generic interfaces for both collections (
List<Vehicle>
) and iterators (default JAVA'sIterator<Vehicle>
and the modifiedRestrictedVehicleIterator
that overrides the default Iterator methodnext()
andhasNext
) . Given that your code now uses these interfaces, it’ll still work if you pass it various kinds of collections and iterators that implement these interfaces.
NOTE:
In Java, the Iterator
interface and the Iterator
implementation classes (ArrayListIterator
, LinkedListIterator
, etc.) are part of the Java Collections Framework and are provided by default. You don't need to re-implement them when using the Iterator design pattern.
The Iterator design pattern is more about the usage and structure of the iterator rather than the implementation of the iterator itself. It provides a standard way to access and traverse elements in a collection without exposing its underlying representation. The design pattern defines the roles and responsibilities of the Iterator, Aggregate, and ConcreteIterator classes.