Some of these templates will be discussed below, and we’ll describe each of them briefly. We tried to choose the most used templates both in our projects and around the world.
public abstract class Command { public abstract void Execute(); public abstract void Undo(); public abstract void Redo(); } public class ConcreteCommand : Command { private Receiver receiver; public ConcreteCommand(Receiver r) { receiver = r; } public override void Execute() { receiver.Action(); } public override void Undo() {} public override void Redo() {} } public class Receiver { public void Action() { } } public class Invoker { private Command command; public void SetCommand(Command c) { command = c; } public void Run() { command.Execute(); } public void Cancel() { command.Undo(); } public void Restart() { command.Redo(); } } public class Client { void Main() { Invoker invoker = new Invoker(); Receiver receiver = new Receiver(); ConcreteCommand command=new ConcreteCommand(receiver); invoker.SetCommand(command); invoker.Run(); } }Let’s describe each participant of this template:
In the gaming field, this design pattern can often be found in user input locations. For example:
private void Update () { if (Input.GetKeyDown(KeyCode.UpArrow)) { invoker.SetCommand(new MoveUp(this)); } else if (Input.GetKeyDown(KeyCode.DownArrow)) { invoker.SetCommand(new MoveDown(this)); } else if (Input.GetKeyDown(KeyCode.LeftArrow)) { invoker.SetCommand(new MoveLeft(this)); } else if (Input.GetKeyDown(KeyCode.RightArrow)) { invoker.SetCommand(new MoveRight(this)); } invoker.Run(); }As a result, we get a system with reverse action functions in response to certain actions, as well as the ability to stop and restart commands. Also with this template, we can significantly expand the logging of our program for better error handling.
Based on the description above, this problem is solved quite simply using the Observer design pattern.
public class Subject { private List<Observer> observers = new List<Observer>(); public void Notify() { for (int i = 0; i < observers.Count; i++) { observers[i].OnNotify(); } } public void AddObserver(Observer observer) { observers.Add(observer); } public void RemoveObserver(Observer observer) { observers.Remove(observer); } } public abstract class Observer : MonoBehaviour { public abstract void OnNotify(); } public class Box : Observer { public override void OnNotify() { Destroy(gameObject); } }This design pattern became so popular that some languages began to introduce it as part of the standard library. For example, in the context of the C# language, the event keyword exists and, in conjunction with delegates, they actually implement the template.
In conclusion, we get a flexible system of alerts for subject observers, which allows us to significantly improve the architecture of applications and remove unnecessary “spaghetti” code.
Look at the example given below:
class Program { static void Main() { Player player = new Player(new State1()); player.DoSmth(); player.CurrentState = new State2(); player.DoSmth(); } } public abstract class State { public abstract void Do(Player player); } public class State1 : State { public override void Do(Player player) { context.State = new StateB(); } } public class State2 : State { public override void Do(Player player) { context.State = new StateA(); } } public class Player { public State CurrentState {get; set;} public Player(State state) { currentState = state; } public void DoSmth() { currentState.Do(this); } }This design reduces the number of branch operators and makes the code cleaner and more extensible.
public struct State { public int valueToSave; } public class Memento { public State State { get; private set;} public Memento(State state) { this.State = state; } } public class Caretaker { public Memento Memento { get; set; } } public class Originator { public State State { get; set; } public void SetMemento(Memento memento) { State = memento.State; } public Memento CreateMemento() { return new Memento(State); } } class Program { static void Main(string[] args) { State state1; state1.valueToSave = 5; State state2; state2.valueToSave = 10; Originator originator = new Originator(); originator.State = state1; originator.State.valueToSave = 0; Caretaker care = new Caretaker(); care.Memento = originator.CreateMemento(); originator.State = state2; } }In conclusion, we have a fairly simple way, without breaking the encapsulation, to maintain the state of an object.
For example, take the situation when you have a Match-3 game and a large number of new crystals are created, which the player periodically destroys. Without an object pool, creating each crystal will allocate memory, which can affect performance (and it will) for the worse. Such actions often increase memory fragmentation much and, as a result, the garbage collector spends more time freeing memory.
What is the solution to this problem? Instead of creating objects and allocating memory for them, we simply hide them from the user’s eyes, as long as they are again not needed. Statistically, resetting parameters is a much easier operation than allocating memory, so we can significantly improve the efficiency of the application by adding a simple object pool mechanism.
There are a lot of implementations of the object pool, but the main requirements are reduced to a short list:
Comments are closed.