Encapsulation and Protection Attributes - D Programming Language

Menu

- Learn
- Documentation
  - Language Reference
  - Library Reference
  - Command-line Reference
  - Feature Overview
  - Articles
- Downloads
- Packages
- Community
  - Blog
  - Orgs using D
  - Twitter
  - Calendar
  - Discord (community-run)
  - Forums
  - IRC
  - Wiki
  - Issues
  - Contributing
  - Contributors
  - Foundation
  - Security Team
  - Donate
  - Sponsors
- Resources
  - Tour
  - Books
  - Tutorials
  - Tools
  - Editors
  - IDEs
  - run.dlang.io
  - Visual D
  - Acknowledgments
  - D Style
  - Glossary
  - Sitemap

Search
Report a bug
If you spot a problem with this page, click here to create a Bugzilla issue.
Improve this page
Quickly fork, edit online, and submit a pull request for this page. Requires a signed-in GitHub account. This works well for small changes. If you'd like to make larger changes you may want to consider using a local clone.

# Encapsulation and Protection Attributes

All of the structs and classes that we have defined so far have all been accessible from the outside.

Let's consider the following struct:

    enum Gender { female, male }

    struct Student {
        string name;
        Gender gender;
    }

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

        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 has been useful to produce 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";

That assignment may put the object in an invalid state:

    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:

    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);
        }
    }

The `add()` member function adds students while ensuring that the counts are always correct:

        auto school = new School;
        school.add(Student("Lindsey", Gender.female));
        school.add(Student("Mark", Gender.male));
        writeln(school);

The program produces the following consistent output:

    1 female, 1 male; total 2 students

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:

        school.students ~= Student("Nancy", Gender.female);

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 of restricting access to members to avoid problems similar to the one above.

Another benefit of encapsulation is to eliminate 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 members of a class freely in the future. As long as the functions that define the interface of a class is 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.

##### Protection attributes

Protection attributes limit access to members of structs, classes, and modules. There are two ways of specifying protection attributes:

- At struct or class level to specify the protection of every struct or class member individually.
- At module level to specify the protection of every feature of a module individually: class, struct, function, enum, etc.

Protection attributes can be specified by the following keywords. The default attribute is `public`.

- `public`: Specifies accessibility by any part of the program without any restriction.

  An example of this is `stdout`. Merely importing `std.stdio` makes `stdout` available to every module that imported it.

- `private`: Specifies restricted accessibility.

  `private` class members and module members can only be accessed by the module that defines that member.

  Additionally, `private` member functions cannot be overridden by subclasses.

- `package`: Specifies package-level accessibility.

  A feature that is marked as `package` can be accessed by all of the code that is a part of the same package. The `package` attribute involves only the inner-most package.

  For example, a `package` definition that is inside the `animal.vertebrate.cat` module can be accessed by any other module of the `vertebrate` package.

  Similar to the `private` attribute, `package` member functions cannot be overridden by subclasses.

- `protected`: Specifies accessibility by derived classes.

  This attribute extends the `private` attribute: A `protected` member can be accessed not only by the module that defines it, but also by the classes that inherit from the class that defines that `protected` member.

Additionally, the `export` attribute specifies accessibility from the outside of the program.

##### Definition

Protection attributes can be specified in three ways.

When written in front of a single definition, it specifies the protection attribute of that definition only. This is similar to the Java programming language:

    private int foo;

    private void bar() {
        // ...
    }

When specified by a colon, it specifies the protection attributes of all of the following definitions until the next specification of a protection attribute. This is similar to the C++ programming language:

    private:
        // ...
        // ... all of the definitions here are private ...
        // ...

    protected:
        // ...
        // ... all of the definitions here are protected ...
        // ...

When specified for a block, the protection attribute is for all of the definitions that are inside that block:

    private {
        // ...
        // ... all of the definitions here are private ...
        // ...
    }

##### Module imports are private by default

A module that is imported by `import` is private to the module that imports it. It would not be visible to other modules that import it indirectly. For example, if a `school` module imports `std.stdio`, modules that import `school` cannot automatically use the `std.stdio` module.

Let's assume that the `school` module starts by the following lines:

    module school.school;

    import std.stdio;    // imported for this module's own use...

    // ...

The following program cannot be compiled because `writeln` is not visible to it:

    import school.school;

    void main() {
        writeln("hello");    // ← derleme HATASI
    }

`std.stdio` must be imported by that module as well:

    import school.school;
    import std.stdio;

    void main() {
        writeln("hello");   // now compiles
    }

Sometimes it is desired that a module presents other modules indirectly. For example, it would make sense for a `school` module to automatically import a `student` module for its users. This is achieved by marking the `import` as `public`:

    module school.school;

    public import school.student;

    // ...

With that definition, modules that import `school` can use the definitions that are inside the `student` module without needing to import it:

    import school.school;

    void main() {
        auto student = Student("Tim", Gender.male);

        // ...
    }

Although the program above imports only the `school` module, the `student.Student` struct is also available to it.

##### When to use encapsulation

Encapsulation avoids problems similar to the one we have seen in the introduction section of this chapter. It is an invaluable tool to ensure that objects are always in consistent states. Encapsulation helps preserve struct and class invariants by protecting members from direct modifications by the users of the type.

Encapsulation provides freedom of implementation by abstracting implementations away from user code. Otherwise, if users had direct access to for example `School.students`, it would be hard to modify the design of the class by changing that array e.g. to an associative array, because this would affect all user code that has been accessing that member.

Encapsulation is one of the most powerful benefits of object oriented programming.

##### Example

Let's define the `Student` struct and the `School` class by taking advantage of encapsulation and let's use them in a short test program.

This example program will consist of three files. As you remember from the previous chapter, being parts of the `school` package, two of these files will be under the "school" directory:

- "school/student.d": The `student` module that defines the `Student` struct
- "school/school.d": The `school` module that defines the `School` class
- "deneme.d": A short test program

Here is the "school/student.d" file:

    module school.student;

    import std.string;
    import std.conv;

    enum Gender { female, male }

    struct Student {
        package string name;
        package Gender gender;

        string toString() const {
            return format("%s is a %s student.",
                          name, to!string(gender));
        }
    }

The members of this struct are marked as `package` to enable access only to modules of the same package. We will soon see that `School` will be accessing these members directly. (Note that even this should be considered as violating the principle of encapsulation. Still, let's stick with the `package` attribute in this example program.)

The following is the "school/school.d" module that makes use of the previous one:

    module school.school;

    public import school.student;                  // 1

    import std.string;

    class School {
    private:                                       // 2

        Student[] students;
        size_t femaleCount;
        size_t maleCount;

    public:                                        // 3

        void add(Student student) {
            students ~= student;

            final switch (student.gender) {        // 4a

            case Gender.female:
                ++femaleCount;
                break;

            case Gender.male:
                ++maleCount;
                break;
            }
        }

        override string toString() const {
            string result = format(
                "%s female, %s male; total %s students",
                femaleCount, maleCount, students.length);

            foreach (i, student; students) {
                result ~= (i == 0) ? ": " : ", ";
                result ~= student.name;            // 4b
            }

            return result;
        }
    }

1. `school.student` is being imported publicly so that the users of `school.school` will not need to import that module explicitly. In a sense, the `student` module is made available by the `school` module.
2. All of the member variables of `School` are marked as private. This is important to help protect the consistency of the member variables of this class.
3. For this class to be useful, it must present some member functions. `add()` and `toString()` are made available to the users of this class.
4. As the two member variables of `Student` have been marked as `package`, being a part of the same package, `School` can access those variables.

Finally, the following is a test program that uses those types:

    import std.stdio;
    import school.school;

    void main() {
        auto student = Student("Tim", Gender.male);
        writeln(student);

        auto school = new School;

        school.add(Student("Lindsey", Gender.female));
        school.add(Student("Mark", Gender.male));
        school.add(Student("Nancy", Gender.female));

        writeln(school);
    }

This program can use `Student` and `School` only through their public interfaces. It cannot access the member variables of those types. As a result, the objects would always be consistent:

    Tim is a male student.
    2 female, 1 male; total 3 students: Lindsey, Mark, Nancy

Note that the program interacts with `School` only by its `add()` and `toString()` functions. As long as the interfaces of these functions are kept the same, changes in their implementations would not affect the program above.

Copyright © 1999-2026 by the D Language Foundation | Page generated by Ddoc on Wed Aug 5 15:38:51 2026