Location>code7788 >text

Summary of architectural evolutionary thinking (2)

Popularity:401 ℃/2024-07-31 22:00:01

Summary of architectural evolutionary thinking (2)

--- Exploring the handling of dependencies from the command model

Before formally introducing the concept of command patterns, let's start with a simple case study to gradually evolve what you commonly see in writing.

public interface ICommand
{
    void Execute();
}

public class PlayMusicCommand : ICommand
{
     public void Execute()
     {
        ("You said home was the only castle.,With Inari running all the way~");
     }
}

var Start()
{
    var command = new PlayMusicCommand();
    ();
}

Here we define a command interface, an execution method that must be implemented if it is a command.

PlayMusicCommand implements the interface, the function of this command is to play Jay's "Rice Scent", if you want to realize the function of playing music, you can directly execute the method of the corresponding command!

Reflecting the nature of commands, we encapsulate what we want to do or control logic, and when we need to execute it, just give the method to execute the command!

Come see what AI has to say about command mode:

img

The small case above corresponds to the encapsulation of the "operation logic", refined into commands, so what are the benefits of such encapsulation of the operation logic?

One of the obvious benefits is ease of management and clarity of logic. Complex logic development, we formally encapsulate it as much as possible into a method, in order to facilitate management, while the command pattern is a higher level of encapsulation of the logic code, that is, from the method of abstraction into a class, it is obviously more convenient to manage and reuse.

Reduce the difficulty of developing and debugging complex logic using command patternsIf you are troubleshooting a bug in a large function of several hundred lines, it is definitely more troublesome than encapsulating the situation by splitting it into several functions or several corresponding commands. For example, we need to carry out a complex operation, but we split it into several commands, so that it can be distributed to several colleagues to collaborate on complex logic development.No excessive coupling to core logic control scripts

On the other hand, of course.Shares the control pressure of the control script Controller, making it less bloated.

public interface ICommand
{
    void Execute();
}

public class ACommand
{
    public void Execute()
    {
        ("Execute A Command");
    }
}

public class BCommand
{
    public void Execute()
    {
        ("Execute B Command");
    }
}

public class CCommand
{
    public void Execute()
    {
        ("Execute C Command");
    }
}

void Start()
{
    var commands = new List<ICommand>();
    (new ACommand());
    (new BCommand());
    (new CCommand());
  
    (c=>());
}

Command Mode - Carrying Parameters

What if we want to execute commands that require parameters to proceed?

Okay, next we implement commands that can take parameters, it's very simple, just declare the parameters for the command you are executing!

interface ICommand
{
    void Execute();
}
public class BuyGoodsCommand : ICommand
{
    private int goodsId;
    private int goodsCount;
    public BuyGoodsCommand(int id,int count)
    {
        goodsId = id;
        goodsCount = count;
    }
    public void Execute()
    {
        ($"purchasedidbecause of{goodsId}merchandise{goodsCount}classifier for individual things or people, general, catch-all classifier");
        //Execute the relevant purchase logic
        //......
    }
}

public class Test : MonoBehaviour
{
    private void Start()
    {
        var buyGoodsCommand = new BuyGoodsCommand(1, 15);
        ();
    }
}

Command Mode - Undo Function

The next step towards the implementation of the command mode function, in the first contact with the command mode, will be curious to think, since the command are encapsulated step by step execution, then can not undo the behavior has been executed? The author is also learning to command mode before associating the various editing tools Ctrl + Z effect of the realization of the idea. Then we add an undo function to the command mode.

Of course to execute the undo command, there should be a container to store the executed commands, here we use List, you can also use Stack

and Queue, and of course the progressive undo of Ctrl + Z is possible using the stack!

interface ICommand
{
    void Execute();
    void Undo();
}
public class BuyGoodsCommand : ICommand
{
    private int goodsId;
    private int goodsCount;
    public BuyGoodsCommand(int id,int count)
    {
        goodsId = id;
        goodsCount = count;
    }
    public void Execute()
    {
        ($"purchasedidbecause of{goodsId}merchandise{goodsCount}classifier for individual things or people, general, catch-all classifier");
        //Execute the relevant purchase logic
        //......
    }

    public void Undo()
    {
        ($"Just purchasedidbecause of{goodsId}merchandise{goodsCount}classifier for individual things or people, general, catch-all classifier,They've all been returned.!");
        //Perform relevant return operations
        //for example, inventory++
        //Player Gold++
    }
}

public class Test : MonoBehaviour
{
    private void Start()
    {
        var commands = new List<BuyGoodsCommand>();
        (new BuyGoodsCommand(1, 15));
        (new BuyGoodsCommand(5, 2));

        //Execution of purchases
        (command => ());

        //5I don't want it anymore. return merchandise
        commands[1].Undo();
    }
}

Command Mode - Separation of Command and Execution

The dependencies here are roughly the same as those stated in the previous article, where we look at commands downgraded from an object to a method.

This behavior of method calls, which we often make, is an example of the failure to separate command from execution. That is, method calls necessarily execute the logic in the method.

void DoSomethingCommand()
{
    ("The command executes the!");
}
void Start()
{
    DoSomethingCommand();
}

So what does it look like to separate commands from execution?

We can use delegates to achieve this, separation in time and space.

 public class A : MonoBehaviour
 B b; B b; MonoBehaviour; MonoBehaviour
     B b; void Start()
     B b; void Start()
     {
         b = ("Animation").GetComponent<B>();

         // Register the completion event
          += DoSomethingCommand;
     }
     void DoSomethingCommand()
     {
         ("Command executed!") ;
     }
 }

public class B : MonoBehaviour
{
    // Define the delegate
    public Action OnDoSomethingDone = ()=>{};;

    // Called when the animation has finished playing
    public void DoSomething()
    {
        //Trigger the execution of the function in the delegate
        OnDoSomethingDone();
    }
}

In this way, the command DoSomethingCommand that will be executed will be called and executed by another script (spatially separated) at a specific time (temporally separated), realizing spatial and temporal separation.

OK, we've expressed the separation of commands at the method level, now let's go back to the class level and separate the declaration and execution of Command.

This requires another layer of encapsulation of the use of a delegate, here it is with a delegate (which can be simply understood as a function container), the storage is a function (command is simplified to the method level), which can be used. Corresponding to upgrade the command upgrade into an object, for this also to the

The delegate is "upgraded", see QFramWork's custom event mechanism here.

Customizing the event mechanism

We want it to have functionality for the event mechanism: send event functionality and auto-logout functionality.

Sending events is mandatory, and the automatic logout function is to logout of the event listening function after the Destroy of the GameObject that is registered to listen to the event.

Now implement the interface as such:

 public interface ITypeEventSystem
 {
     /// <summary>
     /// Send Event
     /// </summary>
     /// <typeparam name="T"></typeparam>
     void Send<T>() where T : new ();

     void Send<T>(T e);

     IUnRegister Register<T>(Action<T> onEvent);

     /// <summary>
     /// logout event
     /// </summary>
     /// <param name="onEvent"></param>
     /// <typeparam name="T"></typeparam>
     void UnRegister<T>(Action<T> onEvent);
 }
//Write-off mechanism
 public interface IUnRegister
 {
     void UnRegister();
 }

to focus on the implementation of the automatic logout mechanism:

Let's declare a class to specifically perform the function of the logout event:

public class TypeEventSystemUnRegister<T> : IUnRegister
{
    //holds a reference to the event mechanism
    public ITypeEventSystem TypeEventSystem { get; set; }

    // Holds the delegate to be canceled
    public Action<T> OnEvent { get; set; }

    // Specific deregister machine methods
    public void UnRegister()
    {
        //specific is to call the corresponding method of the event mechanism (system) to cancel out the specified function (OnEvent)
        (OnEvent).

        TypeEventSystem = null;
            OnEvent = null;
    }
}

Of course, the time to cancel is when the GameObjet is destroyed, for this you need a "trigger", which is mounted on the GameObject that registers the event, and is triggered when Destroy is detected.

to implement the corresponding trigger:

/// <summary>
/// Triggers for logout events
/// </summary>
public class UnRegisterOnDestroyTrigger : MonoBehaviour
{
    private HashSet<IUnRegister> mUnRegisters = new HashSet<IUnRegister>();

    public void AddUnRegister(IUnRegister unRegister)
    {
        (unRegister);
    }

    private void OnDestroy()
    {
        foreach (var unRegister in mUnRegisters)
        {
            ();
        }

        ();
    }
}

to extend the interface of the deregistration mechanism to facilitate the call of a method when registering an event, through which the trigger of the deregistration mechanism shown in the above code is directly mounted on the GameObject.

public static class UnRegisterExtension
{
    public static void UnRegisterWhenGameObjectDestroyed(this IUnRegister unRegister, GameObject gameObject)
    {
        var trigger = <UnRegisterOnDestroyTrigger>();

        if (!trigger)
        {
            trigger = <UnRegisterOnDestroyTrigger>();
        }

        (unRegister);
    }
}

So far, when we call the 'UnRegisterWhenGameObjectDestroyed' method, it will mount the Tirgger, which will be triggered when the object is destroyed, realizing the automatic deregistration event, which effectively ensures the registration and deregistration of the delegates in the use of Unity. deregistering events, effectively ensuring that delegates are registered and deregistered in pairs in Unity, and preventing null pointers from appearing in delegates.

Well, the implementation of the completion of the automatic logout event mechanism, continue to implement the event registration and invocation mechanism.

public class TypeEventSystem : ITypeEventSystem
{
    //Use the dependency inversion principle
    interface IRegistrations
    {

    }

    class Registrations<T> : IRegistrations
    {
        public Action<T> OnEvent = obj => { };
    }
//The corresponding event Action<T> is encapsulated as a class and stored as an interface type.
    private Dictionary<Type, IRegistrations> mEventRegistrations = new Dictionary<Type, IRegistrations>();

    public void Send<T>() where T : new()
    {
        public void Send<T>() where T : new() { var e = new T();
        Send<T>(e);
    }
    // Specific send mechanism Call mechanism
    public void Send<T>(T e)
    {
        var type = typeof(T);
        IRegistrations eventRegistrations;

        if ((type, out eventRegistrations))
        {
            // Specifically call the "unpack buck" call delegate
            (eventRegistrations as Registrations<T>)? (e);
        }
    }

    // Registration implementation
     public IUnRegister Register<T>(Action<T> onEvent)
     {
         var type = typeof(T);
         // Specifically storing the "pressurized and upgraded" functions to add to the delegate
         IRegistrations eventRegistrations.

         // determine if the stored event type exists
         if ((type, out eventRegistrations))
         {

         }
         else (if ((type, out eventRegistrations)) { }
         {
             // Add one if it doesn't exist
             eventRegistrations = new Registrations<T>();
             (type,eventRegistrations);
         }

         // If it exists, decompress it and add it to the "decompressed" event mechanism.
         (eventRegistrations as Registrations<T>).

         // Returns an instance of the data (reference) needed by the deregistration object.
         // The shared access object can be initialized and assigned without a constructor
         return new TypeEventSystemUnRegister<T>()
         return new TypeEventSystemUnRegister<T>()
             OnEvent = onEvent,
             TypeEventSystem = this
         }; }
     }
    // Specific implementation of the logout method
    public void UnRegister<T>(Action<T> onEvent)
    {
        var type = typeof(T);
        IRegistrations eventRegistrations;

        if ((type,out eventRegistrations))
        eventRegistrations; if ((type,out eventRegistrations))
            (eventRegistrations as Registrations<T>).OnEvent -= onEvent;
        }
    }

}

This completes the implementation of the custom event mechanism!

If interested, follow its corresponding test case presentation, (a separate blog will cover this event mechanism)

Moving forward, use this mechanism to implement temporal separation of Command:

public interface ICommand
{
    void Execute();
}

public class SayHelloCommand
{
    public void Execute()
    {
        // fulfillment
        ("Say Hello");
    }
}

void Start()
{
    // command
    var command = new SayHelloCommand();

    ();


    mTypeEventSystem = new TypeEventSystem();
    
   <ICommand>(Execute).UnRegisterWhenGameObjectDestroyed(gameObject);

    // command utilizationCommandObject Registration
   <Icommand>(new SayHelloCommand());
}

So what does comparing the three implementations reveal?

  • Methods: call-as-execute! No separation
  • Event mechanism: implementation is realized in event registration With separation
  • Command: implementation is realized inside Command with separation

Clearly, Command's separation of command and execution is somewhere between a method and an event mechanism.

Focus on the comparison of the event mechanism, in the implementation of custom methods before the author has pointed to the method of delegate storage, and in the use of encapsulation after the delegate (custom events) can be stored in the class (Command instances), although they are all through the delegate to store the implementation of the method, the use of Command is more free to some, can be executed in a customized location and timing, and the event mechanism generally need to be at least through the two objects in order to be completely use.

Let's write here first!

We continue to explore the role of the Command pattern in the evolution of the architecture in the following section, continuing to approach the Command pattern that we have come across in our studies!

Thanks to all of you who can join me in exploring the evolution of project architecture design!