JavaScript and TypeScript course Β· Module 7: Advanced TypeScript
Access Modifiers (public, private, protected)
In this lesson9
"Security in Jurassic Park is a matter of the highest importance," states John Hammond, president of InGen, firmly. "Some data and procedures must be accessible to all employees, others only to those with appropriate clearances, and still others are so critical that only top management has access to them."
Just like in Jurassic Park, in object-oriented programming, controlling access to class properties and methods is a fundamental aspect of good design. TypeScript offers three access modifiers that allow you to precisely define who can use specific class elements and to what extent: public, private, and protected.
Introduction to Access Modifiers
Access modifiers determine from which levels of code you can access a class's properties and methods:
- public - accessible from anywhere in the code (default)
- private - accessible only within the class itself
- protected - accessible within the class and inheriting classes
Let's see how these modifiers work in practice by implementing a dinosaur management system in Jurassic Park.
The public Modifier
Elements marked as public are accessible from anywhere in the code - both inside the class and on its instances. This is the default access modifier in TypeScript - if you don't specify any modifier, the element will automatically be marked as public.
1class Dinosaur {
2 // Public properties
3 public nazwa: string;
4 public gatunek: string;
5 public wiek: number;
6
7 constructor(nazwa: string, gatunek: string, wiek: number) {
8 this.nazwa = nazwa;
9 this.gatunek = gatunek;
10 this.wiek = wiek;
11 }
12
13 // Public method
14 public displayInfo(): void {
15 console.log(`${this.nazwa}: ${this.gatunek}, ${this.wiek} years old`);
16 }
17}
18
19// Creating an instance
20const rex = new ParkDinosaur("Rex", "Tyrannosaurus", 7);
21
22// Accessing public properties and methods from outside the class
23console.log(rex.nazwa); // "Rex" - access to public property
24rex.wiek = 8; // Modifying a public property
25rex.displayInfo(); // Calling a public methodPublic properties and methods are like publicly available information in the park - for example, the park map, opening hours, or restroom locations. Everyone can use them without restrictions.
The private Modifier
Elements marked as private are accessible only within the class where they were defined. This encapsulation allows you to hide internal implementation details and prevents accidental state modifications from outside the class.
1class HealthDiagnosis {
2 // Private properties
3 private id: string;
4 private testResults: Record<string, number>;
5 private notes: string;
6 public healthStatus: "healthy" | "sick" | "critical";
7
8 constructor(id: string) {
9 this.id = id;
10 this.testResults = {};
11 this.notes = "";
12 this.healthStatus = "healthy";
13 }
14
15 // Private method - used only inside the class
16 private analyzeResults(): "healthy" | "sick" | "critical" {
17 // Complex logic for analyzing test results
18 const bodyTemperature = this.testResults["bodyTemperature"] || 37;
19 const leukocyteLevel = this.testResults["leukocyteLevel"] || 7000;
20
21 if (bodyTemperature > 40 || leukocyteLevel > 15000) {
22 return "critical";
23 } else if (bodyTemperature > 38.5 || leukocyteLevel > 11000) {
24 return "sick";
25 } else {
26 return "healthy";
27 }
28 }
29
30 // Public methods using private properties
31 public addTestResult(name: string, value: number): void {
32 this.testResults[name] = value;
33 this.healthStatus = this.analyzeResults(); // Calling a private method
34 }
35
36 public addNote(note: string): void {
37 this.notes += note + "\n";
38 }
39
40 public getReport(): string {
41 return `
42 ID: ${this.id}
43 Health status: ${this.healthStatus}
44 Notes: ${this.notes || "None"}
45 `;
46 }
47}
48
49// Creating an instance
50const raptorDiagnosis = new HealthDiagnosis("VL-001");
51
52// Using the public API
53raptorDiagnosis.addTestResult("bodyTemperature", 39.2);
54raptorDiagnosis.addTestResult("leukocyteLevel", 12500);
55raptorDiagnosis.addNote("Symptoms of viral infection");
56
57console.log(raptorDiagnosis.healthStatus); // "sick" - access to public property
58console.log(raptorDiagnosis.getReport()); // Calling a public method
59
60// Attempting to access private elements
61// console.log(raptorDiagnosis.id); // Error: Property 'id' is private
62// console.log(raptorDiagnosis.testResults); // Error: Property 'testResults' is private
63// raptorDiagnosis.analyzeResults(); // Error: Property 'analyzeResults' is privatePrivate properties and methods are like restricted procedures in the genetics laboratory - only authorized laboratory personnel (class methods) have access to them, while for the rest of the staff they are hidden for security reasons.
The protected Modifier
Elements marked as protected are accessible within the class where they were defined and in inheriting classes. This allows creating class hierarchies where derived classes have access to protected elements of the base class, but these elements remain inaccessible from outside the class hierarchy.
1// Base class
2class Animal {
3 // Protected properties
4 protected id: string;
5 protected species: string;
6 protected weight: number;
7 protected health: number; // 0-100
8
9 constructor(id: string, species: string, weight: number) {
10 this.id = id;
11 this.species = species;
12 this.weight = weight;
13 this.health = 100;
14 }
15
16 // Protected method
17 protected modifyHealth(change: number): void {
18 this.health = Math.max(0, Math.min(100, this.health + change));
19 }
20
21 // Public method
22 public displayStatus(): string {
23 return `${this.species} (${this.id}): Weight: ${this.weight}kg, Health: ${this.health}%`;
24 }
25}
26
27// Inheriting class
28class Dinosaur extends Animal {
29 public name: string;
30 private aggressionLevel: number; // 0-10
31
32 constructor(id: string, species: string, name: string, weight: number) {
33 super(id, species, weight);
34 this.name = name;
35 this.aggressionLevel = 5;
36 }
37
38 // Method using protected elements from the base class
39 public feed(amount: number): void {
40 this.modifyHealth(amount * 5); // Access to protected method from base class
41 this.aggressionLevel = Math.max(0, this.aggressionLevel - 1);
42 console.log(`${this.name} has been fed. New health level: ${this.health}%`);
43 }
44
45 public takeDamage(amount: number): void {
46 this.modifyHealth(-amount); // Access to protected method from base class
47 this.aggressionLevel = Math.min(10, this.aggressionLevel + 2);
48 console.log(`${this.name} took damage. New health level: ${this.health}%`);
49 }
50
51 // Overriding base method using protected properties
52 public displayStatus(): string {
53 return `${this.name} (${this.species}): Weight: ${this.weight}kg, Health: ${this.health}%, Aggression: ${this.aggressionLevel}/10`;
54 }
55}
56
57// Inheriting class with modification of protected property
58class MarineDinosaur extends Animal {
59 private divingDepth: number;
60
61 constructor(id: string, species: string, weight: number, divingDepth: number) {
62 super(id, species, weight);
63 this.divingDepth = divingDepth;
64
65 // Modifying a protected property in the constructor
66 this.health = 90; // Marine dinosaurs start with lower health
67 }
68
69 public swim(time: number): void {
70 // Using a protected method
71 this.modifyHealth(time > 10 ? -10 : 5);
72
73 console.log(`Marine dinosaur ${this.id} swam for ${time} minutes at a depth of ${this.divingDepth}m`);
74 console.log(`New health level: ${this.health}%`);
75 }
76}
77
78// Creating instances
79const velociraptor = new Dinosaur("VL-002", "Velociraptor", "Blue", 150);
80const mosasaur = new MarineDinosaur("MS-001", "Mosasaurus", 15000, 50);
81
82// Using public methods
83velociraptor.feed(2);
84velociraptor.takeDamage(15);
85console.log(velociraptor.displayStatus());
86
87mosasaur.swim(15);
88console.log(mosasaur.displayStatus());
89
90// Attempting to access protected elements
91// console.log(velociraptor.id); // Error: Property 'id' is protected
92// console.log(mosasaur.health); // Error: Property 'health' is protected
93// velociraptor.modifyHealth(50); // Error: Property 'modifyHealth' is protectedProtected properties and methods are like the park's security procedures - only authorized employees and their subordinates (base class and inheriting classes) have access to them, but not the park's guests (external code).
Constructor and Access Modifiers
TypeScript offers a special syntax for constructors that allows you to declare properties directly with access modifiers in constructor parameters. This is a shorthand notation that avoids code repetition.
1// Traditional notation
2class ParkEmployee {
3 private id: string;
4 public firstName: string;
5 public lastName: string;
6 protected accessLevel: number;
7
8 constructor(id: string, firstName: string, lastName: string, accessLevel: number) {
9 this.id = id;
10 this.firstName = firstName;
11 this.lastName = lastName;
12 this.accessLevel = accessLevel;
13 }
14}
15
16// Shorthand notation with modifiers in constructor parameters
17class ParkEmployeeShorthand {
18 constructor(
19 private id: string,
20 public firstName: string,
21 public lastName: string,
22 protected accessLevel: number
23 ) {} // Empty constructor body - everything has already been initialized
24}
25
26// Both notations work identically
27const employee1 = new ParkEmployee("EMP-001", "John", "Hammond", 10);
28const employee2 = new ParkEmployeeShorthand("EMP-002", "Ellie", "Sattler", 8);
29
30console.log(employee1.firstName, employee1.lastName); // "John Hammond"
31console.log(employee2.firstName, employee2.lastName); // "Ellie Sattler"
32
33// console.log(employee1.id); // Error: Property 'id' is private
34// console.log(employee2.id); // Error: Property 'id' is privateThis shorthand syntax is especially useful in simple classes with many properties, as it reduces the amount of repetitive code.
Encapsulation and the Accessor Pattern (Getters and Setters)
One of the main reasons for using access modifiers is encapsulation - hiding implementation details and providing only a controlled interface for interacting with the class. Part of this approach is using getters and setters - special methods that control access to properties.
1class EnclosureSecurity {
2 // Private properties
3 private _fenceStatus: "active" | "inactive" | "under_repair";
4 private _batteryChargeLevel: number; // 0-100
5 private _lastInspection: Date;
6 private _eventLog: string[];
7
8 constructor() {
9 this._fenceStatus = "active";
10 this._batteryChargeLevel = 100;
11 this._lastInspection = new Date();
12 this._eventLog = ["System started: " + new Date().toISOString()];
13 }
14
15 // Getters - controlled read access to private properties
16 get fenceStatus(): "active" | "inactive" | "under_repair" {
17 this.addLog("Fence status read");
18 return this._fenceStatus;
19 }
20
21 get batteryChargeLevel(): number {
22 this.addLog("Battery charge level read");
23 return this._batteryChargeLevel;
24 }
25
26 get lastInspection(): Date {
27 return new Date(this._lastInspection.getTime()); // Defensive copy
28 }
29
30 get securityReport(): string {
31 this.addLog("Security report generated");
32 return `
33 Fence status: ${this._fenceStatus}
34 Battery charge level: ${this._batteryChargeLevel}%
35 Last inspection: ${this._lastInspection.toLocaleDateString()}
36 Number of events in log: ${this._eventLog.length}
37 `;
38 }
39
40 // Setters - controlled write access to private properties
41 set fenceStatus(newStatus: "active" | "inactive" | "under_repair") {
42 this.addLog(`Changed fence status from ${this._fenceStatus} to ${newStatus}`);
43 this._fenceStatus = newStatus;
44
45 if (newStatus === "inactive") {
46 this.startSecurityProcedures();
47 }
48 }
49
50 // This setter contains validation logic
51 set batteryChargeLevel(newLevel: number) {
52 if (newLevel < 0 || newLevel > 100) {
53 throw new Error("Battery charge level must be between 0 and 100");
54 }
55
56 const oldValue = this._batteryChargeLevel;
57 this._batteryChargeLevel = newLevel;
58 this.addLog(`Changed battery charge level from ${oldValue}% to ${newLevel}%`);
59
60 if (newLevel < 20) {
61 this.addLog("WARNING: Low battery level!");
62 }
63 }
64
65 // Private helper method
66 private addLog(message: string): void {
67 this._eventLog.push(`${new Date().toISOString()}: ${message}`);
68 }
69
70 // Private business method
71 private startSecurityProcedures(): void {
72 this.addLog("ALERT: Emergency procedures activated!");
73 // Emergency handling logic
74 }
75
76 // Public method
77 public performInspection(): void {
78 this._lastInspection = new Date();
79 this.addLog("Fence inspection performed");
80 this._batteryChargeLevel = 100;
81 this._fenceStatus = "active";
82 }
83
84 // Public method
85 public getEventLog(): string[] {
86 // We return a copy to prevent modification of the original log
87 return [...this._eventLog];
88 }
89}
90
91// Using the class
92const security = new EnclosureSecurity();
93
94// Using getters
95console.log("Fence status:", security.fenceStatus);
96console.log("Battery level:", security.batteryChargeLevel);
97
98// Using setters
99security.fenceStatus = "under_repair";
100security.batteryChargeLevel = 85;
101
102// Using public methods
103security.performInspection();
104
105// Attempting to set an invalid value
106try {
107 security.batteryChargeLevel = 110; // Will throw an error
108} catch (e) {
109 console.error(e.message);
110}
111
112// Reading the report and logs
113console.log(security.securityReport);
114console.log("Event history:");
115security.getEventLog().forEach((log, i) => {
116 console.log(`${i + 1}. ${log}`);
117});Using getters and setters provides greater control over data access:
- We can add validation logic before changing a value
- We can perform additional operations when accessing data
- We can return defensive copies of objects to prevent accidental modifications
- We can log data access
Practical Example: Dinosaur Management System
Below is a comprehensive example of a dinosaur management system that uses all the discussed access modifiers to create a well-organized object-oriented architecture:
1// Types and interfaces
2type DinosaurHealthStatus = "healthy" | "sick" | "in treatment" | "sedated" | "deceased";
3type DietType = "carnivore" | "herbivore" | "omnivore";
4
5interface IMonitoring {
6 startMonitoring(): void;
7 stopMonitoring(): void;
8 getMonitoringStatus(): string;
9}
10
11// Abstract base class
12abstract class ParkAnimal {
13 // Properties with different access modifiers
14 private _id: string;
15 protected _species: string;
16 protected _age: number;
17 protected _weight: number;
18 protected _status: DinosaurHealthStatus;
19 private _creationDate: Date;
20 private _lastUpdate: Date;
21
22 constructor(id: string, species: string, age: number, weight: number) {
23 this._id = id;
24 this._species = species;
25 this._age = age;
26 this._weight = weight;
27 this._status = "healthy";
28 this._creationDate = new Date();
29 this._lastUpdate = new Date();
30 }
31
32 // Getters
33 get id(): string {
34 return this._id;
35 }
36
37 get species(): string {
38 return this._species;
39 }
40
41 get age(): number {
42 return this._age;
43 }
44
45 get weight(): number {
46 return this._weight;
47 }
48
49 get status(): DinosaurHealthStatus {
50 return this._status;
51 }
52
53 // Setter with value checking and date update
54 set status(newStatus: DinosaurHealthStatus) {
55 if (this._status !== newStatus) {
56 this._status = newStatus;
57 this._lastUpdate = new Date();
58 this.logStatusChange(newStatus);
59 }
60 }
61
62 // Protected method accessible to inheriting classes
63 protected updateWeight(newWeight: number): void {
64 if (newWeight > 0) {
65 this._weight = newWeight;
66 this._lastUpdate = new Date();
67 } else {
68 throw new Error("Weight must be greater than zero.");
69 }
70 }
71
72 // Private method accessible only within this class
73 private logStatusChange(newStatus: DinosaurHealthStatus): void {
74 console.log(`[${new Date().toISOString()}] Status change ${this._id}: ${nowyStatus}`);
75 }
76
77 // Abstract method - every inheriting class must implement it
78 abstract getClinicalDescription(): string;
79
80 // Public method
81 public getBasicData(): string {
82 return `ID: ${this._id} | Species: ${this._gatunek} | Age: ${this._wiek} years | Weight: ${this._weight} kg | Status: ${this._status}`;
83 }
84}
85
86// Inheriting class
87class ParkDinosaur extends ParkAnimal implements IMonitoring {
88 // Private properties specific to dinosaurs
89 private _diet: DietType;
90 private _aggressionLevel: number; // 1-10
91 private _isMonitored: boolean;
92 private _lastMeal: Date | null;
93 private _assignedEnclosure: string | null;
94
95 constructor(
96 id: string,
97 gatunek: string,
98 wiek: number,
99 weight: number,
100 diet: DietType,
101 aggressionLevel: number
102 ) {
103 super(id, gatunek, wiek, weight);
104 this._diet = diet;
105 this._aggressionLevel = this.validateAggressionLevel(poziomAgresji);
106 this._isMonitored = false;
107 this._lastMeal = null;
108 this._assignedEnclosure = null;
109 }
110
111 // Implementation of abstract method
112 getClinicalDescription(): string {
113 let description = `Dinosaur ${this.gatunek} (ID: ${this.id})\n`;
114 description += `Status zdrowotny: ${this.status}\n`;
115 description += `Age: ${this.wiek} lat, Weight: ${this.weight} kg\n`;
116 description += `Diet: ${this._dieta}\n`;
117 description += `Aggression level: ${this._aggressionLevel}/10\n`;
118 description += `Last meal: ${this._lastMeal ? this._lastMeal.toLocaleString() : "No data"}\n`;
119
120 return description;
121 }
122
123 // Implementation of IMonitorowania interface
124 startMonitoring(): void {
125 this._isMonitored = true;
126 console.log(`Monitoring started for dinosaura ${this.id}`);
127 }
128
129 stopMonitoring(): void {
130 this._isMonitored = false;
131 console.log(`Monitoring stopped for dinosaura ${this.id}`);
132 }
133
134 getMonitoringStatus(): string {
135 return this._isMonitored ? "Active" : "Inactive";
136 }
137
138 // Public methods specific to dinosaurs
139 public feed(amount: number): void {
140 if (amount <= 0) {
141 throw new Error("Food amount must be greater than zero.");
142 }
143
144 const weightGain = this._dieta === "carnivore" ? amount * 0.3 : amount * 0.2;
145 this.updateWeight(this.weight + weightGain);
146
147 this._lastMeal = new Date();
148 this._aggressionLevel = this.validateAggressionLevel(this._aggressionLevel - 1);
149
150 console.log(`Dinosaur ${this.id} was fed. New weight: ${this.weight} kg`);
151 }
152
153 public assignToEnclosure(enclosureCode: string): void {
154 this._assignedEnclosure = enclosureCode;
155 console.log(`Dinosaur ${this.id} was assigned to enclosure ${enclosureCode}`);
156 }
157
158 public sedate(): void {
159 this.status = "sedated";
160 this._aggressionLevel = 1;
161 console.log(`Dinosaur ${this.id} was sedated`);
162 }
163
164 public wake(): void {
165 if (this.status === "sedated") {
166 this.status = "zdrowy";
167 this._aggressionLevel = this.validateAggressionLevel(this._aggressionLevel + 2);
168 console.log(`Dinosaur ${this.id} was awakened`);
169 }
170 }
171
172 // Getters and setters
173 get dieta(): TypDiety {
174 return this._dieta;
175 }
176
177 get poziomAgresji(): number {
178 return this._aggressionLevel;
179 }
180
181 get lastMeal(): Date | null {
182 return this._lastMeal ? new Date(this._lastMeal.getTime()) : null;
183 }
184
185 get przypisanyWybieg(): string | null {
186 return this._assignedEnclosure;
187 }
188
189 set poziomAgresji(poziom: number) {
190 this._aggressionLevel = this.validateAggressionLevel(poziom);
191 }
192
193 // Private helper method
194 private validateAggressionLevel(poziom: number): number {
195 return Math.max(1, Math.min(10, poziom));
196 }
197}
198
199// Using the system
200function testDinosaurManagementSystem() {
201 // Creating dinosaurs
202 const trex = new Dinosaur("T-REX-01", "Tyrannosaurus Rex", 8, 7500, "carnivore", 9);
203 const stegosaurus = new Dinosaur("STEG-01", "Stegosaurus", 12, 5000, "herbivorous", 4);
204
205 // Displaying basic information
206 console.log("=== DANE PODSTAWOWE ===");
207 console.log(trex.getBasicData());
208 console.log(stegosaurus.getBasicData());
209
210 // Operations on dinosaurs
211 trex.feed(50);
212 trex.assignToEnclosure("CARNIVORE-A");
213 trex.startMonitoring();
214
215 stegosaurus.feed(80);
216 stegosaurus.assignToEnclosure("HERBIVORE-B");
217
218 // Status change
219 trex.status = "w leczeniu";
220 stegosaurus.sedate();
221
222 // Getting clinical reports
223 console.log("\n=== RAPORTY KLINICZNE ===");
224 console.log(trex.pobierzOpisKliniczny());
225 console.log(stegosaurus.pobierzOpisKliniczny());
226
227 // Monitoring status
228 console.log("\n=== STATUS MONITOROWANIA ===");
229 console.log(`T-Rex monitoring: ${trex.pobierzStatusMonitorowania()}`);
230 console.log(`Stegozaur monitoring: ${stegosaurus.pobierzStatusMonitorowania()}`);
231
232 // Attempting to access private/protected elements
233 // The following lines would cause a compilation error:
234 // console.log(trex._id); // Error: Property '_id' is private
235 // console.log(trex._gatunek); // Error: Property '_gatunek' is protected
236 // trex.logZmianyStatusu("zdrowy"); // Error: Property 'logZmianyStatusu' is private
237 // trex.validateAggressionLevel(5); // Error: Property 'validateAggressionLevel' is private
238}
239
240// Running the test
241testDinosaurManagementSystem();In the example above:
- Abstract base class (ParkAnimal) defines common properties and behaviors for all animals in the park
- Private properties (_id, _dataUtworzenia, _lastUpdate) are accessible only within the class
- Protected properties (_gatunek, _wiek, _weight, _status) are accessible in the base class and inheriting classes
- Public getters and setters provide controlled access to properties
- Private methods (logZmianyStatusu, validateAggressionLevel) are used as helper implementations accessible only within the class
- Protected methods (updateWeight) are accessible to inheriting classes
- Abstract method (pobierzOpisKliniczny) enforces implementation in inheriting classes
- Interface (IMonitorowania) defines a contract that the Dinosaur class must implement
When and How to Use Access Modifiers
Choosing the appropriate access modifier depends on the specific use case, but there are a few general rules:
Use private for:
- Internal implementation details
- Data that should never be accessible outside the class
- Helper methods that are not part of the public API
- Properties that should only be modified through controlled setters
Use protected for:
- Properties and methods that need to be accessible in inheriting classes
- Implementations that can be extended or overridden in derived classes
- Data that is common to the entire class hierarchy but should not be accessible from outside
Use public for:
- Elements that form the public API interface of the class
- Methods that need to be accessible to all users of the class
- Properties that can be safely read or modified from outside
Summary
Access modifiers in TypeScript are a key tool for implementing good object-oriented design principles such as encapsulation, inheritance, and polymorphism. They allow precise control over access to class properties and methods, which promotes creating secure, modular, and maintainable code.
Just like in Jurassic Park, where the security system requires different access levels, a well-designed object-oriented system uses access modifiers to ensure that each element is accessible only to those who should actually have access to it.
John Hammond would summarize it briefly: "In Jurassic Park and in programming - security is paramount. Access modifiers are like security zones in the park - public for everyone, protected for specialists, and private only for top-level management. That's the key to maintaining order and preventing... surprises."
Code for this lesson: index.ts
1// Access modifiers in TypeScript - Jurassic Park
2console.log("Jurassic Park security system");
3console.log("Demonstration of access modifiers: public, private, protected\n");
4
5// ===========================================
6// 1. Basic access modifiers
7// ===========================================
8console.log("=== 1. ACCESS MODIFIERS ===");
9
10class BasicDinosaur {
11 // public - accessible from everywhere (default)
12 public name: string;
13 public species: string;
14
15 // private - accessible only in this class
16 private dnaSequence: string;
17 private securityCode: number;
18
19 // protected - accessible in this class and inheriting classes
20 protected age: number;
21 protected habitat: string;
22
23 constructor(name: string, species: string, dnaSequence: string) {
24 this.name = name;
25 this.species = species;
26 this.dnaSequence = dnaSequence;
27 this.securityCode = Math.floor(Math.random() * 10000);
28 this.age = 0;
29 this.habitat = "Enclosure";
30 }
31
32 // Public method
33 public getBasicInfo(): string {
34 return `${this.name} (${this.species})`;
35 }
36
37 // Private method
38 private validateDNA(): boolean {
39 return this.dnaSequence.length > 10;
40 }
41
42 // Protected method
43 protected calculateMaturity(): string {
44 if (this.age < 5) return "Young";
45 if (this.age < 15) return "Adult";
46 return "Old";
47 }
48
49 // Public access to private functionality
50 public isDNAValid(): boolean {
51 return this.validateDNA();
52 }
53
54 // Method with controlled access to private data
55 public getSecureInfo(accessCode: number): string | null {
56 if (accessCode === this.securityCode) {
57 return `DNA: ${this.dnaSequence.substring(0, 5)}...`;
58 }
59 return null;
60 }
61}
62
63const basicDino = new BasicDinosaur("Rex", "T-Rex", "ATCGATCGATCG");
64
65console.log("Basic info:", basicDino.getBasicInfo());
66console.log("Public fields:");
67console.log(" Name:", basicDino.name);
68console.log(" Species:", basicDino.species);
69
70console.log("DNA valid:", basicDino.isDNAValid());
71
72// These won't work - TypeScript compilation errors:
73// console.log(basicDino.dnaSequence); // Error: private
74// console.log(basicDino.age); // Error: protected
75// basicDino.validateDNA(); // Error: private method
76
77console.log("\n");
78
79// ===========================================
80// 2. Inheritance and modifiers
81// ===========================================
82console.log("=== 2. INHERITANCE AND MODIFIERS ===");
83
84class CarnivorousDinosaur extends BasicDinosaur {
85 private huntingExperience: number;
86 protected packSize: number;
87
88 constructor(name: string, species: string, dnaSequence: string) {
89 super(name, species, dnaSequence);
90 this.huntingExperience = 0;
91 this.packSize = 1;
92 }
93
94 // Access to protected fields from base class
95 public setAge(newAge: number): void {
96 this.age = newAge; // OK - protected in the base class
97 console.log(`${this.name} is now ${this.age} years old`);
98 }
99
100 public getMaturityStatus(): string {
101 // Access to protected method from base class
102 return `${this.name} is ${this.calculateMaturity()}`;
103 }
104
105 // Overriding a protected method
106 protected calculateMaturity(): string {
107 const baseMaturity = super.calculateMaturity();
108 const huntingLevel = this.huntingExperience > 10 ? "Experienced hunter" : "Beginner";
109 return `${baseMaturity} - ${huntingLevel}`;
110 }
111
112 public hunt(): string {
113 this.huntingExperience++;
114 return `${this.name} is hunting! Experience: ${this.huntingExperience}`;
115 }
116
117 // Cannot access private fields from base class
118 // public getDNA(): string {
119 // return this.dnaSequence; // Error: private in base class
120 // }
121}
122
123const carnivoreDino = new CarnivorousDinosaur("Blue", "Velociraptor", "GCTAGCTAGCTA");
124carnivoreDino.setAge(8);
125console.log("Maturity status:", carnivoreDino.getMaturityStatus());
126console.log("Hunting:", carnivoreDino.hunt());
127
128console.log("\n");
129
130// ===========================================
131// 3. Encapsulation of sensitive data
132// ===========================================
133console.log("=== 3. ENCAPSULATION OF SENSITIVE DATA ===");
134
135class SecureDinosaur {
136 public readonly id: string;
137 public name: string;
138
139 private dnaSequence: string;
140 private medicalHistory: string[];
141 private behaviorPatterns: Map<string, number>;
142
143 protected enclosureAccess: boolean;
144 protected emergencyProtocols: string[];
145
146 constructor(id: string, name: string, dna: string) {
147 this.id = id;
148 this.name = name;
149 this.dnaSequence = dna;
150 this.medicalHistory = [];
151 this.behaviorPatterns = new Map();
152 this.enclosureAccess = true;
153 this.emergencyProtocols = ["Tranquilizer", "Emergency Lock"];
154 }
155
156 // Controlled access to medical data
157 public addMedicalRecord(record: string, veterinarianCode: string): boolean {
158 if (this.validateVeterinarianAccess(veterinarianCode)) {
159 this.medicalHistory.push(`[${new Date().toISOString()}] ${record}`);
160 return true;
161 }
162 console.log("Unauthorized access to medical records");
163 return false;
164 }
165
166 public getMedicalHistory(veterinarianCode: string): string[] | null {
167 if (this.validateVeterinarianAccess(veterinarianCode)) {
168 return [...this.medicalHistory]; // A copy, not a reference
169 }
170 console.log("Unauthorized access to medical records");
171 return null;
172 }
173
174 // Controlled access to behavior patterns
175 public updateBehaviorPattern(pattern: string, intensity: number, researcherCode: string): boolean {
176 if (this.validateResearcherAccess(researcherCode)) {
177 this.behaviorPatterns.set(pattern, intensity);
178 console.log(`Behavior pattern '${pattern}' updated to ${intensity}`);
179 return true;
180 }
181 console.log("Unauthorized access to behavior data");
182 return false;
183 }
184
185 public getBehaviorAnalysis(researcherCode: string): string | null {
186 if (this.validateResearcherAccess(researcherCode)) {
187 const patterns = Array.from(this.behaviorPatterns.entries());
188 return patterns.map(([pattern, intensity]) => `${pattern}: ${intensity}`).join(", ");
189 }
190 console.log("Unauthorized access to behavior data");
191 return null;
192 }
193
194 // Private validation methods
195 private validateVeterinarianAccess(code: string): boolean {
196 return code === "VET-2023";
197 }
198
199 private validateResearcherAccess(code: string): boolean {
200 return code === "RESEARCH-2023";
201 }
202
203 // Public security methods
204 public getPublicInfo(): object {
205 return {
206 id: this.id,
207 name: this.name,
208 hasAccess: this.enclosureAccess,
209 lastUpdate: new Date().toISOString()
210 };
211 }
212}
213
214const secureDino = new SecureDinosaur("SEC-001", "Alpha", "CLASSIFIED-DNA");
215
216console.log("Public info:", secureDino.getPublicInfo());
217
218// Test access control
219console.log("\nVeterinary access test:");
220secureDino.addMedicalRecord("Routine checkup", "VET-2023"); // Authorized
221secureDino.addMedicalRecord("Vaccination", "FAKE-CODE"); // Unauthorized
222
223console.log("\nResearch access test:");
224secureDino.updateBehaviorPattern("Aggression", 7, "RESEARCH-2023"); // Authorized
225secureDino.updateBehaviorPattern("Calm", 3, "HACKER-CODE"); // Unauthorized
226
227console.log("\n");
228
229// ===========================================
230// 4. Builder pattern with access modifiers
231// ===========================================
232console.log("=== 4. BUILDER PATTERN WITH MODIFIERS ===");
233
234class DinosaurBuilder {
235 private dinosaur: {
236 name?: string;
237 species?: string;
238 diet?: string;
239 weight?: number;
240 dangerLevel?: number;
241 };
242
243 constructor() {
244 this.dinosaur = {};
245 }
246
247 public setName(name: string): DinosaurBuilder {
248 this.dinosaur.name = name;
249 return this;
250 }
251
252 public setSpecies(species: string): DinosaurBuilder {
253 this.dinosaur.species = species;
254 return this;
255 }
256
257 public setDiet(diet: string): DinosaurBuilder {
258 this.dinosaur.diet = diet;
259 return this;
260 }
261
262 public setWeight(weight: number): DinosaurBuilder {
263 this.dinosaur.weight = weight;
264 return this;
265 }
266
267 public setDangerLevel(level: number): DinosaurBuilder {
268 this.dinosaur.dangerLevel = level;
269 return this;
270 }
271
272 // Private validation
273 private validateDinosaur(): boolean {
274 const required = ['name', 'species', 'diet', 'weight', 'dangerLevel'];
275 return required.every(field => this.dinosaur[field as keyof typeof this.dinosaur] !== undefined);
276 }
277
278 public build(): CompleteDinosaur | null {
279 if (!this.validateDinosaur()) {
280 console.log("Validation failed - missing required fields");
281 return null;
282 }
283
284 return new CompleteDinosaur(
285 this.dinosaur.name!,
286 this.dinosaur.species!,
287 this.dinosaur.diet!,
288 this.dinosaur.weight!,
289 this.dinosaur.dangerLevel!
290 );
291 }
292}
293
294class CompleteDinosaur {
295 public readonly name: string;
296 public readonly species: string;
297 private readonly diet: string;
298 private readonly weight: number;
299 private readonly dangerLevel: number;
300
301 constructor(name: string, species: string, diet: string, weight: number, dangerLevel: number) {
302 this.name = name;
303 this.species = species;
304 this.diet = diet;
305 this.weight = weight;
306 this.dangerLevel = dangerLevel;
307 }
308
309 public getPublicProfile(): object {
310 return {
311 name: this.name,
312 species: this.species
313 };
314 }
315
316 public getDetailedInfo(securityClearance: number): object | null {
317 if (securityClearance >= this.dangerLevel) {
318 return {
319 name: this.name,
320 species: this.species,
321 diet: this.diet,
322 weight: this.weight,
323 dangerLevel: this.dangerLevel
324 };
325 }
326
327 console.log(`Insufficient security clearance. Required: ${this.dangerLevel}, Provided: ${securityClearance}`);
328 return null;
329 }
330}
331
332// Using Builder pattern
333const builder = new DinosaurBuilder();
334const completeDino = builder
335 .setName("Indominus Rex")
336 .setSpecies("Indominus Rex")
337 .setDiet("carnivore")
338 .setWeight(5500)
339 .setDangerLevel(10)
340 .build();
341
342if (completeDino) {
343 console.log("Public profile:", completeDino.getPublicProfile());
344 console.log("\nAccess attempt with clearance 5:", completeDino.getDetailedInfo(5));
345 console.log("Access attempt with clearance 10:", completeDino.getDetailedInfo(10));
346}
347
348console.log("\n");
349
350// ===========================================
351// 5. Permissions and roles system
352// ===========================================
353console.log("=== 5. PERMISSIONS AND ROLES SYSTEM ===");
354
355enum UserRole {
356 Visitor = 1,
357 Staff = 2,
358 Veterinarian = 3,
359 Researcher = 4,
360 Security = 5,
361 Administrator = 6
362}
363
364abstract class ParkUser {
365 protected name: string;
366 protected role: UserRole;
367 protected accessLevel: number;
368
369 constructor(name: string, role: UserRole) {
370 this.name = name;
371 this.role = role;
372 this.accessLevel = role;
373 }
374
375 public getName(): string {
376 return this.name;
377 }
378
379 public getRole(): string {
380 return UserRole[this.role];
381 }
382
383 // Abstract method - must be implemented in derived classes
384 public abstract canAccess(resource: string): boolean;
385
386 protected hasMinimumAccess(requiredLevel: number): boolean {
387 return this.accessLevel >= requiredLevel;
388 }
389}
390
391class ParkStaff extends ParkUser {
392 private department: string;
393
394 constructor(name: string, role: UserRole, department: string) {
395 super(name, role);
396 this.department = department;
397 }
398
399 public canAccess(resource: string): boolean {
400 const resourcePermissions: { [key: string]: number } = {
401 "public_areas": UserRole.Visitor,
402 "staff_rooms": UserRole.Staff,
403 "dinosaur_basic_info": UserRole.Staff,
404 "medical_records": UserRole.Veterinarian,
405 "research_data": UserRole.Researcher,
406 "security_systems": UserRole.Security,
407 "admin_panel": UserRole.Administrator
408 };
409
410 const requiredLevel = resourcePermissions[resource];
411 if (requiredLevel === undefined) {
412 console.log(`Unknown resource: ${resource}`);
413 return false;
414 }
415
416 const hasAccess = this.hasMinimumAccess(requiredLevel);
417 console.log(`${this.name} (${this.getRole()}) accessing ${resource}: ${hasAccess ? 'GRANTED' : 'DENIED'}`);
418 return hasAccess;
419 }
420
421 public getDepartment(): string {
422 return this.department;
423 }
424}
425
426// Test permissions system
427const visitor = new ParkStaff("John Doe", UserRole.Visitor, "Tourist");
428const veterinarian = new ParkStaff("Dr. Sarah Connor", UserRole.Veterinarian, "Medical");
429const researcher = new ParkStaff("Dr. Alan Grant", UserRole.Researcher, "Paleontology");
430const admin = new ParkStaff("Robert Muldoon", UserRole.Administrator, "Operations");
431
432console.log("\nAccess control test:");
433
434const resources = ["public_areas", "dinosaur_basic_info", "medical_records", "research_data", "admin_panel"];
435const users = [visitor, veterinarian, researcher, admin];
436
437users.forEach(user => {
438 console.log(`\n${user.getName()} (${user.getRole()}):`);
439 resources.forEach(resource => {
440 user.canAccess(resource);
441 });
442});
443
444console.log("\n");
445
446// ===========================================
447// 6. Getters and setters with access control
448// ===========================================
449console.log("=== 6. GETTERS AND SETTERS WITH ACCESS CONTROL ===");
450
451class MonitoredDinosaur {
452 private _name: string;
453 private _health: number;
454 private _feedingSchedule: Date[];
455 private _isQuarantined: boolean;
456 private _lastHealthCheck: Date;
457
458 constructor(name: string) {
459 this._name = name;
460 this._health = 100;
461 this._feedingSchedule = [];
462 this._isQuarantined = false;
463 this._lastHealthCheck = new Date();
464 }
465
466 // Getter with access logging
467 get name(): string {
468 console.log(`Access logged: Reading name of ${this._name}`);
469 return this._name;
470 }
471
472 // Getter with permission validation
473 get health(): number {
474 console.log(`Health data accessed for ${this._name}`);
475 return this._health;
476 }
477
478 // Setter with validation
479 set health(value: number) {
480 if (value < 0 || value > 100) {
481 console.log(`Invalid health value: ${value}. Must be 0-100`);
482 return;
483 }
484
485 const oldHealth = this._health;
486 this._health = value;
487 this._lastHealthCheck = new Date();
488
489 console.log(`Health updated for ${this._name}: ${oldHealth} β ${value}`);
490
491 // Automatic quarantine at low health
492 if (value < 30 && !this._isQuarantined) {
493 this._isQuarantined = true;
494 console.log(`${this._name} automatically quarantined due to low health`);
495 }
496 }
497
498 // Computed property
499 get status(): string {
500 if (this._isQuarantined) return "Quarantined";
501 if (this._health < 30) return "Critical";
502 if (this._health < 60) return "Poor";
503 if (this._health < 80) return "Good";
504 return "Excellent";
505 }
506
507 // Setter with business logic
508 set isQuarantined(value: boolean) {
509 if (value !== this._isQuarantined) {
510 this._isQuarantined = value;
511 console.log(`${this._name} quarantine status: ${value ? 'ACTIVE' : 'INACTIVE'}`);
512
513 if (!value && this._health < 50) {
514 console.log(`Warning: Releasing ${this._name} from quarantine with health ${this._health}`);
515 }
516 }
517 }
518
519 get isQuarantined(): boolean {
520 return this._isQuarantined;
521 }
522
523 // Method with controlled access to history
524 public getFeedingHistory(accessLevel: number): Date[] | null {
525 if (accessLevel < UserRole.Staff) {
526 console.log(`Insufficient access to feeding history`);
527 return null;
528 }
529
530 return [...this._feedingSchedule];
531 }
532
533 public scheduleFeed(time: Date): void {
534 this._feedingSchedule.push(time);
535 console.log(`Feeding scheduled for ${this._name} at ${time.toLocaleTimeString()}`);
536 }
537}
538
539const monitoredDino = new MonitoredDinosaur("Alpha-7");
540
541console.log("\nMonitored dinosaur test:");
542console.log("Name:", monitoredDino.name);
543console.log("Initial health:", monitoredDino.health);
544console.log("Initial status:", monitoredDino.status);
545
546// Test setters
547monitoredDino.health = 25; // Automatic quarantine
548console.log("Status after health drop:", monitoredDino.status);
549
550monitoredDino.health = 85; // Health improvement
551console.log("Status after recovery:", monitoredDino.status);
552
553monitoredDino.isQuarantined = false; // Manual release from quarantine
554
555// Test feeding schedule
556monitoredDino.scheduleFeed(new Date(Date.now() + 3600000)); // In one hour
557
558console.log("\n");
559
560// ===========================================
561// 7. Access modifiers summary
562// ===========================================
563console.log("=== 7. ACCESS MODIFIERS SUMMARY ===");
564
565console.log("PUBLIC:");
566console.log(" β’ Accessible from anywhere");
567console.log(" β’ Default modifier in TypeScript");
568console.log(" β’ Use for the class public API\n");
569
570console.log("PRIVATE:");
571console.log(" β’ Accessible only within the same class");
572console.log(" β’ Implementation encapsulation");
573console.log(" β’ Sensitive data protection\n");
574
575console.log("PROTECTED:");
576console.log(" β’ Accessible in the class and inheriting classes");
577console.log(" β’ Sharing between class hierarchy");
578console.log(" β’ Controlled access in inheritance\n");
579
580console.log("READONLY:");
581console.log(" β’ Can only be assigned in the constructor");
582console.log(" β’ Protection against modification");
583console.log(" β’ Immutable fields\n");
584
585console.log("Access modifiers ensure security in Jurassic Park!");Spotted a mistake in this lesson?
Check yourself
Answer the questions from this lesson. Pick an answer to see right away whether it is correct.
1. Which access modifier in TypeScript allows access to a property only within the class and its derived classes?