You need to create a class to protect other developers on your team from having to deal with the details of how to add a hash to a string, as well as how to use the hash to verify if the string has been modified or corrupted.
The following classes decorate
the StringWriter
and
StringReader
classes to handle a hash added to its
contained string. The WriterDecorator
and
StringWriterHash
classes allow the
StringWriter
class to be decorated with extra
functionality to add a hash value to the
StringWriter
’s internal string.
Note that the method calls to create the hash value in the
CreateStringHash
method was defined in Recipe 14.5:
The code for the WriterDecorator
abstract base
class is:
using System; using System.Text; using System.IO; [Serializable] public abstract class WriterDecorator : TextWriter { public WriterDecorator( ) {} public WriterDecorator(StringWriter stringWriter) { internalStringWriter = stringWriter; } protected bool isHashed = false; protected StringWriter internalStringWriter = null; public void SetWriter(StringWriter stringWriter) { internalStringWriter = stringWriter; } }
This is the concrete implementation of the
WriterDecorator
class:
[Serializable] public class StringWriterHash : WriterDecorator { public StringWriterHash( ) : base( ) {} public StringWriterHash(StringWriter stringWriter) : base(stringWriter) { } public override Encoding Encoding { get {return (internalStringWriter.Encoding);} } public override void Close( ) { internalStringWriter.Close( ); base.Dispose(true); // Completes the cleanup } public override void Flush( ) { internalStringWriter.Flush( ); base.Flush( ); } public virtual StringBuilder GetStringBuilder( ) { return (internalStringWriter.GetStringBuilder( )); } public override string ToString( ) { return (internalStringWriter.ToString( )); } public void WriteHash( ) { int originalStrLen = internalStringWriter.GetStringBuilder( ).Length; // Call hash generator here for whole string. string hashedString = HashOps.CreateStringHash(this.ToString( )); internalStringWriter.Write(hashedString.Substring(originalStrLen)); isHashed = true; } public override void Write(char value) { if (isHashed) { throw (new Exception("A hash has already been added to this string"+ ", it cannot be modified.")); } else { internalStringWriter.Write(value); } } public override void Write(string value) { if (isHashed) { throw (new Exception("A hash has already been added to this string"+ ", it cannot be modified.")); } else { internalStringWriter.Write(value); } } public override void Write(char[] buffer, int index, int count) { if (isHashed) { throw (new Exception("A hash has already been added to this string"+ ", it cannot be modified.")); } else { internalStringWriter.Write(buffer, index, count); } } }
These are the ReaderDecorator
and
StringReaderHash
classes, which allow the
StringReader
class to be decorated with extra
functionality to handle the verification of a
string’s hash value. Note that the method calls to
verify the hash value in the
TestRecievedStringHash
method were defined in
Recipe 14.5:
[Serializable] public abstract class ReaderDecorator : TextReader { public ReaderDecorator( ) {} public ReaderDecorator(StringReader stringReader) { internalStringReader = stringReader; } protected StringReader internalStringReader = null; public void SetReader(StringReader stringReader) { internalStringReader = stringReader; } }
This is the concrete implementation of the
ReaderDecorator
class:
[Serializable] public class StringReaderHash : ReaderDecorator { public StringReaderHash( ) : base( ) {} public StringReaderHash(StringReader stringReader) : base(stringReader) { } public override void Close( ) { internalStringReader.Close( ); base.Dispose(true);// Completes the cleanup } public string ReadToEndHash( ) { string hashStr = internalStringReader.ReadToEnd( ); string originalStr = ""; // Call hash reader here. bool isInvalid = HashOps.TestReceivedStringHash(hashStr, out originalStr); if (isInvalid) { throw (new Exception("This string has failed its hash check.")); } return (originalStr); } public override int Read( ) { return (internalStringReader.Read( )); } public override int Read(char[] buffer, int index, int count) { return (internalStringReader.Read(buffer, index, count)); } public override string ReadLine( ) { return (internalStringReader.ReadLine( )); } public override string ReadToEnd( ) { return (internalStringReader.ReadToEnd( )); } }
The following code creates a StringWriter
object
(stringWriter
) and decorates it with a
StringWriterHash
object:
StringWriter stringWriter = new StringWriter(new StringBuilder("Initial Text")); StringWriterHash stringWriterHash = new StringWriterHash( ); stringWriterHash.SetWriter(stringWriter); stringWriterHash.Write("-Extra Text-"); stringWriterHash.WriteHash( ); Console.WriteLine("stringWriterHash.ToString( ): " + stringWriterHash.ToString( ));
The string “Initial Text” is added
to the StringWriter
on initialization, and later
the string “-Extra Text-” is added.
Next, the WriteHash
method is called to handle
adding a hash value to the end of the complete string. Notice that if
the code attempts to write more text to the
StringWriterHash
object after the
WriteHash
method has been called, an exception
will be thrown. The string cannot be modified once the hash has been
calculated and added.
The following code takes a StringReader
object
(stringReader
) that was initialized with the
string and hash produced by the previous code and decorates it with a
StringReaderHash
object:
StringReader stringReader = new StringReader(stringWriterHash.ToString( )); StringReaderHash stringReaderHash = new StringReaderHash( ); stringReaderHash.SetReader(stringReader); Console.WriteLine("stringReaderHash.ReadToEndHash( ): " + stringReaderHash.ReadToEndHash( ));
If the original string is modified after the hash is added, the
ReadToEndHash
method throws an exception.
The decorator design pattern provides the ability to modify individual objects
without having to modify or subclass the object’s
class. This allows for the creation of both decorated and undecorated
objects. The implementation of a decorator pattern is sometimes hard
to understand at first. An abstract decorator class is created that
inherits from the same base class as the class we will decorate. In
the case of this recipe, we will dec
orate the
StringReader
/StringWriter
classes to allow a hash to be calculated and used. The
StringReader
class inherits from
TextReader
, and the
StringWriter
class inherits from
TextWriter
.
Knowing this, we will create two abstract
decorator classes: ReaderDecorator
, which inherits
from TextReader
; and
WriterDecorator
, which inherits from
TextWriter
.
The abstract decorator classes contain two constructors: a private
field named
internalStreamReader
internalStreamWriter
and a method named
SetReader
SetWriter
. Basically,
the field stores a reference to the contained
StringReader
or StringWriter
object that is being decorated. This field can be set through either
a constructor or the
SetReader
SetWriter
method. The
interesting thing about this pattern is that each of the decorator
objects must also contain an instance of the class that they
decorate. The StringReaderHash
class contains a
StringReader
object in its
internalStreamReader
field, and the
StringWriterHash
class contains a
StringWriter
object in its
internalStreamWriter
field.
A concrete decorator class is created that inherits from the abstract
decorator classes. The StringReaderHash
class
inherits from ReaderDecorator
, while the
StringWriterHash
inherits from
WriterDecorator
. This pattern allows us the
flexibility to add concrete decorator classes without having to touch
the existing code.
Most of the methods in the StringReaderHash
and
StringWriterHash
classes simply act as wrappers to
the internalStreamReader
or
internalStreamWriter
objects, respectively. The
method that actually decorates the StringReader
object with a hash is the
StringReaderCRC.ReadToEndHash
method, and the
method that actually decorates the StringWriter
object is StringWriterCRC.WriteHash
. These two
methods allow the hash to be attached to a string and later used to
determine whether the string contents have changed.
The attractiveness of the decorator pattern is that we can add any
number of concrete decorator classes that derives from either
ReaderDecorator
or
WriterDecorator
. If we need to use a different
hashing algorithm, or even a quick and dirty hash algorithm, we can
subclass the ReaderDecorator
or
WriterDecorator
classes and add functionality to
use these new algorithms. Now we have more choices of how to decorate
these classes.