We use cookies to enhance your experience on the site
CodeWorlds

Encapsulation and Private Fields (#)

In our Jurassic Park, dinosaur data must be strictly protected. Not every employee should have direct access to DNA sequences or enclosure security codes. Encapsulation is a fundamental principle of object-oriented programming that allows us to hide internal implementation details and expose only a controlled interface. In JavaScript, from ES2022, we have true private fields denoted by the

#
symbol.

What is encapsulation?

Encapsulation is a principle based on hiding the internal state of an object and exposing it exclusively through defined methods. Thanks to this:

  • We protect data from unauthorized modification
  • We control access to internal properties
  • We can validate data before saving it
  • We simplify code maintenance — internal implementation can change without affecting the rest of the program

Private fields with

In JavaScript we declare private fields by adding

#
before the field name. Such a field is accessible exclusively inside the class where it was declared:

1class Dinosaur {
2  // Private fields — inaccessible from outside
3  #dnaSequence;
4  #healthStatus;
5  #dangerLevel;
6
7  // Public fields — accessible from outside
8  species;
9  name;
10
11  constructor(species, name, dnaSequence, dangerLevel) {
12    this.species = species;
13    this.name = name;
14    this.#dnaSequence = dnaSequence;
15    this.#healthStatus = 100;
16    this.#dangerLevel = dangerLevel;
17  }
18
19  // Public method — controlled access to data
20  getStatus() {
21    return `${this.name} (${this.species}): health ${this.#healthStatus}%, danger ${this.#dangerLevel}/5`;
22  }
23}
24
25const rex = new Dinosaur("Tyrannosaurus Rex", "Rexy", "ATCG-GCTA-TTAA", 5);
26
27console.log(rex.species);    // "Tyrannosaurus Rex" — public field
28console.log(rex.name);       // "Rexy" — public field
29console.log(rex.getStatus()); // Works — public method
30
31// console.log(rex.#dnaSequence); // SyntaxError! Private field
32// console.log(rex.#healthStatus); // SyntaxError! Private field

Attempting to access the

#dnaSequence
field from outside the class will cause a syntax error. This is true privacy — not just a convention.

Getters and setters — controlled access

To allow reading or modifying private fields, we use getters (

get
) and setters (
set
). A setter allows data validation before saving:

1class DinosaurEnclosure {
2  #maxCapacity;
3  #currentDinosaurs;
4  #securityCode;
5
6  constructor(name, maxCapacity, securityCode) {
7    this.name = name;
8    this.#maxCapacity = maxCapacity;
9    this.#currentDinosaurs = [];
10    this.#securityCode = securityCode;
11  }
12
13  // Getter — read capacity
14  get capacity() {
15    return this.#maxCapacity;
16  }
17
18  // Getter — number of dinosaurs
19  get dinosaurCount() {
20    return this.#currentDinosaurs.length;
21  }
22
23  // Getter — list of names (a copy, not a reference!)
24  get dinosaurs() {
25    return [...this.#currentDinosaurs];
26  }
27
28  // Setter with validation
29  set maxCapacity(value) {
30    if (value < this.#currentDinosaurs.length) {
31      throw new Error("Cannot reduce capacity below current number of dinosaurs!");
32    }
33    if (value <= 0) {
34      throw new Error("Capacity must be greater than zero!");
35    }
36    this.#maxCapacity = value;
37  }
38
39  // Public method with validation
40  addDinosaur(dinoName) {
41    if (this.#currentDinosaurs.length >= this.#maxCapacity) {
42      return `Enclosure ${this.name} is full! Maximum capacity: ${this.#maxCapacity}`;
43    }
44    this.#currentDinosaurs.push(dinoName);
45    return `${dinoName} added to enclosure ${this.name}`;
46  }
47
48  // Method requiring authorization
49  getSecurityCode(authToken) {
50    if (authToken !== "PARK-ADMIN-2024") {
51      return "No access! Administrator authorization required.";
52    }
53    return this.#securityCode;
54  }
55}
56
57const enclosure = new DinosaurEnclosure("Raptor Pen", 4, "SEC-7742");
58
59console.log(enclosure.name);          // "Raptor Pen"
60console.log(enclosure.capacity);      // 4 — getter
61console.log(enclosure.addDinosaur("Blue"));    // "Blue added..."
62console.log(enclosure.addDinosaur("Charlie")); // "Charlie added..."
63console.log(enclosure.dinosaurCount); // 2
64
65// Getter returns a copy — modification doesn't affect the original
66const dinos = enclosure.dinosaurs;
67dinos.push("Hacker-Dino");
68console.log(enclosure.dinosaurCount); // Still 2!
69
70// Validation in setter
71enclosure.maxCapacity = 6; // OK
72// enclosure.maxCapacity = 0; // Error! Capacity must be > 0
73
74// Access control for sensitive data
75console.log(enclosure.getSecurityCode("wrong-token"));     // "No access!"
76console.log(enclosure.getSecurityCode("PARK-ADMIN-2024")); // "SEC-7742"

Private methods

Just like fields, methods can also be private. Private methods serve as internal helper mechanisms of the class:

1class DNAAnalyzer {
2  #samples;
3  #analysisLog;
4
5  constructor() {
6    this.#samples = [];
7    this.#analysisLog = [];
8  }
9
10  // Private method — sample validation
11  #validateSample(sample) {
12    const validBases = ["A", "T", "C", "G"];
13    return sample.split("").every(base => validBases.includes(base));
14  }
15
16  // Private method — operation logging
17  #log(message) {
18    this.#analysisLog.push({
19      timestamp: new Date().toISOString(),
20      message
21    });
22  }
23
24  // Public method using private ones
25  addSample(species, dnaSequence) {
26    if (!this.#validateSample(dnaSequence)) {
27      this.#log(`Rejected DNA sample for ${species}: invalid sequence`);
28      return "Error: Invalid DNA sequence!";
29    }
30
31    this.#samples.push({ species, dna: dnaSequence });
32    this.#log(`Added DNA sample for ${species}`);
33    return `DNA sample for ${species} registered successfully`;
34  }
35
36  // Public access to results
37  getSampleCount() {
38    return this.#samples.length;
39  }
40
41  getLog() {
42    return [...this.#analysisLog]; // Copy of the log
43  }
44}
45
46const analyzer = new DNAAnalyzer();
47console.log(analyzer.addSample("T-Rex", "ATCGATCG"));    // Registered
48console.log(analyzer.addSample("Raptor", "ATCGXYZ"));     // Error!
49console.log(analyzer.getSampleCount()); // 1
50console.log(analyzer.getLog());         // Operation history
51
52// analyzer.#validateSample("ATCG"); // SyntaxError! Private method

Why is encapsulation important?

Imagine that without encapsulation someone could directly modify the state of the park's security system:

1// WITHOUT encapsulation — dangerous!
2class UnsafePark {
3  fences = true;
4  electricPower = true;
5  dinosaurCount = 15;
6}
7
8const park = new UnsafePark();
9park.fences = false;       // Someone turned off the fences!
10park.dinosaurCount = -5;   // Nonsensical value
11// No validation, no control — catastrophe!
12
13// WITH ENCAPSULATION — safe!
14class SafePark {
15  #fencesActive;
16  #electricPower;
17  #dinosaurCount;
18
19  constructor(dinoCount) {
20    this.#fencesActive = true;
21    this.#electricPower = true;
22    this.#dinosaurCount = dinoCount;
23  }
24
25  deactivateFences(authCode) {
26    if (authCode !== "EMERGENCY-OVERRIDE") {
27      return "Denied! Disabling fences requires an emergency code.";
28    }
29    this.#fencesActive = false;
30    return "WARNING: Fences deactivated!";
31  }
32
33  get status() {
34    return {
35      fences: this.#fencesActive ? "ACTIVE" : "INACTIVE",
36      power: this.#electricPower ? "ON" : "OFF",
37      dinosaurs: this.#dinosaurCount
38    };
39  }
40}
41
42const safePark = new SafePark(15);
43console.log(safePark.deactivateFences("wrong")); // Denied!
44console.log(safePark.status); // Everything safe

Encapsulation in inheritance

Important note: private fields with

#
are not accessible in child classes. If a child class needs access to the parent's data, the parent must expose it through public methods or getters:

1class Animal {
2  #name;
3  #health;
4
5  constructor(name, health) {
6    this.#name = name;
7    this.#health = health;
8  }
9
10  // Getter exposes value to child classes
11  get name() {
12    return this.#name;
13  }
14
15  get health() {
16    return this.#health;
17  }
18
19  // Protected method (convention _) allowing modification of health
20  _setHealth(value) {
21    if (value < 0) value = 0;
22    if (value > 100) value = 100;
23    this.#health = value;
24  }
25}
26
27class ParkDinosaur extends Animal {
28  #species;
29
30  constructor(name, species, health) {
31    super(name, health);
32    this.#species = species;
33  }
34
35  feed() {
36    // Using public getters and parent methods
37    const newHealth = Math.min(this.health + 10, 100);
38    this._setHealth(newHealth);
39    return `${this.name} (${this.#species}) fed. Health: ${this.health}%`;
40  }
41}
42
43const dino = new ParkDinosaur("Rexy", "T-Rex", 70);
44console.log(dino.feed()); // "Rexy (T-Rex) fed. Health: 80%"

Summary

Encapsulation with private fields

#
is a powerful tool that allows:

  1. Hiding implementation — internal fields and methods are inaccessible from outside
  2. Data validation — setters can check correctness before saving
  3. Access control — sensitive operations require authorization
  4. Safe getters — returning copies instead of references to internal data
  5. Private methods — helper functions hidden from the class user

In Jurassic Park, encapsulation protects our systems just as fences protect visitors from dinosaurs — controlled, secure access instead of chaos.

Go to CodeWorlds