Skip to content

Latest commit

 

History

History
205 lines (165 loc) · 9.84 KB

3D. Iterator.md

File metadata and controls

205 lines (165 loc) · 9.84 KB

Iterator

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.

image

// 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.

Iterator Design Pattern Summary

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 the VehicleList don't need to be aware of this complexity. Instead, they can simply use the Iterator provided by the VehicleList 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 the Iterator 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 as hasNext() and next(). 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, the getFilteredVehicles(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 the Iterator<Vehicle> interface, you could derive an Iterator Class.

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's Iterator<Vehicle> and the modified RestrictedVehicleIterator that overrides the default Iterator method next() and hasNext) . 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.