DEV Community

Ayako yk
Ayako yk

Posted on

Access Modifiers in TypeScript: Best Practices for Encapsulation and Data Hiding

While working on a project and creating classes, I faced a dilemma about which access modifiers to use: public? private? static? public static? or private static? When should private static be used?

In this article, I'll discuss each type of access modifier and its usage.
Access modifiers are an essential part of Object-Oriented Programming, but I'll focus on their usage in TypeScript.

public
This is the default, or we can explicitly write public.
All public members are accessible from anywhere without any restrictions.

class User {
    username?: string;
    public email?: string;
}

let user = new User();
user.username = "John";
user.email = "xyz@example.com";
Enter fullscreen mode Exit fullscreen mode

private
Class members are only visible within the class and are not accessible outside of it.

class User {
    public username?: string;
    private email?: string;
}

let user = new User();
user.username = "John";
user.email = "xyz@example.com";  // Error: Property 'email' is private and only accessible within class 'User'.
Enter fullscreen mode Exit fullscreen mode

static

Static properties cannot be directly accessed on instances of the class. Instead, they're accessed on the class itself.
MDN

class User {
  static username = "John";
  static email = "xyz@example.com";

  static staticMethod(){
    return "staticMethod is called";
  }
}

console.log(User.username); // "John" 
console.log(User.email); // "xyz@example.com" 
console.log(User.staticMethod()) // "staticMethod is called" 

let user = new User()
console.log(user.username) // Error
Enter fullscreen mode Exit fullscreen mode

Error: user.username
JavaScript: Returns undefined because the property does not exist on the instance
TypeScript: Gives a compile-time error

When is static used?
According to MDN,

Static methods are often utility functions, such as functions to create or clone objects, whereas static properties are useful for caches, fixed-configuration, or any other data you don't need to be replicated across instances.

Here are the use cases:

Constant Values
Provide a single source of truth for constants

class MathConstant {
  static pie = 3.14;
}
console.log(MathConstant.pie);
Enter fullscreen mode Exit fullscreen mode

Utility Functions
Perform common operations or calculations that are broadly useful and don't depend on instant-specific data

class MathUtils {
  static add(a: number, b: number){
    return a + b;
  }
}

console.log(MathUtils.add(5, 3));
Enter fullscreen mode Exit fullscreen mode

Cache for Data
Share a common cache across all instances to avoid redundant data storage

class CacheData {
  static cache: {[key: string]: string} = {};

  static set(key: string, value: string){
    this.cache[key] = value;
  }

  static get(key: string): string | undefined {
    return this.cache[key];
  }
}
Enter fullscreen mode Exit fullscreen mode

Configuration Settings
Maintain a single source of configuration that can be accessed and modified globally

class Config {
  static settings: { [key: string]: string } = {
    theme: 'dark',
    lang: 'en'
  }

  static updateSettings(key: string, value: string){
    this.settings[key] = value;
  }
}
Enter fullscreen mode Exit fullscreen mode

Unique Identifier Generator
Ensure unique identifiers across the application

class UniqueID {
  static ID = 0;

  static generateID(){
    return ++this.ID;
  }
}
Enter fullscreen mode Exit fullscreen mode

private static
While public static members are accessible both within the class and outside the class using the class name, private static members are only accessible within the class where they are declared. Private static members are not accessible outside the class or from its subclasses.
The main purpose of private static is encapsulation and data hiding.

class Count {
  private static count: number = 0;

  static increment(){
// Either approach works
    // this.count++; 
    Count.count++;
  }

  static getCount(){
// Either approach works
    // return this.count;
    return Count.count;
  }
}

Count.increment();
Count.increment();
Count.increment();

console.log(Count.getCount()); // 3
Enter fullscreen mode Exit fullscreen mode

In this small example, public static might produce the same result, but it violates the encapsulation.

Access modifiers are fundamental concepts that I learned when I started programming. However, as a project grows larger and its structure needs to be well-considered, I need to think through the details more thoroughly.

Top comments (0)