248 lines
		
	
	
	
		
			7 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
			
		
		
	
	
			248 lines
		
	
	
	
		
			7 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
---
 | 
						|
 | 
						|
tags: [typescript, OOP]
 | 
						|
---
 | 
						|
 | 
						|
# Classes
 | 
						|
 | 
						|
## Type declarations for classes
 | 
						|
 | 
						|
TypeScript offers full type annotations for classes. It also introduces several
 | 
						|
TypeScript-specific options (control access modifiers, interfaces etc) that do
 | 
						|
not exist in JavaScript but which seek to bring it into closer alignment with
 | 
						|
more strict object-oriented languages like Java and C#.
 | 
						|
 | 
						|
A class in JavaScript:
 | 
						|
 | 
						|
```js
 | 
						|
class Age {
 | 
						|
  constructor(name, birthYear) {
 | 
						|
    this.name = name;
 | 
						|
    this.birthYear = birthYear;
 | 
						|
  }
 | 
						|
  currentYear() {
 | 
						|
    return new Date().getFullYear();
 | 
						|
  }
 | 
						|
  get age() {
 | 
						|
    return this.currentYear() - this.birthYear;
 | 
						|
  }
 | 
						|
  get dataOutput() {
 | 
						|
    return `${this.personName} is ${this.age} years old`;
 | 
						|
  }
 | 
						|
}
 | 
						|
```
 | 
						|
 | 
						|
The same class in TypeScript:
 | 
						|
 | 
						|
```ts
 | 
						|
class Age {
 | 
						|
  personName: string;
 | 
						|
  birthYear: number;
 | 
						|
  constructor(personName: string, birthYear: number) {
 | 
						|
    this.personName = personName;
 | 
						|
    this.birthYear = birthYear;
 | 
						|
  }
 | 
						|
  currentYear(): number {
 | 
						|
    return new Date().getFullYear();
 | 
						|
  }
 | 
						|
  get age(): number {
 | 
						|
    return this.currentYear() - this.birthYear;
 | 
						|
  }
 | 
						|
  get dataOutput(): string {
 | 
						|
    return `${this.personName} is ${this.age} years old`;
 | 
						|
  }
 | 
						|
}
 | 
						|
```
 | 
						|
 | 
						|
The main points to note are:
 | 
						|
 | 
						|
- methods must specify their return type, as with [functions](Functions.md)
 | 
						|
- the constructor function must specify its parameters' types
 | 
						|
- we must declare the types of any properties we intend to use at the start of
 | 
						|
  the class.
 | 
						|
 | 
						|
### Instantiating a class
 | 
						|
 | 
						|
In order to create an object instance of `Age`, we can use the standard
 | 
						|
constructor function, viz:
 | 
						|
 | 
						|
```js
 | 
						|
const mum = new Age("Mary Jo", 1959);
 | 
						|
console.log(mum);
 | 
						|
 | 
						|
/* Age { personName: 'Mary Jo', birthYear: 1959 } */
 | 
						|
```
 | 
						|
 | 
						|
But given that classes define objects, we can also now use `Age` as a new custom
 | 
						|
type and define an object that way
 | 
						|
 | 
						|
```jsx
 | 
						|
const thomas: Age = new Age("Thomas", 1988);
 | 
						|
```
 | 
						|
 | 
						|
### Without constructor
 | 
						|
 | 
						|
If your class does not use a constructor, you still need to define your class
 | 
						|
property types at the top:
 | 
						|
 | 
						|
```tsx
 | 
						|
class Dummy {
 | 
						|
  aNum: number = 4;
 | 
						|
  get getSquare(): number {
 | 
						|
    return this.aNum * this.aNum;
 | 
						|
  }
 | 
						|
}
 | 
						|
```
 | 
						|
 | 
						|
## Interfaces
 | 
						|
 | 
						|
In most cases the difference between using the `type` and `interface` keywords
 | 
						|
when defining a custom type is marginal however interfaces are specifically
 | 
						|
designed for classes and OOP style programming in TypeScript. This is obviously
 | 
						|
most apparent in a framework like Angular where interfaces are used heavily.
 | 
						|
 | 
						|
When we use an interface with a class we are asserting that the class must have
 | 
						|
certain properties and methods in order to qualify as that type. This is most
 | 
						|
helpful when you are working with several developers and want to ensure
 | 
						|
consistency.
 | 
						|
 | 
						|
Let's say we have the following interface:
 | 
						|
 | 
						|
```ts
 | 
						|
interface Person {
 | 
						|
  firstName: string;
 | 
						|
  secondName: string;
 | 
						|
  age: number;
 | 
						|
  employed: () => boolean;
 | 
						|
}
 | 
						|
```
 | 
						|
 | 
						|
Now we want to create a class that must share this shape. We go ahead and create
 | 
						|
the class and say that it **implements** `Person` :
 | 
						|
 | 
						|
```ts
 | 
						|
class Programmer implements Person {
 | 
						|
// If the below are not included, TS will generate an error
 | 
						|
	firstName: string,
 | 
						|
	secondName: string,
 | 
						|
	age: number,
 | 
						|
	employed: () => boolean
 | 
						|
}
 | 
						|
```
 | 
						|
 | 
						|
## Inheritance
 | 
						|
 | 
						|
We can extend classes in TypeScript by using sub-classes or abstract classes.
 | 
						|
 | 
						|
### Sub-classes
 | 
						|
 | 
						|
In the case of sub-classes, we use the phrase
 | 
						|
`[child_class] extends [parent_class]` in the class declaration to designate the
 | 
						|
inheritance relationship between the base class and the new class that is
 | 
						|
derived from it.
 | 
						|
 | 
						|
> A derived class has all the properies and methods of its base class but can
 | 
						|
> also define additional members.
 | 
						|
 | 
						|
When you instantiate a child class from a parent class, if the parent class has
 | 
						|
constructor values, you must initialise these in the child. You do this by
 | 
						|
calling the parent constructor via the `super()` syntax. For example:
 | 
						|
 | 
						|
```ts
 | 
						|
class Employee extends Person {
 | 
						|
  constructor(firstName: string, lastName: string, private jobTitle: string) {
 | 
						|
    // call the constructor of the Person class:
 | 
						|
    super(firstName, lastName);
 | 
						|
  }
 | 
						|
}
 | 
						|
```
 | 
						|
 | 
						|
To override or alter a method that exists on the parent in the child, you can
 | 
						|
use the syntax `super.[methodName]()`.
 | 
						|
 | 
						|
### Abstract classes
 | 
						|
 | 
						|
Classes marked `abstract` are similar to parent in the case of sub-classes. The
 | 
						|
difference is that they are like templates. Several of their methods and
 | 
						|
properties may be directly inherited by classes that derive from them (just like
 | 
						|
sub-classes) but they can include 'blank' methods and properties that are
 | 
						|
placeholders for methods and properties that are defined in the derivation
 | 
						|
class.
 | 
						|
 | 
						|
I have found this useful for cases where you want to inherit methods from a
 | 
						|
parent class but implement a specific method differently in each derivation.
 | 
						|
 | 
						|
```ts
 | 
						|
export abstract class IndexHyperlinksProvider
 | 
						|
   implements vscode.TreeDataProvider<TreeItem>
 | 
						|
{
 | 
						|
   public activeFile: string | undefined
 | 
						|
   private outlinks
 | 
						|
   private fileSystemUtils: FileSystemUtils
 | 
						|
 | 
						|
   constructor(
 | 
						|
      activeFile: string | undefined,
 | 
						|
      workspaceRoot: string | undefined
 | 
						|
   ) {
 | 
						|
      this.setActiveFile(activeFile)
 | 
						|
      this.outlinks = this.generateLinks()
 | 
						|
      this.fileSystemUtils = new FileSystemUtils(workspaceRoot)
 | 
						|
   }
 | 
						|
 | 
						|
   abstract generateLinks(): Promise<TreeItem[] | undefined>
 | 
						|
 | 
						|
   public setActiveFile(activeFile: string | undefined) {
 | 
						|
      this.activeFile = activeFile
 | 
						|
   }
 | 
						|
  ...
 | 
						|
```
 | 
						|
 | 
						|
Above we have a base class with a single abstract method `generateLinks()`. The
 | 
						|
class below extends this base. Note that it passes the parent constructor values
 | 
						|
to `super` and defines an actual method for the designated `generateLinks()`
 | 
						|
template:
 | 
						|
 | 
						|
```ts
 | 
						|
export class IndexOutlinksProvider extends IndexHyperlinksProvider {
 | 
						|
  public workspaceFiles: string[];
 | 
						|
  public context: vscode.ExtensionContext;
 | 
						|
  constructor(
 | 
						|
    activeFile: string | undefined,
 | 
						|
    workspaceRoot: string | undefined,
 | 
						|
    workspaceFiles: string[],
 | 
						|
    context: vscode.ExtensionContext
 | 
						|
  ) {
 | 
						|
    super(activeFile, workspaceRoot);
 | 
						|
    this.workspaceFiles = workspaceFiles;
 | 
						|
    this.context = context;
 | 
						|
  }
 | 
						|
 | 
						|
  public async generateLinks(): Promise<TreeItem[] | undefined> {
 | 
						|
    const indexer = new IndexHyperlinks(this.context, this.workspaceFiles);
 | 
						|
    if (typeof this.activeFile === "string") {
 | 
						|
      const outlinks = await indexer.indexOutlinks(this.activeFile);
 | 
						|
      if (outlinks !== undefined) {
 | 
						|
        return this.transformLinksToTreeItem(outlinks);
 | 
						|
      }
 | 
						|
    }
 | 
						|
    return;
 | 
						|
  }
 | 
						|
}
 | 
						|
```
 | 
						|
 | 
						|
> Importantly, you cannot instantiate abstract classes. You can only `extend`
 | 
						|
> them and then instantiate their derivation. This is another important
 | 
						|
> difference from sub-classes.
 | 
						|
 | 
						|
As with sub-classes, you must initialise the properties of the parent
 | 
						|
constructor with `super`.
 | 
						|
 | 
						|
### `implements` vs `extends`
 | 
						|
 | 
						|
You shouldn't confuse `implements` with `extends`. `implements` just checks the
 | 
						|
class as an interface in accordance with the principles of
 | 
						|
[duck typing](Custom_types_in_TypeScript.md#duck-typing):
 | 
						|
i.e the implementing class should have the same properties and methods. It
 | 
						|
doesn't affect anything internal to the methods or properties. So e.g, if you
 | 
						|
typed a method parameter as `string` in the base class, this would still default
 | 
						|
to `any` in the derived class.
 |