You are on page 1of 19

Capitolul 11

ablonul de proiectare Comand


1. Obiective 2. Scop i motivaie 3. Aplicabilitate 4. Analiza ablonului 5. Exemplu de implementare 6. Aplicaie

1. Obiective
Obiectivul principal al capitolului 11 este implementarea unui program dup ablonul de proiectare comportamental Comand (engl. Command). Se va prezenta i o metod de generare i adugare dinamic a unor controale n fereastra unui program.

2. Scop i motivaie
ablonul Comand ncapsuleaz o funcie ntr-un obiect, astfel nct este posibil trimiterea unor funcii ca parametri, formarea de cozi de apeluri, nregistrarea listei de apeluri (engl. logging) i asigurarea suportului pentru operaiile de anulare (engl. undo). S presupunem o aplicaie cu o bar de instrumente care poate fi personalizat. Cu fiecare buton al barei, utilizatorul i poate asocia funcia dorit. Deci, din punct de vedere al proiectantului, nu se poate cunoate apriori nici operaia propriu-zis care va fi efectuat de un buton i nici obiectul concret care va realiza operaia respectiv. De asemenea, pentru a avea o arhitectur elegant, toate butoanele trebuie tratate n mod unitar. Soluia propus de ablonul Comand este ncapsularea unui astfel de apel ntr-un obiect, care poate fi stocat sau trimis ca parametru altor obiecte. Dup cum se poate vedea n figura 11.1, clasa abstract Command include o operaie Execute, implementat de ctre clasele concrete derivate, care conin ca i cmp destinatarul apelului (obiectul care va realiza efectiv operaia) i care i vor transfera acestuia apelul de execuie a operaiei. Pentru simplitate, s-au omis semnturile metodelor. Trebuie subliniat faptul
211
Florin Leon (2012). Aplicatii de ingineria programarii in C#, Tehnopress, Iasi, ISBN 978-973-702-909-6 http://florinleon.byethost24.com

Florin Leon Aplicaii de ingineria programrii n C#

c aceste clase de comand sunt clase normale, care pot include i alte cmpuri i metode pe lng metoda Execute specific ablonului.

Figura 11.1. Exemplu de sistem bazat pe ablonul Comand

n acest exemplu, destinatarii sunt clasele Document i Image. Comanda PasteCommand are un cmp de tipul Document iar clasa FlipCommand are un cmp de tipul Image. La apelul metodelor Execute, obiectele de comand apeleaz metodele corespunztoare din obiectele destinatar, dup unele eventuale prelucrri suplimentare. Clasele de comand sunt derivate dintr-o clas de baz comun i deci pot fi tratate unitar, chiar dac operaiile lor Execute au implementri diferite. ablonul permite de asemenea definirea unor comenzi macro, adic a unei mulimi de operaii. O astfel de clas, prezentat n figura 11.2, poate avea un vector de comenzi simple, pe care le apeleaz succesiv.

Figura 11.2. Comand macro


212
Florin Leon (2012). Aplicatii de ingineria programarii in C#, Tehnopress, Iasi, ISBN 978-973-702-909-6 http://florinleon.byethost24.com

Capitolul 11. ablonul de proiectare Comand

Clasa MacroCommand este derivat la rndul su din aceeai clas abstract Command, deci poate fi tratat ca orice alt comand. Ea nu are ns un destinatar explicit, deoarece comenzile din serie au propriul destinatar. n msura n care sistemul dispune de mai multe comenzi, dac gestionarea acestora ar aparine exclusiv clientului, acest fapt ar conduce la creterea cuplrii ntre aceste clase. De aceea, ablonul introduce o clas suplimentar, Invoker, care este responsabil de setarea dinamic a comenzilor i de apelarea acestora. n exemplul anterior, bara de instrumente este un Invoker, care gestioneaz comenzile asociate cu butoanele sale, iar aplicaia (clasa corespunztoare ferestrei principale, de exemplu) este clientul. Odat ce o comand a fost introdus n Invoker, ea poate fi executat (o dat sau de mai multe ori), poate fi eliminat sau nlocuit n mod dinamic. Una din trsturile foarte importante care pot fi implementate uor cu ajutorul ablonului Comand este asigurarea operaiilor de anulare (engl. undo) i refacere (engl. redo) a ultimelor aciuni. n general, chiar dac aplicaia are mai multe comenzi, exist cte o singur opiune de anulare, respectiv refacere. De aceea, sistemul trebuie s cunoasc succesiunea operaiilor efectuate i modul n care starea sistemului este modificat de fiecare comand. Astfel, clasele de comand vor avea dou metode suplimentare, Undo i Redo i vor trebui s rein starea sistemului nainte de aplicarea comenzii i dup aplicarea sa (figura 11.3).

Figura 11.3. Comand cu operaii de anulare i refacere

Clasa Invoker poate reine ntr-o stiv succesiunea de comenzi efectuate iar dac se dorete de exemplu anularea ultimelor n aciuni, se va apela metoda Undo din cele n elemente scoase din stiv. Putem spune c ablonul decupleaz obiectul care invoc o operaie de cel care tie cum s o efectueze, ceea ce ofer un grad ridicat de flexibilitate n proiectarea unei aplicaii. n cazul interfeei grafice cu utilizatorul, o aplicaie poate furniza pentru o funcie att o interfa cu meniuri, ct i una cu butoane i n acest caz meniurile i butoanele vor fi
213
Florin Leon (2012). Aplicatii de ingineria programarii in C#, Tehnopress, Iasi, ISBN 978-973-702-909-6 http://florinleon.byethost24.com

Florin Leon Aplicaii de ingineria programrii n C#

asociate cu aceleai instane ale claselor concrete de comand. Comenzile pot fi nlocuite n mod dinamic, lucru util pentru implementarea meniurilor sensibile la context. De asemenea, prin compunerea comenzilor ntr-unele de mai mari dimensiuni, se poate facilita generarea script-urilor de comand.

3. Aplicabilitate
ablonul Comand poate fi utilizat n primul rnd atunci cnd trebuie s se trimit o aciune ca parametru unui obiect, aa cum este cazul n situaia cu bara de instrumente prezentat anterior. Trimiterea poate fi fcut i prin intermediul unei funcii de apelare invers (engl. callback). De exemplu, codul de tratare a evenimentelor n platforma .NET este ncapsulat n funcii delegat i utilizat sub forma:
button.Click += new System.EventHandler(button_Click)

Comenzile reprezint un echivalent orientat obiect pentru funciile de apelare invers. S presupunem c ntr-un sistem de echilibrarea ncrcrii, activitile de calcul (task-urile) trebuie transferate pe diferite maini. Fiecare activitate de calcul este diferit. Pentru a trata n mod unitar toate aceste activiti, ele pot fi ncapsulate n obiecte de comand. La fel, dac activitile sunt preluate pentru rulare de fire de execuie dintr-un bazin (engl. thread pool), utilizarea comenzilor simplific foarte mult procesul, deoarece un fir de execuie nu trebuie s tie dect c o activitate are o metod Execute care trebuie apelat. Dup cum am menionat, o comand poate avea posibilitatea anulrii sau refacerii aciunii. Dac sistemul trebuie s permit anularea i refacerea unei ntregi succesiuni de comenzi, acest fapt poate fi uor implementat prin introducerea n Invoker a unor liste (sau stive) de comenzi pentru anulare, respectiv refacere. n momentul cnd se anuleaz o operaie, comanda respectiv se scoate din lista comenzilor de anulare i se introduce n lista comenzilor de refacere. Se procedeaz analog n cazul apelrii unei operaii de refacere. Prin traversarea acestor liste se pot asigura oricte niveluri de anulare i refacere. Pentru sisteme care gestioneaz date foarte complexe, salvarea strii n cazul blocrii se poate face tot cu ajutorul comenzilor. n aceast situaie nu este fezabil salvarea datelor dup fiecare operaie efectuat. Aplicaia
214
Florin Leon (2012). Aplicatii de ingineria programarii in C#, Tehnopress, Iasi, ISBN 978-973-702-909-6 http://florinleon.byethost24.com

Capitolul 11. ablonul de proiectare Comand

poate s pstreze o nregistrare (engl. log) cu seria de comenzi efectuate de la pornirea sa. Dac la un moment dat sistemul se blocheaz, starea sa poate fi refcut din cea iniial, aplicnd n ordine comenzile salvate pe harddisk. n interfaa obiectelor de comand pot fi adugate operaii de ncrcare i salvare. Salvarea comenzilor poate fi att o list cu identificatorii i parametrii acestora, ct i serializarea efectiv a obiectelor de comand aplicate.

4. Analiza ablonului
Diagrama generic de clase a ablonului Comand este prezentat n figura 11.4.

Figura 11.4. Diagrama de clase a ablonului Comand

Clasa abstract (sau interfaa) Command declar metode precum Execute, eventual Undo i Redo. Clasele concrete ConcreteCommand implementeaz operaia Execute prin invocarea operaiei sau operaiilor corespunztoare din obiectul Receiver. Clasa Receiver tie cum s efectueze operaiile asociate cu executarea unei comenzi. Orice clas poate servi drept destinatar. Clasa Invoker cere comenzii s execute aciunea. Clientul creeaz un obiect ConcreteCommand i i stabilete destinatarul. Modul n care acioneaz participanii este detaliat n diagrama de secvene din figura 11.5.

215
Florin Leon (2012). Aplicatii de ingineria programarii in C#, Tehnopress, Iasi, ISBN 978-973-702-909-6 http://florinleon.byethost24.com

Florin Leon Aplicaii de ingineria programrii n C#

Figure 11.5. Diagrama de secvene a ablonului Comand

Clientul creeaz obiectul ConcreteCommand i i precizeaz destinatarul. Obiectul Invoker stocheaz obiectul ConcreteCommand. Cnd este nevoie, obiectul Invoker apeleaz operaia Execute din obiectul Command. n cazul n care comenzile pot fi anulate, obiectul ConcreteCommand i stocheaz starea pentru anularea comenzii nainte de a aplica operaia Execute. Obiectul ConcreteCommand apeleaz operaiile din obiectul Receiver pentru a ndeplini aciunea propriu-zis. Consecina principal a ablonului este decuplarea obiectului care invoc o operaie de obiectul care tie cum s o efectueze. Astfel, noi comenzi pot fi adugate foarte uor deoarece nu sunt necesare modificri ale claselor existente.

5. Exemplu de implementare
Codul C# corespunztor structurii ablonului Comand este prezentat n cele ce urmeaz. Comanda abstract
abstract class Command { protected Receiver _receiver; public Command(Receiver receiver) { _receiver = receiver; } 216
Florin Leon (2012). Aplicatii de ingineria programarii in C#, Tehnopress, Iasi, ISBN 978-973-702-909-6 http://florinleon.byethost24.com

Capitolul 11. ablonul de proiectare Comand abstract public void Execute(); }

Comanda concret
class ConcreteCommand : Command { public ConcreteCommand(Receiver receiver) : base(receiver) { } public override void Execute() { _receiver.Action(); } }

Destinatarul
class Receiver { public void Action() { Console.WriteLine("Apelul metodei din destinatar"); } }

Invocatorul
class Invoker { private Command _command; public void SetCommand(Command command) { _command = command; } public void ExecuteCommand() { _command.Execute(); } }

217
Florin Leon (2012). Aplicatii de ingineria programarii in C#, Tehnopress, Iasi, ISBN 978-973-702-909-6 http://florinleon.byethost24.com

Florin Leon Aplicaii de ingineria programrii n C#

Clientul
public class Client { public static void Main(string[] args) { // Se creeaz destinatarul, comanda i invocatorul Receiver r = new Receiver(); Command c = new ConcreteCommand(r); Invoker i = new Invoker(); // Se seteaz i se execut comanda i.SetCommand(c); i.ExecuteCommand(); } }

6. Aplicaii
6.1. Realizai un program care simuleaz o foaie de calcul (gen Microsoft Excel). Un schelet al aplicaiei pentru construirea interfeei grafice cu utilizatorul (prezentat n figura 11.6) este inclus dup observaii i recomandri.

Figura 11.6. Exemplu de rezolvare: interfaa cu utilizatorul

218
Florin Leon (2012). Aplicatii de ingineria programarii in C#, Tehnopress, Iasi, ISBN 978-973-702-909-6 http://florinleon.byethost24.com

Capitolul 11. ablonul de proiectare Comand

Observaii: Controlul de tip grid este realizat dinamic cu text-box-uri. Alternativ, se poate lucra cu un control DataGridView; Componentele controlului sunt de fapt de tip ExtendedTextBox, clas derivat din TextBox, care include o metod de formatare special a numerelor din foaia de calcul: cu dou zecimale i aliniate la dreapta. Atunci cnd se editeaz textul unui ExtendedTextBox, n momentul n care se apas ENTER sau se prsete controlul respectiv, deci cnd se trimite comanda, Text-ul este deja modificat. Pentru a pstra ntr-un mod mai convenabil starea anterioar, se poate folosi proprietatea PreviousText. Cum ar putea fi proiectat comanda de modificare a textului unei celule pentru a nu fi nevoii s folosii aceast proprietate? Rmne la latitudinea proiectantului s stabileasc gradul de complexitate al comenzilor dac o comand acioneaz asupra unui TextBoxGrid sau asupra unui ExtendedTextBox. De obicei, se recomand utilizarea unor comenzi ct mai simple. Recomandri: Se poate lucra cu trei clase de comenzi, pentru schimbarea textului, schimbarea formatului i schimbarea culorii unui ExtendedTextBox; Comanda va primi n constructor o referin la ExtendedTextBox-ul asupra cruia se fac modificrile (acesta este Receiver-ul).

Pentru a v putea concentra exclusiv asupra aplicrii ablonului, n continuare se dau cteva clase ce urmeaz a fi completate sau utilizate n program. MainForm.cs
namespace Spreadsheet { public partial class MainForm : Form { public MainForm() { InitializeComponent();

219
Florin Leon (2012). Aplicatii de ingineria programarii in C#, Tehnopress, Iasi, ISBN 978-973-702-909-6 http://florinleon.byethost24.com

Florin Leon Aplicaii de ingineria programrii n C# _grid = new TextBoxGrid(Controls, 25, 120, new KeyEventHandler(textBox_KeyDown), new EventHandler(textBox_Leave)); _invoker = new Invoker(_grid); } TextBoxGrid _grid; Invoker _invoker; int _selected; private void textBox_KeyDown(object sender, KeyEventArgs e) { if (e.KeyCode == Keys.Enter) { // cnd se apas ENTER ntr-o celul ExtendedTextBox tb = (ExtendedTextBox)sender; string name = tb.Name; _selected = Convert.ToInt32(name.Substring(2)); this.ActiveControl = _grid.GetSuccessor(_selected); } } private void textBox_Leave(object sender, EventArgs e) { // cnd o celul nu mai este controlul selectat activ din fereastr Control ac = this.ActiveControl; ExtendedTextBox tb = (ExtendedTextBox)sender; string name = tb.Name; _selected = Convert.ToInt32(name.Substring(2)); tb.FormatText(); // de completat - tratarea schimbrii textului this.ActiveControl = ac; UpdateUndoRedoCombos(); } private void buttonColor_Click(object sender, EventArgs e) { if (colorDialog1.ShowDialog() != DialogResult.OK) return;

220
Florin Leon (2012). Aplicatii de ingineria programarii in C#, Tehnopress, Iasi, ISBN 978-973-702-909-6 http://florinleon.byethost24.com

Capitolul 11. ablonul de proiectare Comand Color c = colorDialog1.Color; buttonColor.ForeColor = c; //buttonColor.BackColor = Color.FromArgb(255 - c.R, 255 - c.G, 255 - c.B); // de completat UpdateUndoRedoCombos(); } private void buttonNormal_Click(object sender, EventArgs e) { // de completat UpdateUndoRedoCombos(); } private void buttonBold_Click(object sender, EventArgs e) { // de completat UpdateUndoRedoCombos(); } private void buttonItalic_Click(object sender, EventArgs e) { // de completat UpdateUndoRedoCombos(); } private void UpdateUndoRedoCombos() { comboBoxUndo.Items.Clear(); string[] str = _invoker.UndoNames.ToArray(); for (int i = 0; i < str.Length; i++) comboBoxUndo.Items.Add(str[i]); if (comboBoxUndo.Items.Count > 0) { comboBoxUndo.SelectedIndex = 0; buttonUndo.Enabled = true; } else buttonUndo.Enabled = false; comboBoxRedo.Items.Clear(); str = _invoker.RedoNames.ToArray(); for (int i = 0; i < str.Length; i++) comboBoxRedo.Items.Add(str[i]);

221
Florin Leon (2012). Aplicatii de ingineria programarii in C#, Tehnopress, Iasi, ISBN 978-973-702-909-6 http://florinleon.byethost24.com

Florin Leon Aplicaii de ingineria programrii n C# if (comboBoxRedo.Items.Count > 0) { comboBoxRedo.SelectedIndex = 0; buttonRedo.Enabled = true; } else buttonRedo.Enabled = false; } private void buttonUndo_Click(object sender, EventArgs e) { _invoker.Undo(); UpdateUndoRedoCombos(); } private void buttonRedo_Click(object sender, EventArgs e) { _invoker.Redo(); UpdateUndoRedoCombos(); } private void buttonExit_Click(object sender, EventArgs e) { Close(); } } }

ExtendedTextBox.cs
namespace Spreadsheet { class ExtendedTextBox : TextBox { private string _previousText; public string PreviousText { get { return _previousText; } set { _previousText = value; } }

222
Florin Leon (2012). Aplicatii de ingineria programarii in C#, Tehnopress, Iasi, ISBN 978-973-702-909-6 http://florinleon.byethost24.com

Capitolul 11. ablonul de proiectare Comand public ExtendedTextBox() : base() { _previousText = ""; } public void FormatText() { double d; if (double.TryParse(Text, out d)) { Text = d.ToString("F2"); TextAlign = HorizontalAlignment.Right; } else TextAlign = HorizontalAlignment.Left; } } }

TextBoxGrid.cs
namespace Spreadsheet { class TextBoxGrid { private ExtendedTextBox[] _textBoxes; public const int Size = 5; public TextBoxGrid(Control.ControlCollection controlCollection, int left, int top, KeyEventHandler keyDownEvent, EventHandler leaveEvent) { _textBoxes = new ExtendedTextBox[Size * Size]; for (int i = 0; i < Size * Size; i++) { int x = i % Size; int y = i / Size; _textBoxes[i] = new ExtendedTextBox(); _textBoxes[i].Width = 100; _textBoxes[i].Height = 20; _textBoxes[i].Left = left + x * 100; _textBoxes[i].Top = top + y * 20; _textBoxes[i].Text = ""; _textBoxes[i].Name = "Tb" + i; 223
Florin Leon (2012). Aplicatii de ingineria programarii in C#, Tehnopress, Iasi, ISBN 978-973-702-909-6 http://florinleon.byethost24.com

Florin Leon Aplicaii de ingineria programrii n C# _textBoxes[i].KeyDown += keyDownEvent; _textBoxes[i].Leave += leaveEvent; controlCollection.Add(_textBoxes[i]); } for (int i = 0; i < Size * Size; i++) _textBoxes[i].PreviousText = _textBoxes[i].Text; Label[] labelsX = new Label[Size]; for (int i = 0; i < Size; i++) { labelsX[i] = new Label(); labelsX[i].Text = ((char)(i + 'A')).ToString(); labelsX[i].Left = left + i * 100 + 48; labelsX[i].Top = top - 15; controlCollection.Add(labelsX[i]); } Label[] labelsY = new Label[Size]; for (int i = 0; i < Size; i++) { labelsY[i] = new Label(); labelsY[i].Text = (i + 1).ToString(); labelsY[i].Left = left - 15; labelsY[i].Height = 20; labelsY[i].Top = top + i * 20 + 4; controlCollection.Add(labelsY[i]); } } public void Clear() { for (int i = 0; i < Size * Size; i++) { _textBoxes[i].Clear(); _textBoxes[i].Font = new Font(_textBoxes[i].Font, FontStyle.Regular); _textBoxes[i].ForeColor = Color.Black; _textBoxes[i].PreviousText = _textBoxes[i].Text; } } public ExtendedTextBox GetSuccessor(int cellNumber) { cellNumber = (cellNumber + 1) % (Size*Size); return _textBoxes[cellNumber]; }

224
Florin Leon (2012). Aplicatii de ingineria programarii in C#, Tehnopress, Iasi, ISBN 978-973-702-909-6 http://florinleon.byethost24.com

Capitolul 11. ablonul de proiectare Comand public string GetCoords(int cellNumber) { char tbx = (char)((cellNumber % Size) + 'A'); int tby = cellNumber / Size + 1; return tbx.ToString() + tby; } public ExtendedTextBox GetCell(int cellNumber) { return _textBoxes[cellNumber]; } } }

ICommand.cs
namespace Spreadsheet { interface ICommand { bool Execute(); void Undo(); void Redo(); } }

Invoker.cs
namespace Spreadsheet { class Invoker { private TextBoxGrid _grid; private Stack<ICommand> _commands; private Stack<ICommand> _redoCommands; private Stack<string> _undoNames, _redoNames; public Stack<string> RedoNames { get { return _redoNames; } }

225
Florin Leon (2012). Aplicatii de ingineria programarii in C#, Tehnopress, Iasi, ISBN 978-973-702-909-6 http://florinleon.byethost24.com

Florin Leon Aplicaii de ingineria programrii n C# public Stack<string> UndoNames { get { return _undoNames; } } public Invoker(TextBoxGrid grid) { _grid = grid; _commands = new Stack<ICommand>(); _redoCommands = new Stack<ICommand>(); _undoNames = new Stack<string>(); _redoNames = new Stack<string>(); } public void SetAndExecute(ICommand command, string description) { // de completat } public void Undo() // (int level) { // este foarte simpl anularea mai multor niveluri de aciuni: // a ultimelor "level" comenzi // de completat } public void Redo() // (int level) { // de completat } } }

ChangeColorCommand.cs
namespace Spreadsheet { class ChangeColorCommand : ICommand { ExtendedTextBox _textBox; // alte cmpuri

226
Florin Leon (2012). Aplicatii de ingineria programarii in C#, Tehnopress, Iasi, ISBN 978-973-702-909-6 http://florinleon.byethost24.com

Capitolul 11. ablonul de proiectare Comand public ChangeColorCommand(ExtendedTextBox textBox, Color color) { //... } public bool Execute() { //... // returneaz true dac se modific ceva n _textBox // returneaz false dac nu se modific nimic return false; } public void Undo() { //... } public void Redo() { //... } } }

ChangeFormatCommand.cs
namespace Spreadsheet { class ChangeFormatCommand : ICommand { ExtendedTextBox _textBox; // alte cmpuri public ChangeFormatCommand(ExtendedTextBox textBox, FontStyle format) { //... } public bool Execute() { //... // exemplu de schimbare a corpului de liter: // _textBox.Font = new Font(_textBox.Font, _newStyle); // returneaz true dac se modific ceva n _textBox 227
Florin Leon (2012). Aplicatii de ingineria programarii in C#, Tehnopress, Iasi, ISBN 978-973-702-909-6 http://florinleon.byethost24.com

Florin Leon Aplicaii de ingineria programrii n C# // returneaz false dac nu se modific nimic return false; } public void Undo() { //... } public void Redo() { //... } } }

ChangeTextCommand.cs
namespace Spreadsheet { class ChangeTextCommand : ICommand { ExtendedTextBox _textBox; // alte cmpuri public ChangeTextCommand(ExtendedTextBox textBox) { //... } public bool Execute() { // textul nou deja exist n _textBox.Text //... // returneaz true dac se modific ceva n _textBox // returneaz false dac nu se modific nimic return false; } public void Undo() { //... }

228
Florin Leon (2012). Aplicatii de ingineria programarii in C#, Tehnopress, Iasi, ISBN 978-973-702-909-6 http://florinleon.byethost24.com

Capitolul 11. ablonul de proiectare Comand public void Redo() { //... } } }

6.2. Tem pentru acas. Extindei programul astfel nct s permit operaii simple cu numerele din celule (figura 11.7).

Figura 11.7. Exemplu de rezolvare: adugarea unor operaii matematice simple

La apsarea tastei ENTER n celula D1, coninutul su va deveni 5.00. Operaii permise vor fi: Add (adunare), Sub (scdere), Mul (nmulire), Div (mprire), cu exact 2 parametri. Se va afia un mesaj de eroare dac celulele trimise ca argumente nu conin numere valide. Cnd cursorul revine n celula D1, deci cnd rencepe editarea celulei, va aprea din nou formula. Rezultatul operaiei apare numai cnd celula nu este selectat. Se vor pstra operaiile de anulare (undo) i refacere (redo).

229
Florin Leon (2012). Aplicatii de ingineria programarii in C#, Tehnopress, Iasi, ISBN 978-973-702-909-6 http://florinleon.byethost24.com

You might also like