By Khalil Stemmler

Translation: Crazy geek

Original: www.freecodecamp.org/news/typesc…

Reproduced without permission

In this article, we discussed the utility of getters and setters in modern Web development. Do they work? When does it make sense to use them?

When ECMAScript 5 (2009) was released, getters and setters (also known as accessors) were introduced to JavaScript.

The problem is, there’s a lot of confusion about why they were introduced and how practical they are.

I saw a thread on Reddit that debated whether they were anti-patterns.

Unfortunately, the general consensus on the subject is yes. I think that’s because most of the time, the front-end programming you’re doing doesn’t require getters and setters and things like that.

Although I don’t agree that getters and setters are completely anti-patterns. But they can be more useful in several situations.

What are they?

Getters and setters are another method that provides access to object properties.

The general usage is as follows:

interface ITrackProps {
  name: string;
  artist: string;
}

class Track {  
  private props: ITrackProps;

  get name (): string {
    return this.props.name;
  }

  set name (name: string) {
	  this.props.name = name;
  }

  get artist (): string {
    return this.props.artist;
  }

  set artist (artist: string) {
	  this.props.artist = artist;
  }

  constructor (props: ITrackProps) {
    this.props = props;
  } 

  public play (): void {	
	  console.log(`Playing ${this.name} by ${this.artist}`); }}Copy the code

Now the question becomes, “Why not just use regular class attributes?”

Well, in this case, yes.

interface ITrackProps {
  name: string;
  artist: string;
}

class Track {  
  public name: string;
  public artist: string;

  constructor (name: string, artist: string;) {
    this.name = name;
    this.artist = artist;
  } 

  public play (): void {	
	  console.log(`Playing ${this.name} by ${this.artist}`); }}Copy the code

This is a very simple example. Let’s look at a scenario that better describes why we should care about using getters and settters with general class properties.

Anemia prevention model

Do you remember what an anaemic pattern is? One way to detect anemic patterns early is if every property of your domain entity has getters and setters exposed (i.e., sets that do not make sense for domain-specific languages).

If you don’t explicitly use the get or set keyword, all public will have the same negative effect.

Consider this example:

class User {
  / / is bad. You can now 'set' the user ID.
  // Do I need to change the user ID to another identifier?
  // Is it safe? Should you do this?
  public id: UserId;

  constuctor (id: UserId) {
    this.id = id; }}Copy the code

In domain-driven design, it is important for us to expose only actions that work for the domain in order to prevent anemic patterns and advance the creation of domain-specific languages.

This means you need to understand the area you are working in.

I will subject myself to scrutiny. Let’s take a look at the Vinyl class in White Label, an open source Vinyl trading program designed using domain drivers and built on TypeScript.

import { AggregateRoot } from ".. /.. /core/domain/AggregateRoot";
import { UniqueEntityID } from ".. /.. /core/domain/UniqueEntityID";
import { Result } from ".. /.. /core/Result";
import { Artist } from "./artist";
import { Genre } from "./genre";
import { TraderId } from ".. /.. /trading/domain/traderId";
import { Guard } from ".. /.. /core/Guard";
import { VinylCreatedEvent } from "./events/vinylCreatedEvent";
import { VinylId } from "./vinylId";

interface VinylProps {
  traderId: TraderId;
  title: string; artist: Artist; genres: Genre[]; dateAdded? :Date;
}

export type VinylCollection = Vinyl[];

export class Vinyl extends AggregateRoot<VinylProps> {

  public static MAX_NUMBER_GENRES_PER_VINYL = 3;

	/ / 🔥 1. Appearance. The VinylId key doesn't actually exist
  // VinylProps as an attribute, but we still need it
  // Provide access to it.
  get vinylId(): VinylId {
    return VinylId.create(this.id)
  }

  get title (): string {
    return this.props.title;
  }

  // 🔥2. All of these properties are nested as props
  // At one level, so we can control the ACTUAL value
  // Access and change.
  get artist (): Artist {
    return this.props.artist
  }

  get genres (): Genre[] {
    return this.props.genres;
  }

  get dateAdded (): Date {
    return this.props.dateAdded;
  }

  // 🔥3. You'll notice that there are no setters so far,
  // Because there is no point in changing things after they are created
  
  get traderId (): TraderId {
    return this.props.traderId;
  }

  // 🔥4. This approach is called "encapsulating collections".
  // Yes, we need to add the type. But we are still
  // But there is no setter exposed, because there is one
  // Some immutable logic that we want to make sure we enforce.

  public addGenre (genre: Genre): void {
    const maxLengthExceeded = this.props.genres
      .length >= Vinyl.MAX_NUMBER_GENRES_PER_VINYL;

    const alreadyAdded = this.props.genres
      .find((g) = > g.id.equals(genre.id));

    if(! alreadyAdded && ! maxLengthExceeded) {this.props.genres.push(genre); }}// 🔥 5. Provide a deletion mode.

  public removeGenre (genre: Genre): void {
    this.props.genres = this.props.genres
      .filter((g) = >! g.id.equals(genre.id)); }private constructor (props: VinylProps, id? : UniqueEntityID) {
    super(props, id);
  }

  // 🔥 6. This is how we create Vinyl.
  // After creation, all attributes except Genre
  // both become "read only" because it makes sense to enable modifications.
  public staticcreate (props: VinylProps, id? : UniqueEntityID): Result<Vinyl> {const propsResult = Guard.againstNullOrUndefinedBulk([
      { argument: props.title, argumentName: 'title' },
      { argument: props.artist, argumentName: 'artist' },
      { argument: props.genres, argumentName: 'genres' },
      { argument: props.traderId, argumentName: 'traderId'}]);if(! propsResult.succeeded) {return Result.fail<Vinyl>(propsResult.message)
    } 

    const vinyl = newVinyl({ ... props, dateAdded: props.dateAdded ? props.dateAdded :new Date(),
      genres: Array.isArray(props.genres) ? props.genres : [],
    }, id);
    constisNewlyCreated = !! id ===false;

    if (isNewlyCreated) {
      // 🔥 7. This is why we need VinylId:
      // Provide an identifier for all subscribers of this domain event.
      vinyl.addDomainEvent(new VinylCreatedEvent(vinyl.vinylId))
    }

    returnResult.ok<Vinyl>(vinyl); }}Copy the code

Acting as a facade, maintaining read-only values, enforcing model representations, encapsulating collections, and creating domain events are some of the most reliable use cases for getters and setters in domain-driven design.

Change detection in vue.js

Vue.js is a relatively new front-end framework known for its speed and responsiveness.

The reason vue.js is so effective at detecting changes is that they use the Object.defineProperty() API to monitor changes to View Models!

From vue.js documentation on responsiveness:

When you pass a pure JavaScript Object to a Vue instance as its data option, Vue iterates through all of its properties and converts them to getters/setters with Object.defineProperty. Getters/setters are invisible to the user, but behind the scenes, they enable Vue to perform dependency tracking and change notification when a property is accessed or modified. — Vue.js document: responsive


Anyway, getters and setters are very useful for a lot of problems. In modern front-end Web development, however, these issues do not arise much.

Welcome to pay attention to the front end public number: front end pioneer, receive front-end engineering practical toolkit.