The encapsulation principle advocates separation of concerns and information hiding by hiding abstract implementation details and changes.

Take cars for example. We don’t need to know how an engine works to drive a car. This describes exactly what the encapsulation principle does: the user does not need to know the details of the abstraction (the car), and encapsulation allows the abstraction to hide changes to the implementation details. It doesn’t matter if the engine is gasoline or diesel.

The implementation of encapsulation principle

  • Hiding implementation details

    The abstraction exposes only the functionality it provides to the client program and hides the implementation. The implementation approach (that is, the implementation details) contains the internal representations of the abstraction (such as the data members and data structures used by the abstraction) and details about how the method is implemented (such as the algorithm used by the method).

  • Hidden changes

    Hide implementation changes to types or implementation structures. By hiding the changes, it is easier to modify the abstract implementation without too much impact on the client program.

Bad taste caused by violation of packaging principles

In this blog post, we will focus on the analysis of inadequate encapsulated bad taste. We will analyze other encapsulated bad taste in a later blog post.

Inadequate encapsulation

This bad taste results when one or more members of an abstraction are declared with more access than is actually required. In the extreme form of this bad taste, there are global states represented by global variables, global data structures, and so on that are accessible to all abstractions throughout the software system.

Why sufficient encapsulation?

The main intent of encapsulation is to separate the interface from the implementation so that they can be modified almost independently. This separation of concerns allows client programs to rely only on abstract interfaces, thereby hiding implementation details from them. Exposing implementation details results in tight coupling between the abstraction and the client. This is not desirable and will affect the client program every time the abstract implementation details are changed. Providing more access than needed may expose implementation details to the client program, which violates the “hiding principle.”

Potential causes of inadequate encapsulation

For testing purposes

To facilitate testing, developers often change abstract private methods into public ones. Because private methods involve the implementation details of abstractions, making them public breaks the encapsulation of the abstractions.

We all know that testability is an important measure of code quality. If you write code that cannot be unit tested, the quality of your code cannot be guaranteed. In some cases, code changes can be made if the code cannot write tests, which is called refactoring. But because access rights change code in these cases, doing so breaks the code’s encapsulation. You can use reflection to test low-access members.

Adopt process thinking in object oriented programming

Exposing the data that multiple abstractions need to use as global variables leads to this bad taste.

The sample analysis

// </summary> // </summary> public class Publisher {/// </summary> // channel number range 1-100 /// </summary> public int channel; /// <summary> // create a publisher object for a specific channel /// </summary> /// <param name="channel"</param> public Publisher(int channel) {this.channel = channel; Public vois Publish(string message) {public vois Publish(string message) {Copy the code

The above code example is a good example of inadequate encapsulation. Setting the channel number variable to public is not appropriate because the channel number was specified when the message publication object was created. The channel is set to public, and the channel number can be accessed and modified at will when the client uses it. This makes the client aware of the internal implementation of the message publishing class, creating direct dependencies that violate the “high cohesion, low coupling” principle. This affects the client every time the internal implementation is changed. More importantly, the channel number variable channel has a limited range (1-100), and the client may modify the channel arbitrarily when using it, which may cause the error that the channel is out of bounds. So the right thing to do is to make the channel variable private and provide it with the appropriate accessor methods.

The refactored code implementation:

// <summary> // </summary> public class Publisher {/// <summary> /// channel number range 1-100 /// </summary> private int  channel; /// <summary> /// channel assignment, range limited /// </summary> /// <param name="channel"</param> public void SetChannel(int channel) {if(channel < 1 || channel > 100)
        {
            throw new ArgumentOutOfRangeException("Beyond channel number range 1-100"); } this.channel = channel; } / / / < summary > / / / create a specific channel publisher object / / / < summary > / / / < param name ="channel"</param> public Publisher(int channel) {SetChannel(channel); Public vois Publish(string message) {public vois Publish(string message) {Copy the code

There is another extreme form: global variables. There are two different scenarios for global variables.

  • Make one or more members globally visible, but only a small number of classes access them.
  • Make one or more members globally visible, and a large number of classes will access them.

In the first case, you can pass the necessary variables through parameters to do the refactoring.

In the second case, refactoring can be done by creating appropriate abstractions based on their responsibilities and encapsulating the original global variables in these abstractions so that the client will use these abstractions instead of using the global variables directly.

conclusion

  1. When there is an inadequate encapsulation bad taste, code is less reusable because the client relies directly on state that everyone can access, making it difficult to reuse the client elsewhere.

  2. When an abstraction allows direct access to its data members, the responsibility for ensuring the integrity of the data and the overall abstraction is transferred from the abstraction to the individual clients. Increases the likelihood of problems occurring at runtime.

  3. The performance cost of using accessor methods is negligible compared to the benefits of using accessor methods to control changes to variable access.


Reference: Software Design Refactoring


                                                      —–END—–



If you like this article, please scan the picture below and watch more exciting content