Print
Add To Favorites
Email To Friend
Rate This Article
|
Avoid Exposing Collections Directly as Properties
|
Published:
22 Feb 2011
|
Abstract
.NET makes it easy to create strongly typed collections and expose them as properties of our classes. However, this generally results in a design that fails at encapsulation and exposes too much of the class's internal state. Learn a few simple techniques to keep from going down this path in this article. |
|
by Steven Smith
Feedback
|
Average Rating: This article has not yet been rated.
Views (Total / Last 10 Days):
28680/
41
|
|
|
Introduction |
Sometimes your domain objects have one-to-many or
many-to-many relationships with other objects. For instance, your Customers
might have collections of Orders associated with them. The simplest way to
model this is to expose a List<Order> field or property off of your
Customer class. Unfortunately, this has a number of negative consequences
because of how it breaks encapsulation. Fortunately, there are some effective
ways to shield yourself from these issues while still exposing the collection's
data to your class's clients.
|
Doing It Wrong |
Doing it the simplest way that could possibly work would
mean simply exposing the internal state of the Customer class by creating a
public field of List<Order>. The listing below shows what this might
look like, and a couple of tests that show the required functionality (and
currently pass):
At first glance, this appears to solve the requirements of
the system. Now let's add a few more tests to reveal the weaknesses in this
approach, and refactor it to improve things.
|
Guarding Against Destructive Actions |
The current implementation would allow a user of Customer to
set its OrderHistory field to null. This test shows the problem:
The simplest fix to this is to change our OrderHistory to
use a property, and to make it readonly by giving it a private setter. With
this change in place, the above test no longer even compiles, so we should be
safe from this problem.
However, users of the system can still perform destructive
actions on the OrderHistory by using the List<Order> type's methods. For
instance, they can Clear() the collection, as this next test shows:
In order to remove access to the List<T> class's
methods, we can hide the implementation details of our internal representation
behind a read-only interface, like IEnumerable. However, changing to
IEnumerable also means we can no longer add directly to OrderHistory - this is
also a good thing, since that was exposing too much internal state. Thus we
add a method to Customer specifically for adding orders to the order history.
The resulting class and tests are shown below:
|
Overriding Intended Behavior |
Even if you only expose your type as an IEnumberable, sneaky
clients of your class can still get to its underlying methods if they guess
correctly. As the next test shows, if a consumer of your class is able to
guess (or determine through reflection or a decompiler) the actual underlying
type of your interface, then all bets are off and they can destroy your data at
will:
We can probably safely say that any developer who so
blatantly overcomes your attempts at encapsulating data deserves the bugs they
create by doing so. However, there is one more alternative approach
|
Wrapping Collections |
The built-in .NET class, ReadOnlyCollection<T> is the
standard way to wrap a collection and make available only a read-only version
of the collection's contents. The collection lives in the
System.Collections.ObjectModel namespace. Once we update our Order class to
expose this collection as the type of its OrderHistory property, our
SneakyOrderHistoryCannotBeCleared() test no longer compiles, since it's not
possible to cast a ReadOnlyCollection<T> to a List<T>. The final
version of the Customer class looks like this:
|
Summary |
Encapsulation of object state is fundamental to proper
object oriented programming and design. C# makes it very easy to create
properties and to work with strongly typed collections of objects, and often
this results in object models that expose too much functionality when it comes
to collections. By using interfaces or wrapper classes like the
ReadOnlyCollection<T>, we can ensure our classes' internal state remains
safe from calling code that might inadvertently introduce bugs by changing it
inappropriately.
|
|
|
User Comments
Title:
Picture links all broken (404)
Name:
Anonymous
Date:
2012-12-12 8:46:55 AM
Comment:
The images are blank and result in a 404 if opened individually.
|
Title:
Very nice
Name:
Thanigainathan
Date:
2011-03-28 2:36:03 PM
Comment:
Hi,
The article is very nice. So you mean only the parent of the list property can modify its state. Will this be sort of restricting the features of collections ?
Thanks, Thani
|
|
|
|