Search⌘ K
AI Features

Encapsulation

Explore encapsulation in D programming to restrict access to struct and class members. Understand how it prevents invalid states and maintains consistency by controlling access through defined interfaces, ensuring safer and more maintainable code structures.

We'll cover the following...

Introduction

All of the structs and classes we have defined so far have been accessible from the outside (main() and other functions in the program).

Let’s consider the following struct:

enum Gender { female, male }

struct Student {
    string name;
    Gender gender;
}

The members of that struct are freely accessible to the rest of the program:

D
import std.stdio;
enum Gender { female, male }
struct Student {
string name;
Gender gender;
this (string name,Gender gender) {
this.name = name;
this.gender = gender;
}
}
void main() {
auto student = Student("Tim", Gender.male);
writefln("%s is a %s student.", student.name, student.gender);
}

Such freedom is a convenience in programs. For example, the previous line was useful for producing the following output:

Tim is a male student.

However, this freedom is also a liability. As an example, let’s assume that, perhaps by mistake, the name of a student object gets modified in the program:

student.name = "Anna";
D
import std.stdio;
enum Gender { female, male }
struct Student {
string name;
Gender gender;
this (string name,Gender gender) {
this.name = name;
this.gender = gender;
}
}
void main() {
auto student = Student("Tim", Gender.male);
student.name = "Anna";
writefln("%s is a %s student.", student.name, student.gender);
}

That assignment may put the object in an invalid state (Line # 17):

Anna is a male student.

As another example, let’s consider a School class. Let’s assume that this class has two member variables that store the numbers of the male and female students separately. The add() member function adds students while ensuring that the counts are always correct:

D
import std.stdio;
import std.format;
enum Gender { female, male }
struct Student {
string name;
Gender gender;
this (string name,Gender gender) {
this.name = name;
this.gender = gender;
}
}
class School {
Student[] students;
size_t femaleCount;
size_t maleCount;
void add(Student student) {
students ~= student;
final switch (student.gender) {
case Gender.female: ++femaleCount;
break;
case Gender.male: ++maleCount;
break;
}
}
override string toString() const {
return format("%s female, %s male; total %s students",
femaleCount, maleCount, students.length);
}
}
void main() {
auto school = new School;
school.add(Student("Lindsey", Gender.female));
school.add(Student("Mark", Gender.male));
writeln(school);
}

However, being able to access the members of School freely would not guarantee that this consistency would always be maintained. Let’s consider adding a new element to the students member, this time, directly without using the add() function

school.students ~= Student("Nancy", Gender.female);
D
import std.stdio;
import std.format;
enum Gender { female, male }
struct Student {
string name;
Gender gender;
this (string name,Gender gender) {
this.name = name;
this.gender = gender;
}
}
class School {
Student[] students;
size_t femaleCount;
size_t maleCount;
void add(Student student) {
students ~= student;
final switch (student.gender) {
case Gender.female: ++femaleCount;
break;
case Gender.male: ++maleCount;
break;
}
}
override string toString() const {
return format("%s female, %s male; total %s students",
femaleCount, maleCount, students.length);
}
}
void main() {
auto school = new School;
school.add(Student("Lindsey", Gender.female));
school.add(Student("Mark", Gender.male));
school.students ~= Student("Nancy", Gender.female);
writeln(school);
}

Because the new student has been added to the array directly, without going through the add() member function, the School object is now in an inconsistent state:

1 female, 1 male; total 3 students

Encapsulation

Encapsulation is a programming concept that restricts access to members to avoid problems similar to the one above.

Another benefit of encapsulation is that it eliminates the need to know the implementation details of types. In a sense, encapsulation allows presenting a type as a black box that is used only through its interface.

Additionally, preventing users from accessing the members directly allows changing the member functions of a class freely in the future. As long as the functions that define the interface of a class are kept the same, its implementation can be changed freely.

Encapsulation is not for restricting access to sensitive data like a credit card number or a password, and it cannot be used for that purpose. Encapsulation is a development tool; it allows using and coding types easily and safely.