[ Team LiB ] |
Recipe 14.6 Wrapping a String Hash for Ease of UseProblemYou 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. SolutionThe 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. DiscussionThe 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. See AlsoSee the "StringWriter Class" and "StringReader Class" topics in the MSDN documentation. |
[ Team LiB ] |