"Refactoring – Improving the design of existing code" reading notes---Duplicate observed Data

Source: Internet
Author: User

When MVC comes into being, it pushes the model and view separation trend. However, for some existing old systems or systems that are not well maintained, you will see that there are a lot of huge classes----Model,view,controller are written in a widget. a well-layered system should separate the code that handles the user interface and the business logic . The reasons are as follows

    1. If you need a different user interface to present the data, such as a pie chart and a line chart in Microsoft Excel, he actually shows the same data internally, but if you put the two layers of user interface logic in one widget, you'll make the Wiget complex, Because he also assumed two responsibilities, one is "pie chart" one is "line chart".
    2. Once you've separated the model from the GUI, you can make it easier to maintain and evolve between the two, and you can even have different developers develop separately.

The most difficult part of the separation is the separation of data, because you can easily divide the behavior into different parts, but the data is not so easy. Because you need to consider the synchronization problem , for example, if you need to display the name of your model in the GUI space at this time to put in a separate label, then you may need to embed in the GUI, also need to save a copy in the model. Since the advent of MVC, the user interface framework has used a multilayer system to provide a mechanism that allows you not only to provide such data, but also to keep them in sync .

If the code you encounter is not a single-tier approach, but a two-tier approach to development-the business is embedded in the user interface, you need to separate the behavior. The main work of behavior separation is the decomposition and removal of functions, but the data is different, you can't just move the data, you have to copy him into the new object, and provide the corresponding synchronization mechanism.

Practice:

    • Modify the view class so that it becomes the observer of the Model Class (Observer), if no model class is created, and if there is no association from view to model, the model is stored as a field in the view.
    • For the model data in the GUI, use the self encapsulate Field.
    • Compile, test.
    • Call the Set function in the event handler to update the GUI directly. Put a value function in the event handler and use it to update the GUI component to the current value of the model, which is of course not necessary, because you just set it yourself with its value. But using the SetPoint function in this way allows any of these actions to be executed later, which is the point of this step. When making this change, do not use the accessor for component view, it should be taken directly because we will later modify the accessor so that it takes a value from the model object instead of the GUI, and the set value function will make similar modifications.
    • Compile, test.
    • Define the data and related access functions in the model class to ensure that the value function of the model class triggers the notification mechanism (update) of the Observer mode. For the observed data, the model uses the same data type as the view (usually a string), and subsequent refactoring you can freely change the type.
    • Modify the Access function in view so that its operand is changed to model (not GUI).
    • Modify the Observer update () so that it copies the required data from the appropriate model to the GUI. (There are "push" and "pull" methods for data updates in Ps:observer mode, the "pull" data is described here)
    • Compile, test.

Example:

We assume that there are three text boxes, one is start, one is end, the other is length, where length is the difference between start and end, and you immediately modify any value, and the other two will refresh. For example, if you modify the length, the corresponding end will be updated, you can change the start or end,length will be updated. The first thing we did was to put the business logic in view, known as the focus mechanism in QT.

void Qapplication::focuschanged (Qwidget * old, Qwidget * now) [signal]

He will be based on the loss of focus, Qapplication will send out the corresponding signal, here we need to focus on old, because this pointer represents the lost focus of the widget represented by the pointer, we can judge in the end is which widget lost focus. So we set up a signal slot with qapplication in our own intervalwindow.

This, SLOT (onfocuschanged (Qwidget *, Qwidget *)));

So we can focus on the above 3 Widget:m_startfield,m_endfield,m_lengthfield in our own slot function onfocuschanged.

void onfocuschanged (qwidget *old, Qwidget * now) {    *w = old ;     if (w = = M_startfield)    {        startfield_focuslost ();    }     Else if (w = = M_endfield)    {        endfield_focuslost ();    }     Else if (w = = M_lengthfield)    {        lengthfield_focuslost ();    }}

You can see that when any of the pointers lose focus will go into the corresponding function, the processing function is roughly the following

voidStartfield_focuslost () {BOOLOK; intnum = M_startfield->gettext (). ToInt (&OK); if(OK) {}Else{M_startfield->settext ("0"); } calculatelength ();}voidEndfield_focuslost () {BOOLOK; intnum = M_endfield->gettext (). ToInt (&OK); if(OK) {}Else{M_endfield->settext ("0"); } calculatelength ();}voidLengthfield_focuslost () {BOOLOK; intnum = M_lengthfield->gettext (). ToInt (&OK); if(OK) {}Else{M_lengthfield->settext ("0"); } calculateend ();}

One of the things to note is that when a user enters an illegal character that cannot be successfully converted to a number, it will automatically become 0. The following are two specific calculation functions

voidcalculatelength () {intStart = m_startfield->GetText (). ToInt (); intEnd = M_endfield->GetText (). ToInt (); intLength = end-start; M_lengthfield-SetText (qstring::number (length));}voidCalculateend () {intStart = m_startfield->GetText (). ToInt (); intLength = m_lengthfield->GetText (). ToInt (); intEnd = Start +length; M_endfield-SetText (Qstring::number (end));}

Our task is to pull out the relevant computations unrelated to the GUI, which basically means that we need to put calcuatelength () and Calcuateend () into the model, For this purpose we need to get the value of three text boxes without referencing the view class. The only way to do this is to copy this data into the model class and keep it synchronized with the GUI, which is the task of duplicate observed data.

So far we have not had an independent model class, we have built a

class  Public observable{};

Among them, observable is the simplest observer-mode interface, which is implemented in a similar notify to facilitate the subscription of their respective customers to update accordingly. We need to create a view-to-model association

Interval *m_subject;

Then we need a reasonable initialization of the m_subject, and the view as the observer of this model, it is very simple, just put the following code in the view constructor can be

New Interval (); M_subject->addobserver (this); Update (m_subject) ;

We are used to putting this code at the end of the constructor, where the additional call to update can be done when we put the data into the model class, and the GUI will initialize it according to the model class. Of course, our view class should inherit the Observer interface at this time.

class  Public observer{};

and overwrite the update function, at which point an empty implementation is written first

void Update (Observable *observed) {}

Now we are compiling the tests, although we have not made any substantial changes so far, but we still need to be careful.

Next we focus on the text box, we start with the end text box, the first thing is to use the self encapsulate Field, the text box update is through GetText () and SetText (), So the access function we created needs to call these two functions

QString getend () {    return m_endfield->getText ();} void setend (const QString &Arg) {    M_endfield-setText (ARG);}

Then we find all the reference points of the M_endfield and replace them with the corresponding access functions ( which are already doing the decoupling operation, leaving the computation gradually out of the dependency of the relevant GUI )

voidcalculatelength () {intStart = m_startfield->GetText (). ToInt (); intEnd =Getend (); intLength = end-start; M_lengthfield-SetText (qstring::number (length));}voidCalculateend () {intStart = m_startfield->GetText (). ToInt (); intLength = m_lengthfield->GetText (). ToInt (); intEnd = Start +length; SetEnd (Qstring::number (end));}voidEndfield_focuslost () {BOOLOK; intnum =Getend (); if(OK) {}Else{setend (Qstring::number (0)); } calculatelength ();}

It's a standard process for self-encapsulate field to start packing and then to change the reference point, but when we're dealing with the GUI, the situation is more complicated: the user can modify the text box content through the GUI without having to pass setend (), So we need to call SetEnd () in the GUI event handler and this action sets the end text box to the current value, which has no effect, but in this way, you can ensure that the user's input is really through the set value function, so you can prevent and control all possible situations.

void Endfield_focuslost () {    setend (M_endfield-getText ());     BOOL OK;      int num = getend ();         if (OK)    {    }    else    {        setend (qstring::number (0));    }    Calculatelength ();}

Attentive friends may see why this is not using getend () but directly to manipulate the text box to get, the reason is because our subsequent refactoring will make Getend () from the model object, then if the use of getend (), whenever the user modifies the text box content, This will change the text box to the original value , so here we need to pay special attention to the fact that we have to use the text box directly to get the latest value, now we can compile and test the encapsulated behavior. Now we can add the M_end field to the model.

    Private :        QString M_end;

Here we give him the initial value and the GUI to his initial value is the same, and then we add the value/value function results are as follows

 class  Interval: public   observable{ public  : Interval (): M_end (  0   " " {} QString Getend () { return   M_end;  void  setend (const  QString &        arg)            {m_end  = Arg;            Setchanged ();        Notifyobservsers ();  private  : QString m_end;};  

Because of the use of the Observer mode, we have to give notice in the Set value function, here we put the M_end type value as a string, in fact, as the model itself meaning to be, the use of int seems more reasonable, but at this time we should try to minimize the amount of modification, To refactor at a small pace , we can easily change the m_end type to int if the copied data is successfully completed later.

Now that we can compile and test once, we want to minimize the risk of this more difficult refactoring step through all of these preparations.

First we modify the access function of the view class so that they use the interval object instead

class  Public observer{    public:        QString getend ()        {            return m_subject-> getend ();        }         void setend (const QString &Arg)        {            m_subject-setend (ARG);        }; 

At the same time, we modify the update () function to ensure that the GUI responds to the advertisement issued by the interval object

void Update (Observable *observed) {    q_unused (observed)    M_endfield->settext (m_ Subject->getend ());}

This is another place that requires direct access to the text box, and if we do not access it directly here, using SetEnd () itself, then our GUI controls will never be updated and the program itself will go into infinite recursion. In summary, there are two places in this refactoring step that really need to touch the GUI space itself:

    1. In the event handler function, in order to obtain the most recent value of the GUI control, it must be obtained through the control itself, otherwise if you get the model, the model is still the previous value.
    2. At the end of the update, to modify the value of the GUI control, you must call the control's set instead of your encapsulated set, or you will enter an infinite loop in addition to the control's not being updated.

In summary, one is the moment when the user touches the GUI, you need to get the latest data, and when you use the final set, you need to really set it to the GUI control itself. These two places require special attention and must be manipulated directly, rather than calling indirect delegate functions.

Now we can compile and test, and the data is copied appropriately. Two other text boxes we do the same thing, we can use move METHD to move the Calculateend () and Calculatelength () to the interval model, In this way we have an exclusive model that embraces the model data and behavior and is separate from the GUI. If we do this refactoring, we can also do more exaggerated things is that we can completely get rid of the GUI, to invoke the updated GUI control, so that the display can be better, this is definitely not done before this refactoring difficult to do.

Of course, there may be times when you don't want to use observer mode, you can use event listeners to do the same with duplicate observed Data. In this case you need to create a listener class and an event class in the model class, you need to register the listener with the model, just like the previous observable object registration observer, whenever the model changes (similar to the above update () is called), Sends an event to the listener, Intervalwindow can use an inline class to implement the listener interface and invoke the appropriate update () at the appropriate time.

"Refactoring – Improving the design of existing code" reading notes---Duplicate observed Data

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.