C# Coding Solutions—Dont Expose a Class Internal State
Microsoft .NET Framework, ASP.NET, Visual C# (CSharp, C Sharp, C-Sharp) Developer Training, Visual Studio
Don’t Expose a Class’s Internal State
Classical object-oriented programming says that you should never expose the internal state of your class. Object-oriented programming dictates that the properties or methods exposed are based on application logic. A pragmatic reason for not to exposing the internal workings of a class is to decouple the class definition from the internal structure.
Let’s consider oven temperature. A class exposes its internal state, and is progressively converted to not exposing the details. If we wanted to monitor an oven’s temperature, we would create a property, as in the following source code:
class Oven { private int _temperature; public int Temperature { get { return _temperature; } set { _temperature = value; } } }
The type Oven exposes its temperature as a property, which is a direct reference to the variable _temperature. This means that the internal oven temperature implementation is tied directly to its external interface. The point of good object-oriented programming is to avoid this sort of programming.
To get to a better implementation, we should ask ourselves, what is the operation of an oven? An oven is used to bake something at a constant temperature. Hence an improved method and property interface would expose the oven as a device that bakes something. A better oven implementation would be as follows:
class Oven { private int _temperature; public void SetTemperature( int temperature) { _temperature = temperature; } public bool AreYouPreHeated() { return false; } }
In this implementation of Oven the internal variable _temperature is not tied to its external interface. The property Temperature is replaced with the methods SetTemperature used to assign the temperature, and AreYouPreHeated to indicate that the assigned temperature has been reached. This is a good object-oriented design, as you are telling the oven to take care of its own responsibilities and you are exposing only the logic of the oven.
The new oven implementation has stopped exposing its internal state. However, a client might want the internal information. For example, imagine an oven implementation that needs to poll the oven temperature for monitoring purposes. The new oven implementation could not deliver that information.
Nonetheless, we do not have to use the original implementation—the requested logic is to poll for the temperature at regular intervals. To implement this logic we can use a delegate and receive an asynchronous call whenever a new temperature has been polled. The advantage of this approach is that the oven is in charge of sending the information and could potentially send out alerts when there are dramatic changes in temperature. The following is the rewritten source code:
delegate void OnTemperature( int temperature); class Oven { private int _temperature; OnTemperature _listeners; public void BroadcastTemperature() { _listeners( _temperature); } public void AddTemperatureListener( OnTemperature listener) { _listeners += listener; } } class Controller { public Controller( Oven oven) { oven.AddTemperatureListener( new OnTemperature( this.OnTemperature)); } public void OnTemperature( int temperature) { Console.WriteLine( "Temperature (" + temperature + ")"); } }
Using delegates, types external to the class Oven listen to the oven temperature. The object-oriented design rule where a type is responsible for its own data is not violated. The delegate OnTemperature is used to broadcast the oven’s temperature to all listeners who want to know. The method AddTemperatureListener adds a delegate instance to the list of listeners. The method BroadcastTemperature broadcasts the temperature.
It is important to realize that the BroadcastTemperature method is optional and is an implementation detail. Maybe the class Oven executes using its own thread and broadcasts a temperature periodically. Maybe another class is responsible for polling the oven. In any case, the class Controller uses the Oven.AddTemperatureListener method to listen to the oven temperature.
The solution of using a delegate is elegant because the class Oven is loosely coupled with Controller and does not require the Controller to know about the implementation of the Oven class. In this example it was not necessary to use a property even though it would have been easier, as such use would violate good object-oriented design principles. However, the example ignores where the temperature came from in the first place. Think about where, when, and how Oven assigns the variable _temperature. Where does this temperature come from? It has not been defined, and without it our application will not work.
The Oven class has to get its temperature from somewhere, and most likely from a device driver that interfaces with the physical oven. In most cases that temperature is a property retrieved by the Oven class using a polling mechanism. In other words, the Oven class is hiding the fact that somewhere in the types used by Oven there exists a property called temperature. Even if the device driver used delegates, then in the implementation of the device driver a type has to be defined with a property called current temperature.
When writing types and exposing internal states, think about the following points:
- Expose properties only when the properties are fundamental types.
- Don’t expose an internal state as a property, but expose application logic that represents the property.
- When properties are exposed they should be exposed as data types, not value types. For example, instead of exposing temperature as a double value, expose temperature as the class
Temperature. That way you can avoid problems such as not knowing whether the temperature is in degrees Celsius, Fahrenheit, or Kelvin.
- When properties are exposed they should be exposed as data types, not value types. For example, instead of exposing temperature as a double value, expose temperature as the class
|

