This is the 14th day of my participation in the November Challenge

Class profiles in TypeScript

With classes, you can express common object-oriented patterns in a standard way, making features like inheritance more readable and interoperable. In TypeScript, in addition to using interfaces and functions to describe object types, classes have another way to define the shape of an object.

If you haven’t worked with classes yet, it might be helpful to look at a few basic concepts.

You can think of a class as a blueprint for generating an object, such as a car. The Car class describes attributes of a Car, such as brand, color, or number of doors. It also describes actions that the car can perform, such as acceleration, braking or steering.

But Car is just a plan to build a Car. An instance of Car must be generated from the Car class before it becomes the object to assign attribute values to (such as setting the color to blue) or invoke its behavior (such as pressing the brake).

The Car class can be reused to create any number of new Car objects, each with its own characteristics. You can also extend the Car class. For example, the ElectricCar class extends Car. It would have the same attributes and behaviors as Car, but could also have its own unique attributes and behaviors, such as gears and charging operations.

Class encapsulates the object’s data. Data and behavior are included in classes, but details of both can be hidden from people who use objects in their code. For example, if you call the method Car on the turn object, you don’t need to know exactly how the steering wheel works, just that the Car turns left when you tell it to turn left. This class acts as a black box in which all features and behaviors are exposed only through properties and methods, limiting the coder’s ability to use it.

Class components

  • Properties (also known as fields) are data (or features) of an object. These are the defining characteristics of objects that can be set or returned in code.
  • constructorIs a special function that creates and initializes objects based on classes. When a new instance of a class is created, the constructor creates a new object using the class shape and initializes it with the value passed to it.
  • Accessors are a type of device used forgetsetThe function type of the property value. Attributes can be read-only, simply by omitting the classsetAccessors, or by ellipsisgetAccessors make it inaccessible (this property returns if an attempt is made to access itundefined, even if it is assigned during initialization.
  • Methods are functions that define the behavior or operations an object can perform. These methods can be called to invoke the behavior of the object. You can also define methods that can only be accessed from the class itself and are usually called by other methods in the class to perform tasks.

Design specification

Classes can be created to model data, encapsulate functionality, provide templates, and many other purposes. Therefore, the components listed above are not required in every class you create. You may need only the methods and constructors of the utility object, or you may need only properties to manage the data.

Create a class

To create a class, define its members: properties, constructor, accessors, and methods.

  1. Create a new class using the class keyword followed by the class name Car. By convention, the class name is PascalCase. You can also add comments to make it easier to add class members to the correct location. It looks like JAVA to me.

    class Car {
        // Properties
    
        // Constructor
    
        // Accessors
    
        // Methods
    
    }
    Copy the code

Declaring class attributes

Class attributes can be treated as raw data that is passed to an object at initialization.

  1. Declare three attributes of the Car class: _model: string, _color: string, and _DOORS: number. For example, your editor will give you an error because you don’t have a constructor yet

    // Properties
    _make: string;
    _color: string;
    _doors: number;
    Copy the code

Define the class constructor

Classes in TypeScript create two distinct types: instance types (which define members of class instances) and constructor function types (which define members of class constructor functions). The constructor function type is also called a “static side” type because it includes static members of the class.

Using Constructor simplifies classes and makes them easier to manage when using multiple classes.

A class can contain at most one constructor declaration. If the class does not contain a constructor declaration, an automatic constructor is provided.

For example, your code could look something like this

TypeScript copy

// Constructor
constructor(make: string, color: string, doors = 4) {
    this._make = make;
    this._color = color;
    this._doors = doors;
}
Copy the code

At this point, if you’re a Java user you might be wondering what _ does and I’m obsessive-compulsive and it’s not pretty. The underscore (_) before attribute names is not required in attribute declarations, but it provides a way to distinguish between attribute declarations and parameters accessible through constructors while still combining the two in an intuitive way.

Defining accessors

Although you can access class properties directly (they are all public by default), TypeScript supports using getters/setters as methods to intercept access to properties. This gives you more fine-grained control over how members are accessed on each object.

To set or return the value of an object member from code, you must define get and SET accessors in the class.

If you’re a Java user, isn’t this automatically generating get/set methods

  1. Define a get block for the make parameter that returns the value of the _make attribute.

    // Accessors
    get make() {
        return this._make;
    }
    Copy the code
  2. Define a set block for the make parameter that sets the value of the _make attribute to the value of the make parameter.

    set make(make) {
        this._make = make;
    }
    Copy the code
  3. You can also use get and SET blocks to validate data, impose constraints, or perform other operations on the data before returning it to the program. Define get and set blocks for the color parameter, but this time return a string attached to the value of the _color attribute.

    get color() {
        return 'The color of the car is ' + this._color;
    }
    set color(color) {
        this._color = color;
    }
    Copy the code
  4. Define get and set blocks for DOORS parameters. Before returning the value of the _DOORS attribute, make sure that the value of the DOORS parameter is even. Otherwise, an error is raised.

    get doors() {
        return this._doors;
    }
    set doors(doors) {
        if ((doors % 2) = = =0) {
            this._doors = doors;
        } else {
            throw new Error('Doors must be an even number'); }}Copy the code

Defining class methods

You can define any TypeScript function in a class and call it as a method on an object or from another function in the class. These class members describe the behavior that the class can perform and perform any other tasks that the class needs

  1. Define these four methods for the Car class: accelerate, Brake, turn, and worker. You’ll notice there is no function keyword. This is not required or allowed when defining functions in a class, so it helps keep the syntax concise.

    // Methods
    accelerate(speed: number) :string {
        return `The ${this.worker()} is accelerating to ${speed} MPH.`
    }
    brake(): string {
        return `The ${this.worker()} is braking with the standard braking system.`
    }
    turn(direction: 'left' | 'right') :string {
        return `The ${this.worker()} is turning ${direction}`;
    }
    // This function performs work for the other method functions
    worker(): string {
        return this._make;
    }
    Copy the code

Instantiate the class

At this point, you have a class called Car that has three properties that you can get and set. It also has four methods. You can now create a new Car object by instantiating the new class using the Car keyword and passing arguments to it.

  1. Below the class declaration, declare a variable named myCar1 and assign it a new Car object, passing values for the make, color, and DOORS parameters (be sure to assign an even number to the DOORS parameters).

    let myCar1 = new Car('Cool Car Company'.'blue'.2);  // Instantiates the Car object with all parameters
    Copy the code
  2. You can now access the properties of the new myCar1 object. Type myCar1. You should see a list of members defined in the class, including color and _color. Select Run to return the values of these two properties to the console.

    Accessors are only available when targeting ECMAScript 5 and higher.

    Use the following command

    Tsc-t es5. \ filenames. TsCopy the code
    console.log(myCar1.color);
    console.log(myCar1._color);
    Copy the code

    Then execute the compiled js file node.\ filene.js

  1. The member _color represents the attribute defined in the class, and color is the parameter passed to the constructor. When you reference _color, you will access the raw data of the property, which will return ‘blue’. When referencing color, you will access The property via The GET or set accessor, which will return ‘The color of The car is blue’. It’s important to understand the difference, because you generally don’t want to access properties directly without validating or doing something else with the data before getting or setting it.

  2. Notice that the set block for the DOORS parameter tests the value to determine whether it is even or odd. The test is done by declaring a variable called myCar2 and assigning it a new Car object, passing in values for the make, color, and DOORS parameters. At this point, set the value of the DOORS parameter to odd. Now, select Run. What’s going on? Why is that?

    let myCar2 = new Car('Galaxy Motors', 'red', 3);
    Copy the code

    Which car has three doors, this step assignment, according to our logic will report an error, but the program does not

  3. Although an odd number was passed to DOORS, it compiled and ran without error because no data validation occurred in Constructor. Try setting the value of DOORS to another odd number (for example, mycar2.DOORS = 5) and test it. We expect this to call the set block and raise an error. So if you want to perform this validation step when initializing a Car object, add a validation check to Constructor.

    constructor(make: string, color: string, doors = 4) { this._make = make; this._color = color; if ((doors % 2) === 0) { this._doors = doors; } else { throw new Error('Doors must be an even number'); }}Copy the code
  4. Test it by omitting the optional DOORS parameter from the object initialization.

    let myCar3 = new Car('Galaxy Motors', 'gray');
    console.log(myCar3.doors);  // returns 4, the default value
    Copy the code

    Since we already set the default value in the constructor, it is ok to omit the optional DOORS argument, which defaults to 4

  5. Test the method by sending the return value to the console.

    console.log(myCar1.accelerate(35));
    console.log(myCar1.brake());
    console.log(myCar1.turn('right'));
    Copy the code

Access modifier

By default, all class members are public. This means that the containing classes can be accessed externally. You can see an example of this earlier when two members of the Car class _color (an attribute defined in the class) and color (an argument defined in constructor) are returned. Sometimes we want to provide access to both members, but often we want to control access to the raw data contained in the property by allowing access only through get or SET accessors.

You can also control access to method functions. For example, the Car class contains a function called worker that is called only by other method functions in the class. Calling this function directly from outside the class can cause unexpected results.

In TypeScript, you can control the visibility of class members by prefacing their names with public, private, or protected keywords.

It’s not exactly like Java, it’s very similar

In TypeScript, you can control the visibility of class members by prefacing their names with public, private, or protected keywords.

Access modifier instructions
public If no access modifier is specified, the default is public. You can also usepublicThe keyword explicitly sets the member to public.
private If you are usingprivateKeyword modifies a member that cannot be accessed from outside the class it contains.
protected protectedThe function of the modifier andprivateModifiers are very similar, but declarations can also be accessed in derived classesprotectedA member of.

TypeScript is a structured type system. When comparing two different types, regardless of where they come from, the types themselves can be considered compatible if the types of all members are compatible. However, when comparing types with private and protected members, these types are treated differently. For two types that are considered compatible, if one has a private member, the other must have a private member from the same declaration. The same applies to protected members.

In addition, you can set a property to readonly by using the Readonly modifier. The readonly property can only be set when it is declared or initialized in constructor.

Apply access modifiers to classes

Without setting property private before:

Defining static properties

The properties and methods of the class currently defined are instance properties, which means that they will be instantiated and invoked in each instance of the class object. There is another type of property called a static property. Static properties and methods are shared by all instances of the class.

To set the property to static, use the static keyword before the property or method name.

You can add a new static property to the Car class named numberOfCars, which stores the number of Car instances and sets its initial value to 0. Then, in the constructor, increment the count by one.

Your code might look something like this:

TypeScript copy

class Car {
    // Properties
    private static numberOfCars: number = 0;  // New static property
    private _make: string;
    private _color: string;
    private _doors: number;

    // Constructor
    constructor(make: string, color: string, doors = 4) {
        this._make = make;
        this._color = color;
        this._doors = doors;
        Car.numberOfCars++; // Increments the value of the static property
    }
    // ...
}
Copy the code

Note that when accessing static properties, use the syntax classname.propertyName instead of this.

You can also define static methods. You can call the getNumberOfCars method to return the value of numberOfCars.

public static getNumberOfCars(): number {
    return Car.numberOfCars;
}
Copy the code

Instantiate the Car class as usual, then use the syntax car.getCars () to return the number of instances.

// Instantiate the Car object with all parameters
let myCar1 = new Car('Cool Car Company'.'blue'.2);
// Instantiates the Car object with all parameters
let myCar2 = new Car('Galaxy Motors'.'blue'.2);
// Returns 2
console.log(Car.getNumberOfCars());
Copy the code

Extend classes using inheritance

Through inheritance, relationships can be established and hierarchies of classes in an object composition can be generated

Some reasons to use inheritance include:

  • Code reusability. It can be developed once and reused in many places. In addition, this helps avoid redundancy in your code.
  • You can use a base class to derive any number of subclasses in a hierarchy. For example,CarSubclasses in the hierarchy can also includeSUVClass orConvertibleClass.
  • Instead of changing code in many different classes with similar functionality, you only need to change it once in the base class.

For example, you can extend Car class to create a new class called ElectricCar. The ElectricCar class will inherit the properties and methods of the Car class, but can also have its own unique properties and behaviors, such as range and Charge. Therefore, by extending the Car class, you can create new classes that reuse code from the Car class and then build on top of it. The Car class includes attribute brands, colors and doors, and methods for acceleration, braking, and steering. When the ElectricCar class extends Car, it includes all of the Car’s properties and methods, as well as a new property called “gear” and a new method called “charge.” ElectricCar is a subclass that derives from the Car base class using the extends keyword. (Base classes are also called superclasses or superclasses.) Because ElectricCar extends the capabilities of Car, you can create an ElectricCar instance that supports accelerate, Brake, and TURN. If you need to make a change to the code in the base class, you simply change it in the Car class, and all Car subclasses inherit the changes.

Overriding methods

When a derived class has a different definition of a member function of the base class, it is considered to be overriding the base function. Overwriting occurs when you create a function in a subclass that has the same name as the function in the base class, but has a different function.

For example, suppose that electric cars use a different braking system than conventional cars (called regenerative braking). Therefore, you may wish to override the Brake method in the Car base class using methods specific to the ElectricCar subclass.

The extension class

  1. Under Car, create a new class called ElectricCar that extends Car.

    It’s almost like Java

    class ElectricCar extends Car {
        // Properties unique to ElectricCar
    ​
        // Constructor
    ​
        // Accessors
    ​
        // Methods
    ​
    }
    Copy the code
  2. Declare the ElectricCar class’s unique attribute _range as a private attribute of type number.

    // Properties
    private _range: number;
    Copy the code
  3. The subclass Constructor differs from the base class Constructor in some ways.

    • The argument list can contain any attribute of the base class and subclass. (As with all argument lists in TypeScript, remember that required arguments must appear before optional ones.)
    • inconstructorIn the body, you have to addsuper()Keyword to include arguments from the base class.superThe keyword executes the base class at runtimeconstructor.
    • When referring to a property in a subclass,superKeywords must appear in pairsthis.Any references before.
  4. Define the constructor class for ElectricCar, including the _make, _color, and _DOORS attributes of the base class and the _range attribute of the subclass. In Constructor, set the default value for the DOORS parameter to 2.

    // Constructor
    constructor(make: string, color: string, range: number, doors = 2) {
        super(make, color, doors);
        this._range = range;
    }
    Copy the code
  5. Define get and set accessors for range parameters.

    // Accessors
    get range() {
        return this._range;
    }
    set range(range) {
        this._range = range;
    }
    Copy the code
  6. Enter the following charge method to return the message to the console. This method includes a call to the worker function defined in the Car class. But it raises the error “The property” worker “is private and can only be accessed in class” Car “. Do you know how to fix this problem?

    // Methods
    charge() {
        console.log(this.worker() + " is charging.")
    }
    Copy the code
  7. In the Car class, change the access modifier for the Worker function from private to protected. This allows a subclass of Car to use this function while keeping members that can access objects instantiated from the class hidden. The error in the charge method should now be resolved.

  8. Test the new ElectricCar class to verify that it works as expected.

    let spark = new ElectricCar('Spark Motors','silver', 124, 2);
    let eCar = new ElectricCar('Electric Car Co.', 'black', 263);
    console.log(eCar.doors);         // returns the default, 2
    spark.charge();                  // returns "Spark Motors is charging"
    Copy the code
  9. Define the new Brake method in the ElectricCar class, which has different implementation details. Note that the parameter signature and return type of the Brake method must be the same as the Brake method in the Car class.

    // Overrides the brake method of the Car class
    brake(): string {
        return `${this.worker()}  is braking with the regenerative braking system.`
    }
    Copy the code
  10. Test the new method and verify that it works as expected.

    console.log(spark.brake());  // returns "Spark Motors is braking with the regenerative braking system"
    Copy the code

Declare an interface to ensure that the class conforms to the specification

Interfaces are conventions. In Typescript, interfaces are used to establish “code conventions” that describe the required properties of objects and their types. Therefore, you can use interfaces to ensure class instance shapes. A class declaration can reference one or more interfaces in its implements clause to verify that they provide an implementation of the interface.

  1. Declares a Vehicle interface that describes the properties and methods of the Car class.

    interface Vehicle {
        make: string;
        color: string;
        doors: number;
        accelerate(speed: number): string;
        brake(): string;
        turn(direction: 'left' | 'right'): string;
    }
    Copy the code
  2. Note that this interface contains constructor parameters, not properties. Try including one of the private attributes (such as _make: string). TypeScript raises errors because interfaces can only describe the public aspects of a class, not private members. This prevents you from using them to check that the private end of a class instance also has the correct type.

  3. You can now implement the Vehicle interface in the Car class. When you generate class details, TypeScript ensures that the class follows the code conventions described in the interface.

    class Car implements Vehicle {
        // ...
    }
    Copy the code

TypeScript provides several key methods for defining object structures-classes and interfaces

When to use an interface

Interfaces are a TypeScript design-time construct. Because JavaScript has no interface concepts, they are removed when TypeScript is translated to JavaScript. This means that they are completely insignificant, take up no space in the generated files, and have no negative impact on the code to be executed.

Unlike other programming languages, where interfaces can only be used with classes, TypeScript allows you to define data structures using interfaces rather than classes. You can use interfaces to define function parameter objects, define the structure of various framework properties, and define the appearance of the object in a remote service or API.

For example, if the relevant data is used to store information about dogs, an interface like this could be created:

interface Dog { id? : number; name: string; age: number; description: string; }Copy the code

This interface can be used in shared modules of client and server code to ensure that data structures are the same on both ends. On the client side, you might have code to retrieve the dog from the defined server API, as follows:

async loadDog(id: number): Dog {
    return await (await fetch('demoUrl')).json() as Dog;
}
Copy the code

LoadDog lets TypeScript know the structure of an object by using interfaces. You don’t need to create classes to ensure this.

When to use classes

The main difference between interfaces and classes in any programming language is that classes allow you to define implementation details. Interfaces only define the structure of the data. Classes allow you to define methods, fields, and properties. Class also provides methods to template objects to define default values.

Going back to the example above, on the server, you might want to add code to load or save the dog to the database. A common way to manage data in a database is to use something called “active record mode,” which means that the objects themselves have save, load, and similar methods. We can use the Dog interface defined above to ensure that we have the same properties and structure, while adding the code needed to perform the operation.

class DogRecord implements Dog {
    id: number;
    name: string;
    age: number;
    description: string;
​
    constructor({name, age, description, id = 0}: Dog) {
        this.id = id;
        this.name = name;
        this.age = age;
        this.description = description;
    }
​
    static load(id: number): DogRecord {
        // code to load dog from database
        return dog;
    }
​
    save() {
        // code to save dog to database
    }
}
Copy the code

conclusion

As you continue to work with TypeScript, you’ll find many new instances, especially interfaces, that make your development life easier. A key feature of TypeScript about interfaces is that it doesn’t need classes. This allows you to use data structures when you need to define them without having to create a full class implementation.