Did your parents ever tell you "don't talk to strangers?" They were trying to protect you, and even though you are all grown up now, that principle is still relevant--to the design of your software, that is. The basic idea is that classes should limit their surface area and not expose that which reveals implementation details. Let's go though an example to illustrate these concepts.
In a traditional layered architecture you logically divide components by responsibility. In this example, we will use a domain, business and data access logic layer each realized as a Visual Studio 2008 project. In our contrived architecture, we want the business layer to be the "gatekeeper" for retrieving customer domain objects. The business layer uses the data access logic layer to fetch a customer, so we might expose the data access class as a property of the business class as demonstrated here:
Here's the code to use this design:
So what's wrong with this approach? We have violated a core principal of object oriented design by exposing implementation details. Just like your parents protected you from strangers, classes should not expose their children or any other details about implementation. You may have heard of this concept by another name such as:
Consumers don't need to know how the CustomerBusiness fetches a Customer, it only needs to know that CustomerBusiness fetches a customer. So, let's revise the design using a technique called interface promotion. This involves hiding a child object and promoting all or some of its interface to the parent. The new design looks like this:
Here's the code to use the new design:
The responsibility of the business layer is to act as a "gatekeeper" and control access to the underlying layers. Although it might seem that we have accomplished our goal with this design, there is another issue that needs to be addressed. Remember the layered architecture? Business logic is in one project/assembly and data access logic is in another. In order for CustomerBusiness to use CustomerData, the methods in CustomerData must be public. The implication of this is a consumer can reference the data access layer and call it's public methods, bypassing any logic in the business layer. To solve this problem, I would like to introduce a technique that I call only talk to friends.
The intent of the only talk to friends pattern is provide a mechanism for an assembly to expose it's interface to specified friend assemblies. In our example, the data access logic layer would only expose it's interface to the business layer. In order to accomplish this, let's revisit the design and make the CustomerData class and its methods internal:
Now for the magic. Add the InternalsVisibleTo attribute to the AssemblyInfo.cs file in the Data project thereby exposing anything internal in the data access logic layer to the business layer.
The InternalsVisibleTo attribute exposes anything internal in one assembly to the specified friend assembly. It would be nice if we could be more granular than the entire assembly, but this approach will work well for most circumstances.
There is one exception for breaking the "do not talk to strangers" principle, and that is unit testing. When we write the unit test for CustomerBusiness, we only want to test CustomerBusiness, not CustomerData or the database. In order to do this, we mock the CustomerData class. For this work, CustomerBusiness will need to provide a mechanism that allows the unit test to replace the CustomerData instance in CustomerBusiness. There are a few techniques for doing this such as dependency injection (and I do have an article planned on this subject), but one way is to allow CustomerData to be passed into the CustomerBusiness constructor as follows.
Note that the constructor that allows the data access object to be specified is internal. Once again, you use the InternalsVisibleTo attribute granting access only to the business layer unit tests.
I hope this article has reinforced the object-oriented principle "don't talk to strangers," and provided a modern spin, "only talk to friends," that you can add to your toolbox.