Add Duplication to Reduce Duplication


As we talked, we felt we wanted to go forward with delegates, to understand them better and to see if we could improve this code. We decided to add some duplication to the KeyDownHandler that we would then try to eliminate. We refactored XMLKeyDownHandler this way:

 public void XMLKeyDownHandler(object objSender, KeyEventArgs kea) { 
if (kea.KeyCode == Keys.Enter && kea.Modifiers == Keys.None) {
GetText();
model.Enter();
PutText(textbox, model.LinesArray(), model.SelectionStart);
kea.Handled = true;
}
else if (kea.KeyCode == Keys.Enter && kea.Modifiers == Keys.Shift) {
GetText();
model.InsertReturn();
PutText(textbox, model.LinesArray(), model.SelectionStart);
kea.Handled = true;
}
}

This actually turns out to have been a much more elegant move than it might appear. We just put the duplication in, thinking that if we could get delegates to work, we would move the duplication up and out. As you ll see in a moment, that s what happened, but look what else happened .

The KeyDownHandler originally duplicated the GetText() and PutText() in time, rather than in space. Because we didn t have the methods inside the if statements, they were executed all the time. Now that they are inside the ifs, they are duplicated more in the text, but less often in time. You can think of that refactoring as removing duplication in the time domain and increasing it in the text domain. This might be a trick to remember.

Now, our intention was to remove the duplication by using a delegate, so we used the fake it till you make it approach and wrote this:

 private void HandleKeyboard(KeyboardDelegate kb) { 
GetText();
kb();
PutText(textbox, model.LinesArray(), model.SelectionStart);
}
public void XMLKeyDownHandler(object objSender, KeyEventArgs kea) {
if (kea.KeyCode == Keys.Enter && kea.Modifiers == Keys.None) {
HandleKeyboard(enterDelegate);
kea.Handled = true;
}
else if (kea.KeyCode == Keys.Enter && kea.Modifiers == Keys.Shift) {
GetText();
model.InsertReturn();
PutText(textbox, model.LinesArray(), model.SelectionStart);
kea.Handled = true;
}
}

This code won t work, but we typed it in just to look at it. The idea should be clear. We would define a new method, HandleKeyboard, that takes a delegate named kb. That method does the GetText, dispatches the delegate, and does the PutText. The idea is that we would use that same HandleKeyboard method from the other branch of the if, thereby getting rid of the duplication. That would be cool.

We decided we didn t like the name of the delegate, so we immediately refactored our non-working code this way:

 private void HandleKeyboard(KeyboardDelegate modelAction) { 
GetText();
modelAction();
PutText(textbox, model.LinesArray(), model.SelectionStart);
}

Now, normally, refactoring is a no-no on a red bar, and what we had here wouldn t even compile, but we were sketching code, not really trying to make anything work, and we felt that better names would help us better figure out what we were really trying to do. Working without any C# documentation, we fumbled our way through a definition of the delegate:

 private delegate void KeyboardDelegate(); 

We tried to create the actual delegate:

 private KeyboardDelegate enterDelegate = model.Enter; 

We wrote a more reasonable syntax for defining the actual delegate:

 private KeyboardDelegate enterDelegate = new KeyboardDelegate(model.Enter); 

This makes sense. A delegate definition is basically a type or class, so you have to create a new instance to assign a concrete delegate. But this still didn t compile. Finally, we came to this combination. We defined the delegate type:

 public delegate void KeyboardDelegate(); 

We defined a member variable of that type:

 private KeyboardDelegate enterDelegate; 

And we initialized the delegate, inside the Form s initialize() method:

 private void initialize(TextModel model) { 
enterDelegate = new KeyboardDelegate(model.Enter);
this.model = model;
this.Text = "XML Notepad";
insertSection = new MenuItem (
"Insert &Section",
new EventHandler(MenuInsertSection));
insertPre = new MenuItem (
"Insert &Pre",
new EventHandler(MenuInsertPre));
this.Menu = new MainMenu(new MenuItem[] {insertPre, insertSection} );
... and so on ...
this.Name = "XMLNotepad";
}

It turns out this actually works! The KeyDown method calls HandleKeyboard(enterDelegate), the Get and Put are done, and the tests are green! We go ahead and add another delegate:

 private KeyboardDelegate shiftEnterDelegate; 

private void initialize(TextModel model) {
enterDelegate = new KeyboardDelegate(model.Enter);
shiftEnterDelegate = new KeyboardDelegate(model.InsertReturn);
...

public delegate void KeyboardDelegate();
private void HandleKeyboard(KeyboardDelegate modelAction) {
GetText();
modelAction();
PutText(textbox, model.LinesArray(), model.SelectionStart);
}

public void XMLKeyDownHandler(object objSender, KeyEventArgs kea) {
if (kea.KeyCode == Keys.Enter && kea.Modifiers == Keys.None) {
HandleKeyboard(enterDelegate);
kea.Handled = true;
}
else if (kea.KeyCode == Keys.Enter && kea.Modifiers == Keys.Shift) {
HandleKeyboard(shiftEnterDelegate);
kea.Handled = true;
}
}

This also works. And look, we have removed the duplication in the KeyDownHandler!! By creating and using delegates, we ve reduced the code duplication. We ll talk later about whether we have decreased overall complexity, but for now, we re not done yet. First, we decide to rename HandleKeyboard to CallModel, which we think is closer to what it does:

 private void  CallModel  (KeyboardDelegate modelAction) { 
GetText();
modelAction();
PutText(textbox, model.LinesArray(), model.SelectionStart);
}
public void XMLKeyDownHandler(object objSender, KeyEventArgs kea) {
if (kea.KeyCode == Keys.Enter && kea.Modifiers == Keys.None) {
CallModel(enterDelegate);
kea.Handled = true;
}
else if (kea.KeyCode == Keys.Enter && kea.Modifiers == Keys.Shift) {
CallModel(shiftEnterDelegate);
kea.Handled = true;
}
}

We decide to go after one of the menu items. We begin with a new delegate:

 private void initialize(TextModel model) { 
enterDelegate = new KeyboardDelegate(model.Enter);
shiftEnterDelegate = new KeyboardDelegate(model.InsertReturn);
insertSectionDelegate = new KeyboardDelegate(model.InsertSectionTags);
this.model = model;
...

And we change the MenuHandler for the Insert Section menu. Here s the old:

 void MenuInsertSection(object obj, EventArgs ea) { 
GetText();
model.InsertSectionTags();
PutText(textbox, model.LinesArray(), model.SelectionStart);
}

Here s the new:

 void MenuInsertSection(object obj, EventArgs ea) { 
CallModel(insertSectionDelegate);
}

This works! So we do the other menu:

 void MenuInsertPre(object obj, EventArgs ea) { 
CallModel(insertPreTagDelegate);
}

(These uses get the same installation as member variables, with inits in the initialize() method, of course.) In summary, the code looks like the following. We define the member variables :

 private KeyboardDelegate enterDelegate; 
private KeyboardDelegate shiftEnterDelegate;
private KeyboardDelegate insertSectionDelegate;
private KeyboardDelegate insertPreTagDelegate;

We initialize them:

 private void initialize(TextModel model) { 
enterDelegate = new KeyboardDelegate(model.Enter);
shiftEnterDelegate = new KeyboardDelegate(model.InsertReturn);
insertSectionDelegate = new KeyboardDelegate(model.InsertSectionTags);
insertPreTagDelegate = new KeyboardDelegate(model.InsertPreTag);
this.model = model;
this.Text = "XML Notepad";
insertSection = new MenuItem (
"Insert &Section",
new EventHandler(MenuInsertSection));
insertPre = new MenuItem (
"Insert &Pre",
new EventHandler(MenuInsertPre));
...

We use the delegates in the menu and keyboard handlers:

 void MenuInsertSection(object obj, EventArgs ea) { 
CallModel(insertSectionDelegate);
}

void MenuInsertPre(object obj, EventArgs ea) {
CallModel(insertPreTagDelegate);
}
public void XMLKeyPressHandler(object objSender, KeyPressEventArgs kea) {
if ((int) kea.KeyChar == (int) Keys.Enter) {
kea.Handled = true;
// this code is here to avoid putting extra enters in the window.
// if removed, when you hit enter, the new <P> line breaks in two:
// <P>
// </P> like that.
}
}

public delegate void KeyboardDelegate();
private void CallModel(KeyboardDelegate modelAction) {
GetText();
modelAction();
PutText(textbox, model.LinesArray(), model.SelectionStart);
}

And we have completely eliminated the duplicate calls to GetText() and PutText()! Now to do some cleanup. We rename KeyboardDelegate to ModelAction, which we think better represents what the delegate is. We extract the delegate inits to a separate method and do a little more renaming of variables, and here s the final code, highlighting the main points of interest:

 using System; 
using System.Drawing;
using System.Windows.Forms;
using NUnit.Framework;
using System.Collections;
using System.Text.RegularExpressions;
namespace Notepad
{
class XMLNotepad : Form {
public TestableTextBox textbox;
private TextModel model;
private MenuItem insertPre;
private MenuItem insertSection;
private ModelAction enterAction;
private ModelAction shiftEnterAction;
private ModelAction insertSectionAction;
private ModelAction insertPreTagAction;
public delegate void ModelAction();
public MenuItem MenuForAccelerator(string accelerator) {
if (accelerator == "&S") return insertSection;
return insertPre;
}
static void Main(string[] args)
{
Application.Run(new XMLNotepad());
}
public XMLNotepad() {
initialize(new TextModel());
}
public XMLNotepad(TextModel model) {
initialize(model);
}
private void initialize(TextModel model) {
InitializeDelegates(model);
this.model = model; this.Text = "XML Notepad";
insertSection = new MenuItem (
"Insert &Section",
new EventHandler(MenuInsertSection));
insertPre = new MenuItem (
"Insert &Pre",
new EventHandler(MenuInsertPre));
this.Menu = new MainMenu(new MenuItem[] {insertPre, insertSection} );
this.textbox = new TestableTextBox();
this.textbox.Parent = this;
this.textbox.Dock = DockStyle.Fill;
this.textbox.BorderStyle = BorderStyle.None;
this.textbox.Multiline = true;
this.textbox.ScrollBars = ScrollBars.Both;
this.textbox.AcceptsTab = true;
this.textbox.KeyDown += new KeyEventHandler(XMLKeyDownHandler);
this.textbox.KeyPress += new KeyPressEventHandler(XMLKeyPressHandler);
this.textbox.Visible = true;
this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);
this.ClientSize = new System.Drawing.Size(292, 266);
this.Controls.AddRange(new System.Windows.Forms.Control[]
{ this.textbox});
this.Name = "XMLNotepad";
}
private void InitializeDelegates(TextModel model) {
enterAction = new ModelAction(model.Enter);
shiftEnterAction = new ModelAction(model.InsertReturn);
insertSectionAction = new ModelAction(model.InsertSectionTags);
insertPreTagAction = new ModelAction(model.InsertPreTag);
}
void MenuInsertSection(object obj, EventArgs ea) { CallModel(insertSectionAction);
}
void MenuInsertPre(object obj, EventArgs ea) {
CallModel(insertPreTagAction); }
public void XMLKeyPressHandler(object objSender, KeyPressEventArgs kea) {
if ((int) kea.KeyChar == (int) Keys.Enter) {
kea.Handled = true;
// this code is here to avoid putting extra enters in the window.
// if removed, when you hit enter, the new <P> line breaks in two:
// <P>
// </P> like that.
}
}

private void CallModel(ModelAction modelAction) {
GetText();
modelAction();
PutText(textbox, model.LinesArray(), model.SelectionStart); }
public void XMLKeyDownHandler(object objSender, KeyEventArgs kea) {
if (kea.KeyCode == Keys.Enter && kea.Modifiers == Keys.None) {
CallModel(enterAction);
kea.Handled = true;
}
else if (kea.KeyCode == Keys.Enter && kea.Modifiers == Keys.Shift) {
CallModel(shiftEnterAction);
kea.Handled = true;
}
}
public void PutText(ITestTextBox textbox, string[] lines,
int selectionStart) {
// this is Feature Envy big time.
textbox.Lines = lines;
textbox.SelectionStart = selectionStart;
textbox.ScrollToCaret();
}
private void GetText() {
model.SetLines(textbox.Lines);
model.SelectionStart = textbox.SelectionStart;
}
public void PutText() {
PutText(textbox, model.LinesArray(), model.SelectionStart);
}
}
}

All that ugly duplication is gone! And Paul and I know a lot more about how to do delegates than we used to.




Extreme Programming Adventures in C#
Javaв„ў EE 5 Tutorial, The (3rd Edition)
ISBN: 735619492
EAN: 2147483647
Year: 2006
Pages: 291

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net