Utilities: Design of a Compression Gadget
Utility classes in software development projects of all sizes exist with the purpose of supporting a particular functionally; from compression utilities to setting loaders are amongst the most common.
However, what is the real meaning of a utility class, and what does 'low cost' mean in this context? Without any doubt, most programmers have worked with 'utility classes' at least once, and I'm sure we'd agree that utility classes must meet certain criteria in a .NET project.
Before getting into C# code, think for a moment, from the business point of view, what design should we consider so it's affordable yet useful to the project?
The cost should be low:
- Can be done fast
- Easy to maintain by you or others in your team
- Have the least amount of dependencies
Its features must:
Its implementation can:
- Be done by a single developer.
- Reduce the amount of code in other modules or the context of which is being used.
- Be integrated into other components easily and without much effort.
In a previous post I wrote, not too long ago, named Stream Compression - System.IO.Compression Library – Usability Review.
I mentioned some features that a compression utility should contain, so let's go ahead and take that as the starting point now and code a utility class from scratch.
Let's take a look at the functionality that we want:
- Flexible enough to support
DeflateStream
, GzipStream
or any other future implementations (if API doesn't change) - You can use your own compression class implementation as long as it follows the same pattern.
- It must be affordable as mentioned above.
You'll find the code file attached at the bottom of this post. You can use it freely, extend it and/or modify it as needed.
Dependencies
.NET Framework 4.0 libraries:
System
System.text. System.IO.Compression
System.IO
Assumptions and Limitations
DeflateStream
and GzipStream
classes both inherit from stream
class; its core functionality seems to be similar, I'm assuming Microsoft framework designers will continue to use this pattern (if they eventually add another compression class). Furthermore, any developers who implement a compression algorithm will also be using this approach.
Usages
We want to make it as simple as possible so other developers can use it by writing very little code. Let's make it intuitive, something maybe like UtilityName.Action<the_compression_class> (file); </the_compression_class>
By inferring the class compression type, we allow it to be pluggable.
Design
- Let's make the class
static
- that way another developer won't need to instantiate it (less code). - Since we're inferring the compression class, the best way to go will be using generic types.
- And because we're using generic types, we need a way to instantiate it also, so let's use
Activator
class.
Implementation
This approach is very simple yet useful that allows using any compression class, all we need to do is find a way to create an instance of it, passed in the necessary parameters; and because the base class for the compression classes will remain "Stream
", we know, we can abstract a generic functionality from it.
Stream
class has one static
constructor and another one protected
, its implementers both DeflateStream
and GzipStream
heavily rely on its class constructors. This can be seen as the entry point of the actual compression functionality. That means we most likely won't need to call any other method from Stream
to apply an action other than the class constructor.
Key Points
A few things I want to mention about the structure of this design:
- It doesn't really care what type of compression the developers selects. We're not using specific type classes inside the code anyway. Therefore, if Microsoft adds another compression class in the future, it shouldn't be a problem. No further changes will be needed inside our utility class; obviously this means that maintenance will be too little to none.
- To call the function, per se, doesn't require writing more than one line of code. Making it easy to use.
- Since the utility class is
static
, that means that we can integrate its functionality with other components using extension methods, for example, maybe we want to support Text compression into primitive members such as the String
.
The compress
function looks like:
Decompress
function looks the same, except it does the opposite:
Using this functionality requires only one line of code as such: