Overview
- What is a Wrapper Class?
- Bare-Bones Wrapper Example
- Real-World Wrapper Example
What is a Wrapper Class?
The main idea of a wrapper class is to mimic the functionality of another class while hiding the mimicked class' complexity. A wrapper is a class that takes an instance of another class (the class being wrapped) as an argument in its constructor, making it a primitive class of the one being wrapped. This means the wrapper has access to the wrapped class' functionality and is able to expose the desired features/build utility components on top of this wrapped class. In this post we'll be using C# to demonstrate but this is pattern is viable in all object-oriented languages.
Bare-Bones Wrapper Example
Okay, we understand that a wrapper should "wrap" another class. But what does that structure actually look like? Well it's pretty simple, the wrapper will contain a private class variable, which is the type of the wrapped class. The wrapper's constructor will take an instance of the wrapped class, and set the private class variable to this instance. This allows us to add to the wrapped class without actually modifying it. In this example, there's a class CoolPerson
, who has FirstName
, and LastName
class variables. This CoolPerson
class has two methods: one to say its first name, and another to say its last name. What if we wanted a method that will say both names at the same time? This is where the CoolPersonWrapper
class comes into play. This CoolPersonWrapper
will add to CoolPerson
with a FullName
class variable, and a method that says this full name.
In this block we see the main program instantiating a CoolPerson
, and wrapping that CoolPerson
within a CoolPersonWrapper
. The wrapper then goes ahead and says its full name with a single method.
namespace WrapperExample
{
class Program
{
static void Main(string[] args)
{
CoolPerson someCoolPerson = new CoolPerson("Cool", "Dude");
CoolPersonWrapper someCoolPersonWrapper = new CoolPersonWrapper(someCoolPerson);
someCoolPersonWrapper.SayFullName();
Console.ReadKey();
}
In this block we see the definition of CoolPerson
which has two separate methods for saying its first and last names.
public class CoolPerson
{
public string FirstName { get; set; }
public string LastName { get; set; }
public CoolPerson(string firstName, string lastName)
{
this.FirstName = firstName;
this.LastName = lastName;
}
public void SayFirstName()
{
Console.WriteLine("My first name is " + this.FirstName);
}
public void SayLastName()
{
Console.WriteLine("My last name is " + this.LastName);
}
}
In this block we see CoolPersonWrapper
takes a CoolPerson
in the constructor and uses this CoolPerson
to set the FullName
variable, which is used in the single SayFullName()
method which is the functionality we're adding on top of the wrapped class. We can also see SayFirstName()
and SayLastName()
are both methods from the wrapped class that we are exposing with public methods.
public class CoolPersonWrapper
{
private CoolPerson CoolPerson { get; set; }
private string FullName { get; set; }
public CoolPersonWrapper(CoolPerson coolPerson)
{
this.CoolPerson = coolPerson;
this.FullName = coolPerson.FirstName + " " + coolPerson.LastName;
}
public void SayFullName()
{
Console.WriteLine("My name is " + this.FullName);
}
public void SayFirstName()
{
this.CoolPerson.SayFirstName();
}
public void SayLastName()
{
this.CoolPerson.SayLastName();
}
}
}
}
The console output looks like this:
My name is Cool Dude
Real-World Wrapper Example
Why wouldn't we have just added the SayFullName()
method to the CoolPerson
class? Well, that would have worked too but what if the CoolPerson
class was part of a third party library that we did not have access to modify? Changing the functionality of third party classes is where wrapping is needed.
As an example, we can discuss the case of disposing COM objects. When developing an Excel or Word add-in, developers will be working with COM objects which need some manual memory management. The developer is required to release these objects using a Marshal.ReleaseComObject()
method call passing the COM object. On top of that, the developer must wrap the codeblock the COM object is used in with a try/finally, and call the release method in the finally block. This requires the developer to also declare the COM object outside of the try/finally. This can get ugly. Here is an example:
public void SetApplication()
{
WordInterop.Application application = null; // COM Object
try
{
application = new WordInterop.Application();
}
finally
{
if (application != null)
{
application.Quit();
Marshal.ReleaseComObject(application);
}
}
}
Imagine what it starts to look like when more COM objects are added! What can we do to make this cleaner? Well, we know that C# has an IDisposable
object interface which allows us to use a using
block for objects that implement this interface, and it will dispose of the object correctly when the block is finished. We can use a wrapper class to do this! What we do is wrap WordInterop.Application
in a new class, WordApplicationWrapper
. In this new wrapper class, we will also implement IDisposable
. To satisfy the requirements of IDisposble
we will implement a method that is called when the wrapped object is being released at the end of the using
. This method is where we're able to call Marshal.ReleaseComObject()
. This allows us now to use a using block instead of this try/catch, with the declaration outside:
public void SetApplication()
{
using (var application = new WordApplicationWrapper(new WordInterop.Application()))
{
application.Quit();
// When we reach the end of the using block, the wrapped object
// will be disposed of.
}
}
Much cleaner, the wrapper served us well. :)
You can check out my GitHub tutorials here.
If this post helped you, you might also like:
Top comments (3)
This is one of those patters I forget about since I haven't had need of it yet. Thanks for writing this. It was a good refresher for me.
I actually may try this on a project I'm working on currently.
A lot of other explanations I've seen for wrappers go overboard. You did a good job on this write-up.
Thank you, I appreciate that! 😊