Iterator mode and command mode

1, Definition of iterator pattern

Iterator Pattern, also known as cursor pattern, provides a way to access collections sequentially/
Container object elements without exposing the internal representation of the collection. The iterator pattern can provide consistent for different containers
Traversal behavior, regardless of the composition structure of container content elements, belongs to behavior mode.

Provide a way to access the elements of an aggregate object sequentially without exposing its under lying representation
Explanation: provides a way to access collection / container object elements sequentially without exposing the internal representation of the collection.

The essence of iterator pattern is to extract the iterative behavior of collection objects into iterators and provide consistent access interfaces.

2, Application scenario of iterator

Iterator mode is also widely used in our life, such as conveyor belt in logistics system, no matter what it is
All items are packed into boxes one by one and have a unified QR code. So we don't need to care about the inside of the box
What is it? We only need to check the sending destinations one by one during distribution. Another example is that we usually take transportation,
They all swipe their cards or face to enter the station, without caring about personalized information such as men or women, disabled or normal people.

We call the aggregate of multiple objects. A collection object is a container object that can contain a group of objects. The aggregation structure of the internal elements of different collections may be different, and the iterator pattern masks the internal elements
Take details, provide consistent element access behavior for the outside, decouple the coupling between element iteration and collection objects, and
Different iterators can provide different order element access behaviors for the same collection object, and expand the collection object element overlap
Generation function, in line with the opening and closing principle. Iterator mode applies to the following scenarios:

1. Accessing the contents of a collection object without exposing its internal representation;
2. Provide a unified access interface for traversing different collection structures.

From the UML class diagram, we can see that the iterator pattern mainly includes three roles:
Abstract iterator: the abstract iterator is responsible for defining the interface for accessing and traversing elements;
Concrete iterator: provides specific element traversal behavior;
Abstract container (Aggregate): it is responsible for defining the interface to provide specific iterators;
Concrete aggregate: creates a concrete iterator.

. hand written custom senders
Overall, the iterator pattern is very simple. Let's take the course as an example. Let's create a collection of courses. Each element in the collection is a course object, and then hand write an iterator to read the information of each course object.

First create the Course class of the collection element:

public class Course {
    private String name;

    public Course(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }
}

Then create a custom Iterator iterator interface:

public interface Iterator<E> {
    E next();
    boolean hasNext();
}

Then create a customized course collection ICourseAggregate interface:

public interface ICourseAggregate {
    void add(Course course);
    void remove(Course course);
    Iterator<Course> iterator();
}

Then, implement iterator interface and collection interface respectively, and create Iteratorlmpl implementation class:

public class IteratorImpl<E> implements Iterator<E> {
    private List<E> list;
    private int cursor;
    private E element;

    public IteratorImpl(List<E> list) {
        this.list = list;
    }

    public E next() {
        System.out.print("current location " + cursor + " : ");
        element = list.get(cursor);
        cursor ++;
        return element;
    }

    public boolean hasNext() {
        if(cursor > list.size() - 1){
            return false;
        }
        return true;
    }
}

Create the courseaggregatelmppi implementation class of the course collection:

public class CourseAggregateImpl implements ICourseAggregate {
    private List courseList;

    public CourseAggregateImpl() {
        this.courseList = new ArrayList();
    }

    public void add(Course course) {
        courseList.add(course);
    }

    public void remove(Course course) {
        courseList.remove(course);
    }

    public Iterator<Course> iterator() {
        return new IteratorImpl<Course>(courseList);
    }
}

Then, write the client code:

public class Test {
    public static void main(String[] args) {
        Course java = new Course("Java framework");
        Course javaBase = new Course("Java Basics");
        Course design = new Course("Design pattern");
        Course ai = new Course("artificial intelligence");

        ICourseAggregate aggregate = new CourseAggregateImpl();
        aggregate.add(java);
        aggregate.add(javaBase);
        aggregate.add(design);
        aggregate.add(ai);

        System.out.println("===========Course list==========");
        printCourse(aggregate);

        aggregate.remove(ai);

        System.out.println("===========Course list after deletion==========");
        printCourse(aggregate);
    }

    private static void printCourse(ICourseAggregate aggregate) {
        Iterator<Course> i = aggregate.iterator();
        while (i.hasNext()){
            Course course = i.next();
            System.out.println("<" + course.getName()  + ">");
        }
    }
}

The operation results are as follows:

Seeing here, my friends will certainly have a sense of deja vu, which reminds people of the JDK we use every day
Built in binding iterator. Let's take a look at how iterators are used in the source code.

3, The embodiment of iterator pattern in source code

Let's first look at the familiar Iterator source code in JDK:

public interface Iterator<E> {
    boolean hasNext();

    E next();

    default void remove() {
        throw new UnsupportedOperationException("remove");
    }

    default void forEachRemaining(Consumer<? super E> action) {
        Objects.requireNonNull(action);
        while (hasNext())
            action.accept(next());
    }
}

From the above code, we can see that the two main method definitions, hasNext() and next(), are exactly the same as those written by ourselves.

In addition, from the above code, we can see that the removeO method implementation is deja vu. In fact, we have seen it in the combination mode. There seems to be some similarity between iterator pattern and composite pattern. The combination pattern solves the problem of unified tree structure
The iterator pattern solves the problem of unifying the traversal interface of each set object element. Although their adaptation scenarios
Different, but the core ideas are interlinked.

Next, let's take a look at the implementation class of Iterator. In fact, there is an internal implementation class Itr in our commonly used ArrayList
The Iterator interface is implemented:

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
    private class Itr implements Iterator<E> {
        int cursor;       // index of next element to return
        int lastRet = -1; // index of last element returned; -1 if no such
        int expectedModCount = modCount;

        public boolean hasNext() {
            return cursor != size;
        }

        @SuppressWarnings("unchecked")
        public E next() {
            checkForComodification();
            int i = cursor;
            if (i >= size)
                throw new NoSuchElementException();
            Object[] elementData = ArrayList.this.elementData;
            if (i >= elementData.length)
                throw new ConcurrentModificationException();
            cursor = i + 1;
            return (E) elementData[lastRet = i];
        }

        ...
    }
}

The implementation of hasNextO method and next() method is also very simple. Let's continue to look down. There are several iterators in ArrayList to further expand Itr. First, look at Listltr:

private class ListItr extends Itr implements ListIterator<E> {
    ListItr(int index) {
        super();
        cursor = index;
    }

    public boolean hasPrevious() {
        return cursor != 0;
    }

    public int nextIndex() {
        return cursor;
    }

    public int previousIndex() {
        return cursor - 1;
    }

    ...
}

It increases hasPreviousQ Whether the method has such a judgment as the previous one. In addition SubList Iterative processing of subsets.

of course,Iterator mode in MyBatis Is also essential. Let's take a look at one Defaultcursor Class:
public class DefaultCursor<T> implements Cursor<T> {
	...
    private final CursorIterator cursorIterator = new CursorIterator();
}

First, it implements the Cursor interface and defines a member variable Cursor. I'll continue to look at it
The source code of cursor is found. It is an internal class of Defaultcursor and implements the
Iterater interface.

4, Advantages and disadvantages of iterator pattern

advantage:

1. Polymorphic iteration: provide a consistent traversal interface for different aggregation structures, that is, an iterative interface can access different set pairs
Elephant;

2. Simplify the set object interface: the iterator pattern extracts the element iteration interface that the set object itself should provide into the iterator, so that the set object does not need to care about the specific iteration behavior;

3. Diversification of element iteration functions: each collection object can provide one or more different iterators, so that the same element aggregation structure can have different iterative behavior;

4. Decoupling iteration and set: the iterator pattern encapsulates the specific iterative algorithm. The change of the iterative algorithm will not affect the architecture of the set object.

Disadvantages:

1. For relatively simple traversal (such as array or sequence table), it is cumbersome to use iterator.
In daily development, we hardly write iterators ourselves. Unless we need to customize a data structure implemented by ourselves
Otherwise, the API provided by the open source framework is completely sufficient.

5, Command mode definition

Command Pattern is the encapsulation of commands. Each command is an operation: the requesting party sends a request to execute an operation; The receiving party receives the request and performs the operation. The command mode decouples the requester from the receiver. The requester only needs to request the execution of the command, and does not care how the command is received, how it is operated, whether it is executed, etc.
Command mode is behavior mode.

Encapsulate a request as an object, there by writing you parameterize clients with different requests, queue or log requests, and support undoable operations
Explanation: encapsulating a request into an object allows you to parameterize the client with different requests, queue requests or record request logs, which can provide command revocation and recovery functions

In software system, behavior requester and behavior implementer are usually a tight coupling relationship, because such implementation is simple and clear.
However, the tight coupling relationship lacks expansibility. In some cases, when it is necessary to record, undo or redo the behavior, only
Can modify the source code. The command mode decouples the request and implementation by introducing an abstract command interface between the request and implementation
And middleware is abstract. It can be implemented in different subclasses, so it has scalability. So the essence of the command pattern is
Decouple command request and processing.

6, Application scenario of command mode

When an operation of the system has command semantics and the command implementation is unstable (changing), the request and implementation can be decoupled through the command mode, and the abstract command interface can be used to stabilize the code architecture of the requestor and encapsulate the implementation details of the specific command of the receiver. The receiver and the abstract command interface are weakly coupled (internal methods do not need to be consistent) and have good scalability.

Command mode applies to
The following application scenarios:

1. Operations with "command" in real semantics (such as command menu, shell command...);

2. The request caller and the request receiver need to be decoupled so that the caller and the receiver do not interact directly;

3. Behaviors waiting to be executed need to be abstracted, such as Undo and Redo;

4. Command macros (i.e. command combination operations) need to be supported.

From the UML class diagram, we can see that the command pattern mainly includes four roles:

Receiver role: this class is responsible for implementing or executing a request;

Command role: defines all command behaviors to be executed;

Concrete Command role: this class maintains a Receiver within its execute ()
The method of Receiver is called in the method.

Requester role (Invoker): receives commands from the client and executes commands.

From the UML class diagram of Command pattern, it can be clearly seen that the emergence of Command is the middleware of Receiver and Invoker, decoupling each other. I think there are two reasons for the introduction of Command middleware:

Decoupling request and Implementation: that is, the Invoker and Receiver are decoupled, because in the UML class diagram, the Invoker is a
Body, waiting to receive the incoming Command from the client (that is, the Invoker is coupled with the client). The Invoker is in the business logic area and should be a stable structure. The Receiver belongs to the business function module and changes frequently. If there is no Command z, the Invoker is tightly coupled with the Receiver. A stable structure depends on an unstable structure, which will lead to the instability of the whole structure. This is the reason for the introduction of Command: it is not only decoupling the request and implementation, but also stabilizing the Invoker
Depending on Command, the structure is still stable.

Scalability enhancement: scalability is reflected in two aspects:

1. The Receiver belongs to the bottom level of detail, and different details can be achieved by replacing different receivers;

2. The Command interface itself is abstract and extensible; Moreover, because the Command object itself is abstract, if combined with the decorator pattern, the function expansion is like a duck to water.

Note: in a system, different commands correspond to different requests, that is, requests cannot be abstracted, so the Receiver in the command mode is a concrete implementation; However, if the Receiver can be abstracted in a module, in fact, the bridging mode is used in disguise (the command class has two changing dimensions: Command and Receiver), which will have better scalability.

For example, I believe that all the young friends of the post-80s generation should have experienced the era of popularizing black-and-white TV sets. Black and white electricity
It's not easy to change the TV. People need to run forward and break the channel switching knob on the TV
Only by tossing and turning can we complete a channel change. Now the times are good. We just need to lie on the sofa and press the remote control
Change channels. This is the command mode used to separate the station change command from the station change processing.

In addition, it is the order menu of the restaurant. Generally, the back kitchen configures all the raw materials first. Customers only need to order before eating to understand the needs and processing.

19.3. Application of command mode in business scenarios
If we develop a player ourselves, the player has playback function, drag progress bar function, stop playback function, temporary
Stop function. When we operate the player ourselves, we do not directly call the player's methods, but convey instructions to the player kernel through a control bar. Then what specific instructions will be encapsulated into buttons one by one. So each button
Is equivalent to the encapsulation of a command. The control bar is used to decouple the instructions sent by the user from the instructions received by the player kernel.

Let's look at the code. First, create the player kernel GPlayer class:

public class GPlayer {
    public void play(){
        System.out.println("Normal play");
    }

    public void speed(){
        System.out.println("Drag progress bar");
    }

    public void stop(){
        System.out.println("stop playing");
    }

    public void pause(){
        System.out.println("Pause playback");
    }
}

Create command interface IAction class:

public interface IAction {
    void execute();
}

Then create instructions that can be accepted by the operation player, and play the instruction PlayAction class:

public class PlayAction implements IAction {
    private GPlayer gplayer;

    public PlayAction(GPlayer gplayer) {
        this.gplayer = gplayer;
    }

    public void execute() {
        gplayer.play();
    }
}

Pause instruction PauseAction class:

public class PauseAction implements IAction {
    private GPlayer gplayer;

    public PauseAction(GPlayer gplayer) {
        this.gplayer = gplayer;
    }

    public void execute() {
        gplayer.pause();
    }
}

Drag the progress bar instruction SpeedAction class:

public class SpeedAction implements IAction {
    private GPlayer gplayer;

    public SpeedAction(GPlayer gplayer) {
        this.gplayer = gplayer;
    }

    public void execute() {
        gplayer.speed();
    }
}

Stop playing instruction StopAction class:

public class StopAction implements IAction {
    private GPlayer gplayer;

    public StopAction(GPlayer gplayer) {
        this.gplayer = gplayer;
    }

    public void execute() {
        gplayer.stop();
    }
}

Finally, create the control bar Controller class:

public class Controller {
    private List<IAction> actions = new ArrayList<IAction>();

    public void addAction(IAction action){
        actions.add(action);
    }

    public void execute(IAction action){
        action.execute();
    }

    public void executes(){
        for (IAction action:actions) {
            action.execute();
        }
        actions.clear();
    }
}

From the above code, the control bar can execute a single command or multiple commands in batch. Let's look at the client test code:

public class Test {
    public static void main(String[] args) {
        GPlayer player = new GPlayer();
        Controller controller = new Controller();
        controller.execute(new PlayAction(player));

        controller.addAction(new PauseAction(player));
        controller.addAction(new PlayAction(player));
        controller.addAction(new StopAction(player));
        controller.addAction(new SpeedAction(player));
        controller.executes();
    }
}

The operation effect is as follows:

Normal play
 Pause playback
 Normal play
 stop playing
 Drag progress bar

Since the control bar has been decoupled from the player kernel, if you want to expand new commands in the future, you only need to add commands
The structure of the does not need to be changed.

7, The embodiment of command mode in source code

Since the control bar has been decoupled from the player kernel, if you want to expand new commands in the future, you only need to add commands
The structure of the does not need to be changed.

public interface Runnable { 
    public abstract void run();
}

In fact, after calling the thread's start() method, we are qualified to grab CPU resources without writing our own logic to obtain CPU resources. After the thread grabs the CPU resources, it will execute the contents of the run() method, and use the Runnable interface to understand the coupling between the user request and the CPU execution.

Then, let's take a look at the junit.framework.Test interface that everyone knows very well:

package junit.framework; 
public interface Test { 
    public abstract int countTestCases(); 
    public abstract void run(TestResult result);
}

There are two methods in the Test interface. The first is the countTestCases() method, which is used to count the total number of Test cases to be executed. The second is the run() method, which is used to execute specific Test logic, and its parameter TestResult is used to return Test results. In fact, when we usually write Test cases, we only need to implement the Test interface. Even if we think it is a Test case, it will be automatically identified during execution. In fact, we usually inherit the TestCase class. Let's take a look
- the following is the source code of TestCase:

public abstract class TestCase extends Assert implements Test { 
    ...
    public void run(TestResult result) { 
        result.run(this);
	}
    ...
}

In fact, the TestCase class also implements the Test interface. We inherit the TestCase class, which is equivalent to implementing the Test interface. Naturally, it will be scanned into a Test case.

8, Advantages and disadvantages of command mode

advantage:

1. By introducing middleware (abstract interface), the command request and implementation are decoupled;

2. It has good expansibility and can easily add new commands;

3. Support combined commands and command queue;

4. You can add additional functions (such as logging... Combined with decorator mode) on the basis of existing commands.

Disadvantages:

1. There may be too many specific command classes;

2. The result of the command mode is actually the execution result of the receiver. However, in order to implement the architecture in the form of commands, the request and
Implementation, the introduction of additional type structure (the introduction of the requestor and abstract command interface), which increases the difficulty of understanding (but this
It is also a common problem brought about by design patterns. Abstraction will inevitably introduce additional types; abstraction must be more difficult to understand than compactness).

11, Iterator mode and command mode

Tags: Design Pattern Algorithm

Posted on Mon, 20 Sep 2021 18:37:30 -0400 by sobbayi