The main idea of a wrapper class is to mimic the functionality of another class while hiding the mimicked class’s complexity. A wrapper is a class that takes an instance of another class (the class being wrapped) as an argument in its constructor, which makes 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 or build utility components on top of this wrapped class. In this post, we’ll be using C# for demonstration, but this pattern is viable in all object-oriented languages.
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(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
, that has FirstName
and LastName
instance 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 the full name.
In the code block below, 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 the following 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 the next block, we see that CoolPersonWrapper
takes a CoolPerson
in the constructor and uses this CoolPerson
to set the FullName
variable, which is issued in the single SayFullName()
method that is the functionality we’re adding on top of the wrapped class. We can also see that SayFirstName()
and SayLastName()
are both methods from the wrapped class that we are exposing with public methods.
namespace WrapperExample{class Program{static void Main(string[] args){CoolPerson someCoolPerson = new CoolPerson("Cool", "Dude");CoolPersonWrapper someCoolPersonWrapper = new CoolPersonWrapper(someCoolPerson);someCoolPersonWrapper.SayFullName();}}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(){System.Console.WriteLine("My first name is " + this.FirstName);}public void SayLastName(){System.Console.WriteLine("My last name is " + this.LastName);}}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(){System.Console.WriteLine("My name is " + this.FullName);}public void SayFirstName(){this.CoolPerson.SayFirstName();}public void SayLastName(){this.CoolPerson.SayLastName();}}}
Why didn’t we just add 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 that 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 code block that uses the COM object must be wrapped in a try/finally
and the release method must be called in the finally
block. This requires the developer to also declare the COM object outside of the try/finally
, which can get ugly. Here is an example:
public void SetApplication(){WordInterop.Application application = null; // COM Objecttry{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 that allows us to use a using
block for objects that implement this interface, and that 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 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
block. This method is where we’re able to call Marshal.ReleaseComObject()
. This allows us to use a using
block instead of the 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.