TypeScript Classes
From SCRUM-39: Implement DictionaryAdapter
My Learning Notes
TypeScript class is pretty similar to JavaScript class. It's actually more advanced with more features:
- Ability to specify the type of the variable
- Ability to specify that the value will be assigned to the variable using definite assignment (
!:)
When to Use Class vs Interface
Interface = "what fields does this object have?" (compile-time only, no JS generated)
Class = "what fields AND what can this object do?" (generates real JS code)
// Interface - just data, no behavior
interface Word {
id: string;
word: string;
definition: string;
}
const myWord: Word = { id: "1", word: "hello", definition: "greeting" };
// Class - has methods with real code
class FreeDictionaryAdapter {
private userId: string;
constructor(userId: string) {
this.userId = userId;
}
async lookup(word: string) {
// actual implementation
}
toString() {
return `Adapter for user ${this.userId}`;
}
}
const adapter = new FreeDictionaryAdapter("123");
In VocabPal:
Word,Category,User→ interfaces (just data, no behavior)FreeDictionaryAdapter→ class (haslookup()method with real code)
Summary: Use class when you want to encapsulate methods/behavior inside the object.
Access Modifiers
TypeScript has access modifiers like Java:
class Example {
public name: string; // accessible anywhere
private secret: string; // only inside this class
protected inherited: string; // inside class and subclasses
}
| Modifier | Inside Class | Subclass | Outside |
|---|---|---|---|
public | ✅ | ✅ | ✅ |
protected | ✅ | ✅ | ❌ |
private | ✅ | ❌ | ❌ |
TS/JS vs Java Encapsulation
In Java, encapsulation is done by using class with access modifier for each field or method.
In TS/JS, it's 2 layers: by using class and by using module import/export feature.
Modules in TS/JS act like packages in Java. Since modules have import/export as access mechanism, we don't need "default" access modifier like Java.
Definite Assignment Assertion
Use !: when you know a property will be assigned (but TypeScript can't verify):
class OKGreeter {
// Not initialized, but we promise it will be
name!: string;
}
strictPropertyInitialization
By default, tsconfig.json sets "strictPropertyInitialization": false. To enable strict checks:
{
"compilerOptions": {
"strict": true // Bundles strictPropertyInitialization with other checks
}
}
With strict mode:
- Without
strictNullChecks: TS ignores undefined, no property errors - With both enabled: TS catches uninitialized = possibly undefined properties
Extends vs Implements
extends - Inheritance
Inherits ALL properties + methods + constructor
class Animal {
name: string;
move() { console.log("Moving"); }
}
class Dog extends Animal {
bark() { console.log("Woof!"); }
}
const dog = new Dog();
dog.name = "Rex"; // inherited
dog.move(); // inherited
dog.bark(); // own method
implements - Contract Fulfillment
Must MATCH interface shape (no inheritance)
interface DictionaryAdapter {
lookup(word: string): Promise<Word | null>;
}
class FreeDictionaryAdapter implements DictionaryAdapter {
async lookup(word: string): Promise<Word | null> {
// MUST implement this method
}
}
Key Difference
extends= "I inherit from you" (get code for free)implements= "I promise to have these methods" (must write them yourself)
Interfaces CAN Declare Method Signatures
Interfaces can declare method signatures - they just cannot have the implementation (the actual code).
// ✅ Valid - interface declares the method signature (no body)
interface DictionaryAdapter {
lookup(word: string): Promise<Word | null>;
}
// ✅ The CLASS provides the implementation (with body)
class FreeDictionaryAdapter implements DictionaryAdapter {
async lookup(word: string): Promise<Word | null> {
// actual code goes here
const apiResponse = await fetchWordDefinition(word);
// ...
}
}
Think of it this way:
- Interface = "Here are the methods you MUST have" (the contract)
- Class = "Here is HOW those methods actually work" (the implementation)
The implements keyword says "this class promises to have all the methods declared in that interface."
What's Allowed in TypeScript
| Statement | Allowed? |
|---|---|
interface extends class | ✅ (copies shape) |
interface extends interface(s) | ✅ |
class extends class | ✅ |
class implements interface | ✅ |
class implements class | ✅ (checks structure) |
Extends in Java vs TypeScript
Java (nominal typing):
- Classes and interfaces are separate "family trees"
extends= family inheritance (must be same family)- You ARE what you're declared as
TypeScript (structural typing):
- No strict family trees
extends= copy the shape/code- You ARE what you look like
// TypeScript doesn't care about "family"
class Point { x: number; y: number; }
// Anything with x and y is compatible
const p: Point = { x: 1, y: 2 }; // ✅ plain object works!
// Java cares about family
class Point { int x; int y; }
Point p = new Point(); // ✅
Point p = someRandomObject; // ❌ even if it has x and y
"Family passing down" vs "copying shape" is a good way to remember the difference between Java's inheritance and TypeScript's inheritance.
VocabPal Example: Adapter Pattern
// Interface defines the contract
export interface DictionaryAdapter {
lookup(word: string): Promise<Omit<Word, "id"|"createdAt"|"updatedAt"> | null>;
}
// Class implements the contract
export class FreeDictionaryAdapter implements DictionaryAdapter {
private userId: string;
private sourceUrl?: string;
constructor(userId: string, sourceUrl?: string) {
this.userId = userId;
this.sourceUrl = sourceUrl;
}
async lookup(word: string): Promise<Omit<Word, "id"|"createdAt"|"updatedAt"> | null> {
const apiResponse = await fetchWordDefinition(word);
if (!apiResponse) return null;
return this.transformToWord(apiResponse, word);
}
private transformToWord(apiResponse: any, originalWord: string) {
return {
word: apiResponse.word || originalWord,
definition: apiResponse.meanings?.[0]?.definitions?.[0]?.definition || '',
userId: this.userId,
sourceUrl: this.sourceUrl,
};
}
}
Notes from SCRUM-39
Synced from Jira on 2026-04-04
In Java, an instance of Parent type can be of Child class. Parent X = new Child(). X calls Parent’s fields and has Child’s behaviours/methods. This way we say Child modifies Parent’s methods/behaviour (Polymorphism). To force X to call Child’s fields ( include those not exist in Parent) we need to downcast X : Child X_down = (Child) X. Note that here we need to do type-check for X to ensure X is of Parent type before downcasting otherwise we will get ClassCastException
In JavaScript/TS, X always declared with Parent type, and it calls Child’s fields and methods without the need of downcast. However, this way it can only call fields those are co-exist in both Parent and Child class. Hence, we downcast X : const X_down = X as Child. → At runtime though X is not child, no error is thrown as TS trust the declaration “as Child”
Subclass in TS is structural, it just needs to have Parent’s structure of the class then we can assign const p: ParentLike = new Something()
“Extend” keyword is used as a quick way to copy parent’s structure in a systematic way that it has prototype chain. This helps save the memory because by “extend”-ing, we have parent’s structure without creating a real copy, it’s more like referencing. Chaining also allows the use of “super()” method.
Answer: Interfaces CAN declare method signatures
In TypeScript, an interface can declare method signatures - it just cannot have the implementation (the actual code).
// ✅ Valid - interface declares the method signature (no body)
interface DictionaryAdapter {
lookup(word: string): Promise<Omit<Word, "id"|"createdAt"|"updatedAt">>;
}
// ✅ The CLASS provides the implementation (with body)
class FreeDictionaryAdapter implements DictionaryAdapter {
async lookup(word: string): Promise<Omit<Word, "id"|"createdAt"|"updatedAt">> {
// actual code goes here
const apiResponse = await fetchWordDefinition(word);
// ...
}
}
Think of it this way:
- Interface = "Here are the methods you MUST have" (the contract)
- Class = "Here is HOW those methods actually work" (the implementation)
The implements keyword says "this class promises to have all the methods declared in that interface." The adapter pattern relies on interfaces to define contracts that multiple classes can implement.
Notes: how to use “implements” and “extends”?
- Extends : interface has shape, class has shape and code. Extends is a way to copy either shape or both shape and code. Hence, interface can extend a class to copy the shape of the class, but class cannot extend an interface because interface has no code for the class to copy. In short, these are allowed: interface extends class, class extends class, class extends interface
- Implements: It’s like a promise to follow a certain shape. This can only be used by class. In short, these are allowed: class implements class, class implements interface
**Note: when to use class and when to use interface? **
Interface = "what fields does this object have?" (compile-time only, no JS generated)
interface Word {
id: string;
word: string;
definition: string;
}
const myWord: Word = { id: "1", word: "hello", definition: "greeting" };
Class = "what fields AND what can this object do?" (generates real JS code)
class FreeDictionaryAdapter {
private userId: string;
constructor(userId: string) {
this.userId = userId;
}
async lookup(word: string) {
// actual implementation
}
toString() {
return `Adapter for user ${this.userId}`;
}
}
const adapter = new FreeDictionaryAdapter("123");
In VocabPal:
Word,Category,User→ interfaces (just data, no behavior)FreeDictionaryAdapter→ class (haslookup()method with real code)
Summary: use class when you want to encapsulate methods/behavior inside the object.
Note: Extends keyword in Java vs Typescript
Java (nominal typing):
- Classes and interfaces are separate "family trees"
extends= family inheritance (must be same family)- You ARE what you're declared as
TypeScript (structural typing):
- No strict family trees
extends= copy the shape/code- You ARE what you look like
// TypeScript doesn't care about "family"
class Point { x: number; y: number; }
// Anything with x and y is compatible
const p: Point = { x: 1, y: 2 }; // ✅ plain object works
// Java cares about family
class Point { int x; int y; }
// Must be actual Point instance
Point p = new Point(); // ✅
Point p = someRandomObject; // ❌ even if it has x and y
"family passing down" vs "copying shape" is a good way to remember distinguishing between TS’s inheritance and Java inheritance.
This explains why interface extends class in Typescript while it’s not allowed in Java. Java’s interface can only extends other interface(s).
What’s allowed in Java:
- interface extends interface(s)
- class extends class
What’s allowed in TS:
- interface extends class
- class extends class
- interface extends interface(s)
Note: Implements keyword in Java vs Typescript
Implements keyword is used exclusively in Java to copy structure of interfaces (it means provide implementation of the interface) . While in TS, implements can be used for checking if the class has the implementation with the right structure (means a class that has the structure of that class or interface)
What allowed in Java:
- Class implements interface(s)
What allowed in TS:
- class implements class
- class implements interface
Code Review Feedback
Katie Nguyen (2026-02-05):
[Code Review - Changes Needed]
Issues Found
1. Missing interface declaration (Major) - The ticket asks for a DictionaryAdapter interface, but you created a class. The adapter pattern requires an interface that defines the contract, and a class that implements it.
Current: class DictionaryAdapter / class FreeDictionaryAdapter extends DictionaryAdapter
Should be: interface DictionaryAdapter / class FreeDictionaryAdapter implements DictionaryAdapter
2. Missing exports (Major) - Neither DictionaryAdapter nor FreeDictionaryAdapter are exported. Other code cannot use them.
3. Extra space in class name (Minor) - Line 24 has two spaces before FreeDictionaryAdapter.
4. No trailing newline (Minor)
What is Good
- The lookup() method logic is correct
- Good use of Omit for the return type
- Error handling for word not found
- Constructor takes userId and optional sourceUrl
Katie Nguyen (2026-02-05):
[Code Review - APPROVED]
All issues fixed: DictionaryAdapter interface created in types/index.ts, FreeDictionaryAdapter class uses implements keyword, both are exported, extra space removed.
Great job understanding the difference between interface (contract) and class (implementation). Ready to move to Done.
Key Takeaways
- Use interface for data shapes (no methods)
- Use class for objects with behavior (methods)
extends= inherit code,implements= fulfill contract- Interfaces declare method signatures, classes provide implementations
- TypeScript uses structural typing (shape matters, not family)
- Access modifiers:
public,private,protected - Use
!:for definite assignment when you know a property will be set