2020-Object-Oriented Design and Construction-Summary of Unit 2

Part 1 Multi-thread coordination and control

Limited to OO blog format, you can take a look at UML and Sequence Diagram in Part 3 and then look at this section

Overall signal communication

In this unit of work, I uniformly adopted the observer pattern (in fact, it's a different look of the observer pattern, which I will talk about later), borrowing the Observable and Observer mechanisms provided by Java to achieve.

The mechanism of Java ensures that for an Observable object, after I register some Observer objects, after each call setChanged()and then call notifyObservers(Object o), all Observer (actually only a total Scheduler) update(Object o)method will be called.

In my architecture, there is only one Observer, which is the central master scheduler, and will accept:

  • New request for input thread
  • Input thread termination signal
  • Elevator idle status signal of elevator thread
  • Elevator thread is replaced with signal to passengers to transfer station

In order to distinguish the different information from different Observable objects, I added a Notification class object to indicate the message content during the notification. The content of this class is roughly as follows:

public class Notification {
    public enum NoteType {
        INPUT, ELEV, TERMINATE, EXARRIVE, NEWELEV
    }

    private NoteType type;
    private int id;
    private Request req;
}

With such a structure, all the information can be loaded. Especially the type and use of enum can improve code readability.

All information must be distributed and processed by the update method, which makes the update method particularly complicated, and data analysis proves this.

Specifically analyze each thread,

  • The input thread is very simple. It accepts the request. It sends INPUT, TERMINATE, NEWELEVthree messages at the end of the personnel request, elevator request, and input . Each time a new personnel request is received, it will be placed in a thread-safe container. Wrong [cover face], I used a queue as an array), and there is nothing in the Notification, and the Request reference in the Notification will only be set when a new elevator notification is received.
  • Elevator thread, when the elevator is empty, send ELEVclass information to the Scheduler to request more passengers; the elevator sends a transfer to the station and sends the EXARRIVEclass information to the general dispatcher to let the general dispatcher arrange the next work of the passenger.

The dispatcher judges that the input is over and all elevator tasks are over, and then sends a termination request. This detection is performed every time the elevator sends a signal and when the input is terminated. As long as the elevator finishes running, it must send the IDLE signal, so in theory there is no problem that it cannot be ended.


Data communication between input thread and scheduler

In most cases, the data communication between the input thread and the scheduler is done through ConcurrentLinkedDeque. In my code, every time I convert it to a List and then operate it, so it is actually a thread-safe container (maybe use Vector is better?).


Data communication between elevator thread and dispatcher

All the state of the elevator is stored in an ElevInfo object. The reference of this object is held by the elevator process and the scheduler at the same time. Therefore, any operation on him (including the operation of the elevator and the scheduling of the scheduler) needs to be locked. Now it seems that I have stored too much data in this structure, so that locking him will almost lock all activities. Therefore, this may be a divisible point. (Supplement: I have thought about it again, no matter what state of an elevator is changing, for example, going upstairs and the main dispatcher plugging in a new message comes in at the same time, it may be caused by the sequential execution of the new information The gain is greater than the little time saved by quickly going upstairs, how to weigh this) (If the PPS is put together, the gain is uncertain, but the correlation between the data is not large, and putting it together does not meet the design principles)

The reason for setting the ELEV signal is for load balancing. Doing nothing (a large shared queue) is a good way to improve performance, because the most suitable passengers can be selected between elevators. And my structure (centralized scheduling) is not only difficult to achieve, but also severely increases coupling and reduces maintainability. The compromise I chose was to let the elevator load balance when it was empty. Even so, the maintainability of the code is reduced a lot (I am afraid that it is not "backward architecturally").


There are only two types of real threads in my work : input threads and elevator threads. Where is the scheduler? The global scheduler (responsible for distributing tasks in the elevator) is just an object, as the scheduler for Observer to listen to all incoming calls (slip, while the elevator internally controls elevator operation & shuttle tasks, etc ... due to my wrong design (and wrong)) and The elevators were mixed together.

Now I want to think about it. A suitable architecture for elevators, elevator dispatchers and elevator information that I can think of now should be this: the elevator is responsible for the movement between floors and is implemented by a finite state machine; the elevator dispatcher is an object that is responsible for Door opening / closing judgment, decision to get on and off, direction, and queue management are called by the elevator; the elevator information still stores almost all the information such as queues, floors, etc., but the lock of these information should be fine-grained , and the scheduler read has arrived When transferring passengers at the station, the elevator does not need to block the reading of its own running time, floor and other data.


Part 2 The scalability of the third job architecture design

Scalability

The scalability of this assignment is average.

Various parameters of the elevator can be set at the time of creation. I use an enum type with attributes to indicate the type of elevator. A new elevator can be quickly added (within the current frame). However, the shortcomings are also obvious. First, I did not apply the factory pattern, but instead passed in a parameter during construction to determine the type of elevator. In addition, I did not define an interface for the elevator, so I would encounter trouble when expanding.

Separation of scheduler and algorithm: No, I started to confuse the elevator of the scheduler algorithm, and finally repented (CheckStyle makes people sober.jpg, I actually ran on the 500th line) to take out the direction of the elevator, but The coupling is still high. Door opening and closing judgments, up and down people, and direction decisions should be placed in the same part and separated from the elevator as much as possible.


SRP-Single Responsibility Principle

Obviously, I didn't do this well. The problem focused on the elevator category. The elevator category has too many responsibilities, which makes his responsibilities unclear. That is to say, the modularity is not enough, and the objects should be further split. However, the responsibilities of the method still meet the SRP.

In addition, I am too pursuing: a single elevator can operate efficiently, so that the division of responsibilities between the general dispatcher and the dispatcher in the elevator is no longer clear.


OCP-Open Close Principle

I ’m doing just fine at this point, and modifications within the scope of the course do not require modification of existing implementations. However, my kind of no modification is more "over-designed". In other words, I not only set aside the "mouth" for extension, but also implemented a certain realization of the extension I guessed. This is actually bad.

Combining this assignment and the first assignment, I have a new understanding of scalability. Extensibility is not to say that your elevator can be used directly under different needs when it is taken out, not to mention that if I need a certain function, I only need to pass in one parameter; my understanding is that no matter what the complexity is, as long as The difference is within a certain range (this range should be very large), the overall architecture of the elevator system does not need to be changed, only new elevators / dispatch algorithms are added / changed.


LSP- Liskov Substitution Principle

How to say it, I have no subclasses, LSP must be satisfied (slip.

Seriously, my code should be able to achieve this, the elevator is actually exposed to his interface, so there is no problem with subclass replacement.


ISP-Interface Segregation Principle

There is no interface (paralysis, but if there is, I think it should be possible (of course it is not necessarily the first time I wrote it).

The scheduler can be divided into two interfaces: 1. Select the direction operation and 2. Open and close the door, and the related operations of the upper and lower people

The elevator provides an interface: adding requests and ending operation


DIP-Dependency Inversion Principle

No, I did not set up an interface for the elevator, nor did I set up an interface for the dispatcher. However, the reading of the container is through the List interface, tentatively to meet the DIP. Observer / Observable is used to transfer information between the scheduler and other components, so that some parts need not be considered when changing some parts.

Speaking of which, in fact my architecture is very loosely coupled, but it really makes me highly coupled because the main dispatcher can directly read all data in the sub-scheduler / elevator. Because I think that the general dispatcher can only dispatch better if it knows the information of all elevators, which leads to the fact that the ElevInfo of my elevator is shared with all elements, and this structure cannot be moved at all . I know this is a very bad performance, but I don't have a good way at present. If any students have relevant opinions, I hope they can be in the comment area, thank you! ( Going off topic, should this be part of OCP and scalability? )

PS I think the only way to get out of this dilemma is to do nothing, and the elevator itself grabs people from the same queue. But this efficiency is not necessarily guaranteed, it can only be regarded as a way to circumvent the problem, and it will also introduce a large number of synchronization problems.


Part 3 Metric-based program structure analysis

First homework


The basic structure is shown in the figure. MainClass starts the scheduler, elevator thread and input thread. When the input and elevator thread change, it will send a message to the scheduler, and the scheduler will respond accordingly.

The timing diagram (I don't know if the drawing is correct or not) is as shown in the figure. Almost all the operations of the controller on the elevator are directly controlled by ElevInfo.

Dependency: overall it is better, there is no circular dependency




Complexity: This time the complexity is concentrated in three places: the elevator arriveStat()method, the elevator run()method and the dispatcher update()method. The latter two have similar reasons: the run / update method is to assign a task. Because my elevator uses a finite state machine, the run method is to perform tasks according to the state; and the update method also has code for dispatching processing of different types of notifications, so the complexity of these two methods is also a certain reason.

But it is undeniable that I also had other problems in coding, which caused high complexity.

  • In the run method, I use the sleep time as the return value, and perform a real sleep operation in the run method. A separate function should be taken out of the sleep operation to change: sleep should be completed directly in the processing method of each state.
  • In the run method, the functions called in each state are hard-coded, which adversely affects scalability. Jingshen's post in the OO course discussion area gave me a good idea. Using the interface method, each state has a handle function called uniformly, which will be more elegant, readable and extensible. .
  • In the update method, the assignment and some specific operations are confused, and the modularity is insufficient.

The problem of arriveStat is a little more complicated, because he is in a very complicated state. In the state machine, arriveStat is a meeting point of state transition.

  • The direction of judgment should be separated from arriveStat
  • Should be determined to separate people from arriveStat

In this way, arriveStat only has the responsibility of determining the next state, which complies with the SRP principle.

Method Name Code Smell
update Complex Method
run Complex Method
run Missing default
arrivedStat Complex Method

Implementation smell also pointed out these problems

The problem in DesignCodeSmells is Unutilized Abstraction, probably because I did leave a lot of room for optimization when designing (that is, there is no real optimization [sad]). The total scheduler should originally assign an elevator to only the tasks that are most suitable for him. The scheduler in the elevator then completes the tasks according to this criterion. It's a pity that when it is written, it becomes: first ensure correctness, and then optimize ---> Long live effective work! Long live the test! (Slip

Second job


The basic structure is shown in the figure. The Scheduler maintains an elevator list. After determining which elevator to add a new request to, the operation is consistent with HW5.

The newly added Person class is a wrapper for PersonRequest. The PersonRequest class used directly in HW5 may not be very useful when dealing with elevator problems, so I encapsulated it in HW6 to add new attribute fields.

The timing diagram (I don't know if the drawing is correct or not) is as shown in the figure. The controller has added a new function: synthesize the information of each elevator and select the most suitable elevator. At the same time, when an elevator is empty, the controller will take the request from the control queue and other elevators and redistribute it to this elevator.

Note that the elevator is created in the Main at this time, which is not good, and should be submitted to the Scheduler for planning.

Dependency: still good, no red





Terms of complexity, problems still exist in HW5, similar to the previous reasons not discussed here, it equals method can only be so, it will not go, mainly to see inoutStat()and tryMove()method.

  • inoutStat: manages access methods. My design is: open the door-sleep 400ms-enter and exit people-close the door. Incoming and outgoing people must be related to the scheduling algorithm, so as to obtain better performance (sstf is connected to anyone, look is best to only take the same direction), which leads to more complicated. In fact, entering and leaving people can be done separately, and this method is disassembled, which is more in line with SRP.
  • tryMove: need to know the information of all elevators, and then take a request from the waiting queue of one elevator and put it into another. I haven't thought of a good solution for this modification. If you have any ideas, you can point it out in the comment area. Thank you!

This time ElevatorInfo reported Insufficient Modularization to me. Indeed, the content inside can be separated, and the status information of the elevator should be separated from the status of the dispatcher.


Third homework


The basic structure is shown in the figure.

This time, the enum ElevType is added as a parameter for creating elevators, and some basic information of elevators is specified. PossibleFloor is an internal class that is used to initialize the reachable floors. If you know a better way, you can say it in the comment area. Part of my code is as follows:

public enum ElevType {

    ATYPE("A", 400, 6), BTYPE("B", 500, 8),
    CTYPE("C", 600, 7);

    private String name;
    private int [] availFloor;
    private long moveSpeed;
    private int maxPpl;

    ElevType(String name, int moveSpeed, int maxPpl) {
        this.name = name;
        this.availFloor = PossibleFloor.getFloorList(name);
        this.moveSpeed = moveSpeed;
        this.maxPpl = maxPpl;
    }

    // Method: Getters

    static class PossibleFloor {
        private static final int [] AFloor = {-3, -2, -1, 1, 15, 16, 17, 18, 19, 20};
        // B/C

        public static int[] getFloorList(String s) {
            if (s.equals("A")) {
                return AFloor;
            } /* B / C...*/
        }
    }
}

CcOutput is a Wrapper class that locks TimableOutput to reduce the occurrence of output timing problems.

The timing diagram (I don't know if it was drawn correctly) is shown in the figure. After the passenger's request enters the controller, it will detect whether it can be reached directly. It is best to put it in a direct elevator if it can be reached directly, otherwise it will divide the task and transfer (lazy, static transfer, only 1, 5, 15 three floors). When a transfer task arrives at the transfer station, the elevator moves it into the transfer station queue and signals the controller. The controller takes the request and distributes it further.

Dependency: still better, no circular dependency


Some problematic complexity data:


moveFromOtherElev is TryMove in HW6. This time the In and Out are taken apart and the complexity is better.


Part 4 bug analysis --- own program

No bugs were found in the three tests, strong test and mutual test

Before the final submission, I found a very hidden bug in the program, and I am not even sure whether this problem really exists. Synchronized keywords and Lock are added to various methods of my scheduler. However, there is a problem caused by thread insecurity when accessing an internal container. After a lot of investigation, I guess it may be that my Constructor started the elevator thread before it was completed, and after the elevator thread started, it found that it was in the idle state and sent a signal to the dispatcher to request the passenger. At this time, the Constructor is modifying the container, and the update method is also called when a signal is received, triggering a bug. (Note: This is just my guess. Practice shows that when I finish processing the internal container first and then start the thread, the bug no longer reappears. (The detection of the PPS bug should be accurate enough. I used 300 processes to run repeatedly 20 times without reappearing. , And there will be about 1-2 occurrences every three hundred times before modification))

Part 5 bug analysis-other people's program

In this unit test, I mostly use automatic evaluation technology, using python's subprocess module and multi-thread module to build a multi-thread evaluation machine to evaluate with a large amount of data and high load, so I can better detect problems.

When testing each other, I usually use 1000-3000 data for initial testing. At this time, some serious bugs will generally appear, and I can also get a rough performance score. These bugs are usually easy to find problems in the code.

Then, for the passed code, 300 threads and 50 tests with a certain intensity are used. When the system load is increased, some thread-unsafe bugs will appear (generally manifested as RTLE).

After getting the data, use this data to test the reproducibility and error output using file redirection (also multi-threaded). If it is a repeated jump, go to detect the elevator scheduling and direction selection; if it can not be ended, go to check the sleeping conditions; if it is other WA, corresponding to the passenger processing part.

After finding the bug, construct the data accordingly to improve the recurrence rate (card time, etc.). (In fact, the effect is not very good, the card should be stuck to three decimal places, but the evaluation machine should do some confidential reverse optimization to make thread security problems more likely, so there is no need for a card)

The fifth assignment:

  • Archer: There is a problem with the elevator state switching process, resulting in ARRIVE-0the output

Sixth homework:

  • [UNHACKED Hash: b66e5835] Lancer:

    Adding an input at the moment of closing the door will produce an error output similar to OPEN-CLOSE-IN, because the competition condition of "Check and action" appears, and the door variable is not locked.

    [0.0]1
    [1.2]1-FROM-1-TO-2
    [2.412]2-FROM-2-TO-3
    

    Time data needs to be adjusted according to CPU speed, etc.

    This bug was not successful in the mutual test.

  • Caster: Joining people after entering and exiting and closing the door will be treated as a piggyback request to join the queue. If the elevator is empty at this time, it will cause a bug.

  • Archer: thread safety issues, specific data is difficult to reproduce

  • [UNHACKED Hash: 9e815697] Berserker

    Berserker's elevator has a timeout mechanism, it will be forced to be allocated if it is not allocated for 5s. Normal allocation and forced allocation are in the same for loop, and will not jump out. According to the design data, when the request is timed out within 5s, the request is just allocated normally, and there will be two arrivals.

    [0.0]1
    [1.1]21-FROM-1-TO-13
    [1.7]14-FROM-1-TO--3
    

    Time data needs to be adjusted according to CPU speed, etc.

    This bug was not successful in the mutual test.

Seventh homework:

  • [UNHACKED Hash: afe48615] Caster:

    Synchronized is not added before isEmpty () method in Scheduler

  • Assassin:
    There is a problem with the RequestQueue findMinFrom algorithm, which may cause a jump up and down.

  • Berserker: The
    thread is not safe, so the main request may be lost. The request can be given by the sub-scheduler or by the elevator, presumably because of a problem.

In general, this mutual test is quite metaphysical, and some bug evaluation machines cannot be reproduced (this is normal, data accuracy problem), but some people who I think are ok have been tested by Hack for thousands of sides Reproduced bugs (even some of the data I handed in Hack missed the desired target but scratched two, absolutely). However, if the program really has bugs, the chance of winning is still very high.

Part 6 Experience

The code of the second unit of OO feels very different from the first unit. There are countless cauldrons waiting for us in concurrent programming. However, if the relationship between the processes is synchronously understood, then there should be no major problems. I am a genre of locking everywhere, but I think this is appropriate. The purpose of concurrency here is not to improve CPU utilization but to ensure that multiple programs run at the same time, and security is more important. At the same time, there is no large-scale operation within the lock range, and CPU time consumption is not very large; in addition, the method of reducing locks may need to sacrifice some performance.

My experience of using locks: using synchronized as a lock --- standard lock is a bit troublesome --- the result of using synchronized is still using lock in three stages. If you understand the principle, the synchronized keyword should also be used more. When appropriate, it can greatly reduce the amount of code.

Here I suggest again, let's talk about Lock first, and talk about the synchronized keyword, so as to better understand , the synchronized keyword can only be regarded as a syntactic sugar thing, to understand must understand Lock.

I did not design a thread-safe class, but logically manually locked the relevant code block. This is more flexible, but sacrifices the maintainability of the code, which is not a good practice.

Regarding performance points, the progress space of my three assignments is larger than the first time (yes, you are right, it is progress space instead of progress, sad), I have designed a large number of mechanisms in the architecture to allow requests in each elevator Redistribution, but I did not actually use this mechanism in the end. This may be typical of over-design. How to say, this problem is still a deviation in my understanding of scalability. The true scalability must not be over-designed, at least this is certain.

The OO blog ( whip corpse ) unit is really cleverly designed. Now it seems that my code really wants to beat myself (what is written. Jpb), but maybe these ideas are only discovered after writing. What? This may be the reason for waterfall development. Design cannot be perfect at one time. Refactoring in the process of writing code (small range, small range) is one of the lessons I have learned.

Code quality analysis should be carried out as soon as possible, and it can even be said to be carried out at every commit. why? If I see my metric data in my fifth assignment, I may rewrite it on the spot and fix some problems.

The characteristic of the OO course is that effective homework is not difficult. If you think about strong test and want to get better performance points, you have to work several times. Many students have problems in the optimization process. I still think that correctness is more important.

Regarding correctness, I have been thinking about a problem. Angshen once said that for a program, 10000 correctness does not necessarily mean that the program is correct, and one wrong data can prove that he is wrong. However, I have been thinking, what if the probability of this erroneous data is really 1/10000, or even 1 / 1e10, 1 / 1e20? If his appearance probability is so small that it is almost impossible to happen, far smaller than other failure factors, can we think he is not a problem? Is this an ostrich algorithm? (PS may actually be that an erroneous data represents a type of erroneous data, so the probability of this occurrence is not as small as we have seen. However, if the probability of this type of data is also small, it is impossible to find out What? Can you think he can be used normally?)

(Finally whispering, I feel like UML pictures are not displayed)

In short, OO is really time-consuming, but the harvest is really not small, I hope I can use the harvested things next time, and I will have less urge to hammer myself next time when I whip the corpse blog.

Limited to the level, mistakes and omissions are inevitable, please comment in the comments area.

(If you have any thoughts about the doubts mentioned in my article, if you have any opinions, please hope to mention it in the comment area.

Guess you like

Origin www.cnblogs.com/VOIDMalkuth/p/12715267.html