Prerequisites
- Understanding of TypeScript interfaces ๐
- Knowledge of generics basics ๐
- Familiarity with type parameters ๐ป
What you'll learn
- Create powerful generic interface contracts ๐ฏ
- Build type-safe API definitions ๐๏ธ
- Design flexible interface hierarchies ๐ก๏ธ
- Implement advanced generic patterns โจ
๐ฏ Introduction
Welcome to the world of generic interfaces! ๐ In this guide, weโll explore how to create flexible, parameterized contracts that define the shape of your data while adapting to any type.
Youโll discover how generic interfaces are like universal plug adapters ๐ - they provide a consistent connection interface while working with different types! Whether youโre designing APIs ๐, building libraries ๐, or creating complex type hierarchies ๐๏ธ, mastering generic interfaces is crucial for professional TypeScript development.
By the end of this tutorial, youโll be confidently creating generic interfaces that solve real-world problems elegantly! Letโs build some powerful contracts! ๐โโ๏ธ
๐ Understanding Generic Interfaces
๐ค Why Generic Interfaces?
Generic interfaces allow you to create flexible contracts that work with multiple types while maintaining type safety:
// โ Without generics - Limited flexibility
interface StringContainer {
value: string;
getValue(): string;
setValue(value: string): void;
}
interface NumberContainer {
value: number;
getValue(): number;
setValue(value: number): void;
}
// Duplicate interfaces for each type! ๐ข
// โ
With generics - One interface, infinite possibilities
interface Container<T> {
value: T;
getValue(): T;
setValue(value: T): void;
}
// Implementation for any type
class Box<T> implements Container<T> {
constructor(public value: T) {}
getValue(): T {
return this.value;
}
setValue(value: T): void {
this.value = value;
}
}
const stringBox = new Box<string>('hello');
const numberBox = new Box<number>(42);
const objectBox = new Box<{ name: string }>({ name: 'Alice' });
๐ก Generic Interface Syntax
Understanding the structure and patterns:
// ๐ฏ Basic generic interface
interface Pair<T, U> {
first: T;
second: U;
}
const coordinate: Pair<number, number> = { first: 10, second: 20 };
const keyValue: Pair<string, boolean> = { first: 'active', second: true };
// ๐๏ธ Generic methods in interfaces
interface Collection<T> {
items: T[];
add(item: T): void;
remove(item: T): boolean;
find(predicate: (item: T) => boolean): T | undefined;
map<U>(transform: (item: T) => U): U[];
}
// ๐ง Multiple type parameters with constraints
interface Dictionary<K extends string | number, V> {
get(key: K): V | undefined;
set(key: K, value: V): void;
has(key: K): boolean;
delete(key: K): boolean;
clear(): void;
size(): number;
}
// ๐จ Extending generic interfaces
interface ReadonlyDictionary<K extends string | number, V>
extends Dictionary<K, V> {
readonly [key: string]: V;
set: never; // Disable set method
delete: never; // Disable delete method
clear: never; // Disable clear method
}
๐ Advanced Generic Interface Patterns
๐จ Interface Composition
Building complex interfaces from simpler ones:
// ๐ฏ Base interfaces with generics
interface Identifiable<T> {
id: T;
}
interface Timestamped {
createdAt: Date;
updatedAt: Date;
}
interface Versionable {
version: number;
}
// ๐๏ธ Composed interface
interface Entity<ID> extends Identifiable<ID>, Timestamped, Versionable {
metadata?: Record<string, any>;
}
// Usage with different ID types
interface User extends Entity<number> {
username: string;
email: string;
}
interface Product extends Entity<string> {
name: string;
price: number;
sku: string;
}
// ๐ง Generic repository pattern
interface Repository<T extends Identifiable<any>, ID> {
findById(id: ID): Promise<T | null>;
findAll(): Promise<T[]>;
findWhere(predicate: (item: T) => boolean): Promise<T[]>;
create(item: Omit<T, 'id'>): Promise<T>;
update(id: ID, updates: Partial<T>): Promise<T | null>;
delete(id: ID): Promise<boolean>;
}
// Implementation
class UserRepository implements Repository<User, number> {
async findById(id: number): Promise<User | null> {
// Database query implementation
return null;
}
async findAll(): Promise<User[]> {
return [];
}
async findWhere(predicate: (user: User) => boolean): Promise<User[]> {
const allUsers = await this.findAll();
return allUsers.filter(predicate);
}
async create(userData: Omit<User, 'id'>): Promise<User> {
const id = Math.floor(Math.random() * 1000);
const now = new Date();
return {
id,
...userData,
createdAt: now,
updatedAt: now,
version: 1
};
}
async update(id: number, updates: Partial<User>): Promise<User | null> {
// Update implementation
return null;
}
async delete(id: number): Promise<boolean> {
return true;
}
}
๐ Functional Generic Interfaces
Creating type-safe functional patterns:
// ๐ฏ Generic function interfaces
interface Mapper<T, U> {
(item: T): U;
}
interface Reducer<T, U> {
(accumulator: U, current: T, index: number, array: T[]): U;
}
interface Predicate<T> {
(item: T): boolean;
}
interface Comparator<T> {
(a: T, b: T): number;
}
// ๐๏ธ Collection with functional methods
interface FunctionalCollection<T> {
map<U>(mapper: Mapper<T, U>): FunctionalCollection<U>;
filter(predicate: Predicate<T>): FunctionalCollection<T>;
reduce<U>(reducer: Reducer<T, U>, initial: U): U;
sort(comparator?: Comparator<T>): FunctionalCollection<T>;
find(predicate: Predicate<T>): T | undefined;
every(predicate: Predicate<T>): boolean;
some(predicate: Predicate<T>): boolean;
}
// Implementation
class ArrayList<T> implements FunctionalCollection<T> {
constructor(private items: T[]) {}
map<U>(mapper: Mapper<T, U>): ArrayList<U> {
return new ArrayList(this.items.map(mapper));
}
filter(predicate: Predicate<T>): ArrayList<T> {
return new ArrayList(this.items.filter(predicate));
}
reduce<U>(reducer: Reducer<T, U>, initial: U): U {
return this.items.reduce(reducer, initial);
}
sort(comparator?: Comparator<T>): ArrayList<T> {
const sorted = [...this.items];
if (comparator) {
sorted.sort(comparator);
}
return new ArrayList(sorted);
}
find(predicate: Predicate<T>): T | undefined {
return this.items.find(predicate);
}
every(predicate: Predicate<T>): boolean {
return this.items.every(predicate);
}
some(predicate: Predicate<T>): boolean {
return this.items.some(predicate);
}
}
// ๐ง Advanced functional interface
interface Pipeline<T> {
value: T;
pipe<U>(transform: (value: T) => U): Pipeline<U>;
tap(effect: (value: T) => void): Pipeline<T>;
unwrap(): T;
}
class PipelineImpl<T> implements Pipeline<T> {
constructor(public value: T) {}
pipe<U>(transform: (value: T) => U): Pipeline<U> {
return new PipelineImpl(transform(this.value));
}
tap(effect: (value: T) => void): Pipeline<T> {
effect(this.value);
return this;
}
unwrap(): T {
return this.value;
}
}
// Usage
const result = new PipelineImpl(5)
.pipe(n => n * 2)
.pipe(n => n + 1)
.tap(n => console.log('Current value:', n))
.pipe(n => n.toString())
.unwrap(); // "11"
๐ช Real-World Applications
๐ Generic API Interfaces
Building type-safe API contracts:
// ๐ฏ Generic HTTP response
interface ApiResponse<T> {
data?: T;
error?: {
code: string;
message: string;
details?: any;
};
metadata: {
timestamp: Date;
requestId: string;
duration: number;
};
}
// ๐๏ธ Paginated response
interface PaginatedResponse<T> extends ApiResponse<T[]> {
pagination: {
page: number;
pageSize: number;
totalItems: number;
totalPages: number;
hasNext: boolean;
hasPrevious: boolean;
};
}
// ๐ง RESTful API interface
interface RestApi<T extends { id: string | number }> {
getAll(params?: QueryParams): Promise<PaginatedResponse<T>>;
getById(id: T['id']): Promise<ApiResponse<T>>;
create(data: Omit<T, 'id'>): Promise<ApiResponse<T>>;
update(id: T['id'], data: Partial<T>): Promise<ApiResponse<T>>;
delete(id: T['id']): Promise<ApiResponse<void>>;
}
interface QueryParams {
page?: number;
pageSize?: number;
sort?: string;
order?: 'asc' | 'desc';
filter?: Record<string, any>;
}
// Implementation
class UserApiService implements RestApi<User> {
private baseUrl = '/api/users';
async getAll(params?: QueryParams): Promise<PaginatedResponse<User>> {
const response = await fetch(`${this.baseUrl}?${this.buildQuery(params)}`);
return response.json();
}
async getById(id: number): Promise<ApiResponse<User>> {
const response = await fetch(`${this.baseUrl}/${id}`);
return response.json();
}
async create(userData: Omit<User, 'id'>): Promise<ApiResponse<User>> {
const response = await fetch(this.baseUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(userData)
});
return response.json();
}
async update(id: number, updates: Partial<User>): Promise<ApiResponse<User>> {
const response = await fetch(`${this.baseUrl}/${id}`, {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(updates)
});
return response.json();
}
async delete(id: number): Promise<ApiResponse<void>> {
const response = await fetch(`${this.baseUrl}/${id}`, {
method: 'DELETE'
});
return response.json();
}
private buildQuery(params?: QueryParams): string {
if (!params) return '';
const query = new URLSearchParams();
Object.entries(params).forEach(([key, value]) => {
if (value !== undefined) {
query.append(key, String(value));
}
});
return query.toString();
}
}
๐ฎ State Management Interfaces
Generic interfaces for state management:
// ๐ฏ Generic store interface
interface Store<S> {
getState(): S;
setState(updater: S | ((prev: S) => S)): void;
subscribe(listener: (state: S) => void): () => void;
reset(): void;
}
// ๐๏ธ Slice interface for modular state
interface StateSlice<T, S> {
name: keyof S;
initialState: T;
reducers: {
[K: string]: (state: T, payload?: any) => T;
};
selectors?: {
[K: string]: (state: T) => any;
};
}
// ๐ง Action interface
interface Action<T = any> {
type: string;
payload?: T;
meta?: {
timestamp: Date;
[key: string]: any;
};
}
// ๐จ Enhanced store with middleware
interface EnhancedStore<S> extends Store<S> {
dispatch(action: Action): void;
addMiddleware(middleware: Middleware<S>): void;
createSlice<K extends keyof S>(slice: StateSlice<S[K], S>): void;
}
interface Middleware<S> {
(store: Store<S>): (next: (action: Action) => void) => (action: Action) => void;
}
// Implementation
class StoreImpl<S> implements EnhancedStore<S> {
private state: S;
private listeners = new Set<(state: S) => void>();
private middlewares: Middleware<S>[] = [];
private slices = new Map<keyof S, StateSlice<any, S>>();
constructor(initialState: S) {
this.state = initialState;
}
getState(): S {
return this.state;
}
setState(updater: S | ((prev: S) => S)): void {
const newState = typeof updater === 'function'
? (updater as (prev: S) => S)(this.state)
: updater;
this.state = newState;
this.notifyListeners();
}
subscribe(listener: (state: S) => void): () => void {
this.listeners.add(listener);
return () => this.listeners.delete(listener);
}
reset(): void {
// Reset to initial state
const initialState = {} as S;
this.slices.forEach((slice, name) => {
initialState[name] = slice.initialState;
});
this.setState(initialState);
}
dispatch(action: Action): void {
// Apply middleware chain
let dispatch = (action: Action) => this.applyAction(action);
for (const middleware of this.middlewares.reverse()) {
const next = dispatch;
dispatch = middleware(this)(next);
}
dispatch(action);
}
addMiddleware(middleware: Middleware<S>): void {
this.middlewares.push(middleware);
}
createSlice<K extends keyof S>(slice: StateSlice<S[K], S>): void {
this.slices.set(slice.name, slice);
}
private applyAction(action: Action): void {
const [sliceName, reducerName] = action.type.split('/');
const slice = this.slices.get(sliceName as keyof S);
if (slice && slice.reducers[reducerName]) {
const newSliceState = slice.reducers[reducerName](
this.state[slice.name],
action.payload
);
this.setState({
...this.state,
[slice.name]: newSliceState
});
}
}
private notifyListeners(): void {
this.listeners.forEach(listener => listener(this.state));
}
}
// Usage example
interface AppState {
user: UserState;
products: ProductState;
}
interface UserState {
currentUser: User | null;
isLoading: boolean;
}
interface ProductState {
items: Product[];
selectedId: string | null;
}
const store = new StoreImpl<AppState>({
user: { currentUser: null, isLoading: false },
products: { items: [], selectedId: null }
});
// Create slices
store.createSlice({
name: 'user',
initialState: { currentUser: null, isLoading: false },
reducers: {
setUser: (state, user: User) => ({ ...state, currentUser: user }),
setLoading: (state, loading: boolean) => ({ ...state, isLoading: loading })
}
});
๐ Type-Safe Event System
Generic interfaces for event handling:
// ๐ฏ Event map interface
interface EventMap {
[event: string]: any[];
}
// ๐๏ธ Generic event emitter
interface EventEmitter<T extends EventMap> {
on<K extends keyof T>(event: K, handler: (...args: T[K]) => void): void;
off<K extends keyof T>(event: K, handler: (...args: T[K]) => void): void;
once<K extends keyof T>(event: K, handler: (...args: T[K]) => void): void;
emit<K extends keyof T>(event: K, ...args: T[K]): void;
removeAllListeners(event?: keyof T): void;
}
// ๐ง Observable interface
interface Observable<T> {
subscribe(observer: Observer<T>): Subscription;
pipe<U>(...operators: Operator<any, any>[]): Observable<U>;
}
interface Observer<T> {
next(value: T): void;
error?(error: any): void;
complete?(): void;
}
interface Subscription {
unsubscribe(): void;
closed: boolean;
}
interface Operator<T, U> {
(source: Observable<T>): Observable<U>;
}
// ๐จ Typed event system implementation
class TypedEventEmitter<T extends EventMap> implements EventEmitter<T> {
private events = new Map<keyof T, Set<Function>>();
on<K extends keyof T>(event: K, handler: (...args: T[K]) => void): void {
if (!this.events.has(event)) {
this.events.set(event, new Set());
}
this.events.get(event)!.add(handler);
}
off<K extends keyof T>(event: K, handler: (...args: T[K]) => void): void {
this.events.get(event)?.delete(handler);
}
once<K extends keyof T>(event: K, handler: (...args: T[K]) => void): void {
const wrappedHandler = (...args: T[K]) => {
handler(...args);
this.off(event, wrappedHandler);
};
this.on(event, wrappedHandler);
}
emit<K extends keyof T>(event: K, ...args: T[K]): void {
this.events.get(event)?.forEach(handler => handler(...args));
}
removeAllListeners(event?: keyof T): void {
if (event) {
this.events.delete(event);
} else {
this.events.clear();
}
}
}
// Define application events
interface AppEvents {
login: [user: User];
logout: [];
error: [error: Error, context?: string];
dataUpdate: [type: string, data: any];
notification: [message: string, level: 'info' | 'warning' | 'error'];
}
const appEvents = new TypedEventEmitter<AppEvents>();
// Type-safe event handling
appEvents.on('login', (user) => {
console.log(`User ${user.username} logged in`);
});
appEvents.on('error', (error, context) => {
console.error(`Error in ${context || 'unknown'}: ${error.message}`);
});
appEvents.on('notification', (message, level) => {
if (level === 'error') {
console.error(`๐ด ${message}`);
} else if (level === 'warning') {
console.warn(`๐ก ${message}`);
} else {
console.log(`๐ต ${message}`);
}
});
// Emit events with type safety
appEvents.emit('login', { id: 1, username: 'alice', email: '[email protected]' } as User);
appEvents.emit('notification', 'System update completed', 'info');
๐ฎ Hands-On Exercise
Letโs build a generic caching system with interfaces!
๐ Challenge: Generic Cache Interface System
Create a flexible caching system that:
- Supports different cache strategies
- Provides type-safe cache operations
- Includes TTL (time-to-live) support
- Implements cache statistics
// Your challenge: Implement this cache system
interface CacheItem<T> {
value: T;
expires?: Date;
hits: number;
}
interface CacheStrategy<K, V> {
shouldEvict(key: K, item: CacheItem<V>): boolean;
onAccess(key: K, item: CacheItem<V>): void;
onEvict?(key: K, item: CacheItem<V>): void;
}
interface Cache<K, V> {
get(key: K): V | undefined;
set(key: K, value: V, ttl?: number): void;
has(key: K): boolean;
delete(key: K): boolean;
clear(): void;
size(): number;
stats(): CacheStats;
}
interface CacheStats {
hits: number;
misses: number;
evictions: number;
size: number;
hitRate: number;
}
// Example usage to support:
const cache = createCache<string, User>({
maxSize: 100,
strategy: 'lru', // or custom strategy
defaultTTL: 3600000 // 1 hour
});
cache.set('user:123', { id: 123, name: 'Alice' });
const user = cache.get('user:123');
const stats = cache.stats();
๐ก Solution
Click to see the solution
// ๐ฏ Complete cache system implementation
interface CacheItem<T> {
value: T;
expires?: Date;
hits: number;
lastAccessed: Date;
created: Date;
}
interface CacheStrategy<K, V> {
shouldEvict(key: K, item: CacheItem<V>, cache: Map<K, CacheItem<V>>): boolean;
onAccess(key: K, item: CacheItem<V>): void;
onEvict?(key: K, item: CacheItem<V>): void;
compareForEviction?(a: [K, CacheItem<V>], b: [K, CacheItem<V>]): number;
}
interface Cache<K, V> {
get(key: K): V | undefined;
set(key: K, value: V, ttl?: number): void;
has(key: K): boolean;
delete(key: K): boolean;
clear(): void;
size(): number;
stats(): CacheStats;
setStrategy(strategy: CacheStrategy<K, V>): void;
}
interface CacheStats {
hits: number;
misses: number;
evictions: number;
size: number;
hitRate: number;
averageHitsPerItem: number;
}
interface CacheConfig<K, V> {
maxSize?: number;
strategy?: 'lru' | 'lfu' | 'fifo' | CacheStrategy<K, V>;
defaultTTL?: number;
onEvict?: (key: K, value: V) => void;
}
// ๐๏ธ Cache strategies
class LRUStrategy<K, V> implements CacheStrategy<K, V> {
shouldEvict(key: K, item: CacheItem<V>, cache: Map<K, CacheItem<V>>): boolean {
return false; // Eviction handled by compareForEviction
}
onAccess(key: K, item: CacheItem<V>): void {
item.lastAccessed = new Date();
}
compareForEviction(a: [K, CacheItem<V>], b: [K, CacheItem<V>]): number {
return a[1].lastAccessed.getTime() - b[1].lastAccessed.getTime();
}
}
class LFUStrategy<K, V> implements CacheStrategy<K, V> {
shouldEvict(key: K, item: CacheItem<V>, cache: Map<K, CacheItem<V>>): boolean {
return false;
}
onAccess(key: K, item: CacheItem<V>): void {
item.hits++;
item.lastAccessed = new Date();
}
compareForEviction(a: [K, CacheItem<V>], b: [K, CacheItem<V>]): number {
const hitDiff = a[1].hits - b[1].hits;
if (hitDiff === 0) {
return a[1].lastAccessed.getTime() - b[1].lastAccessed.getTime();
}
return hitDiff;
}
}
class FIFOStrategy<K, V> implements CacheStrategy<K, V> {
shouldEvict(key: K, item: CacheItem<V>, cache: Map<K, CacheItem<V>>): boolean {
return false;
}
onAccess(key: K, item: CacheItem<V>): void {
item.lastAccessed = new Date();
}
compareForEviction(a: [K, CacheItem<V>], b: [K, CacheItem<V>]): number {
return a[1].created.getTime() - b[1].created.getTime();
}
}
// ๐ง Main cache implementation
class CacheImpl<K, V> implements Cache<K, V> {
private cache = new Map<K, CacheItem<V>>();
private strategy: CacheStrategy<K, V>;
private stats = {
hits: 0,
misses: 0,
evictions: 0
};
constructor(private config: CacheConfig<K, V>) {
this.strategy = this.getStrategy(config.strategy);
}
get(key: K): V | undefined {
const item = this.cache.get(key);
if (!item) {
this.stats.misses++;
return undefined;
}
// Check expiration
if (item.expires && item.expires < new Date()) {
this.delete(key);
this.stats.misses++;
return undefined;
}
// Update stats and strategy
this.stats.hits++;
item.hits++;
this.strategy.onAccess(key, item);
return item.value;
}
set(key: K, value: V, ttl?: number): void {
const effectiveTTL = ttl ?? this.config.defaultTTL;
const now = new Date();
const item: CacheItem<V> = {
value,
expires: effectiveTTL ? new Date(now.getTime() + effectiveTTL) : undefined,
hits: 0,
lastAccessed: now,
created: now
};
// Check if we need to evict
if (this.config.maxSize && this.cache.size >= this.config.maxSize && !this.cache.has(key)) {
this.evictOne();
}
this.cache.set(key, item);
}
has(key: K): boolean {
const item = this.cache.get(key);
if (!item) return false;
// Check expiration
if (item.expires && item.expires < new Date()) {
this.delete(key);
return false;
}
return true;
}
delete(key: K): boolean {
const item = this.cache.get(key);
if (item) {
this.strategy.onEvict?.(key, item);
this.config.onEvict?.(key, item.value);
}
return this.cache.delete(key);
}
clear(): void {
this.cache.clear();
this.stats = { hits: 0, misses: 0, evictions: 0 };
}
size(): number {
// Clean up expired items
this.cleanupExpired();
return this.cache.size;
}
stats(): CacheStats {
const size = this.size();
const totalAccess = this.stats.hits + this.stats.misses;
const totalHits = Array.from(this.cache.values())
.reduce((sum, item) => sum + item.hits, 0);
return {
hits: this.stats.hits,
misses: this.stats.misses,
evictions: this.stats.evictions,
size,
hitRate: totalAccess > 0 ? this.stats.hits / totalAccess : 0,
averageHitsPerItem: size > 0 ? totalHits / size : 0
};
}
setStrategy(strategy: CacheStrategy<K, V>): void {
this.strategy = strategy;
}
private getStrategy(strategy?: 'lru' | 'lfu' | 'fifo' | CacheStrategy<K, V>): CacheStrategy<K, V> {
if (!strategy || strategy === 'lru') {
return new LRUStrategy<K, V>();
}
if (strategy === 'lfu') {
return new LFUStrategy<K, V>();
}
if (strategy === 'fifo') {
return new FIFOStrategy<K, V>();
}
return strategy;
}
private evictOne(): void {
const entries = Array.from(this.cache.entries());
if (this.strategy.compareForEviction) {
entries.sort(this.strategy.compareForEviction);
}
const [keyToEvict] = entries[0];
if (keyToEvict !== undefined) {
this.delete(keyToEvict);
this.stats.evictions++;
}
}
private cleanupExpired(): void {
const now = new Date();
const keysToDelete: K[] = [];
this.cache.forEach((item, key) => {
if (item.expires && item.expires < now) {
keysToDelete.push(key);
}
});
keysToDelete.forEach(key => this.delete(key));
}
}
// ๐จ Factory function
function createCache<K, V>(config: CacheConfig<K, V> = {}): Cache<K, V> {
return new CacheImpl(config);
}
// ๐ซ Advanced cache with metrics
interface MetricsCache<K, V> extends Cache<K, V> {
getMetrics(): CacheMetrics;
enableMetrics(enabled: boolean): void;
}
interface CacheMetrics {
totalRequests: number;
cacheHitRate: number;
averageAccessTime: number;
memoryUsage: number;
topKeys: Array<{ key: K; hits: number }>;
}
class MetricsCacheImpl<K, V> extends CacheImpl<K, V> implements MetricsCache<K, V> {
private metricsEnabled = true;
private accessTimes: number[] = [];
private keyMetrics = new Map<K, number>();
get(key: K): V | undefined {
const start = Date.now();
const result = super.get(key);
if (this.metricsEnabled) {
this.accessTimes.push(Date.now() - start);
if (this.accessTimes.length > 1000) {
this.accessTimes.shift();
}
this.keyMetrics.set(key, (this.keyMetrics.get(key) || 0) + 1);
}
return result;
}
getMetrics(): CacheMetrics {
const stats = this.stats();
const topKeys = Array.from(this.keyMetrics.entries())
.sort((a, b) => b[1] - a[1])
.slice(0, 10)
.map(([key, hits]) => ({ key, hits }));
return {
totalRequests: stats.hits + stats.misses,
cacheHitRate: stats.hitRate,
averageAccessTime: this.accessTimes.length > 0
? this.accessTimes.reduce((a, b) => a + b, 0) / this.accessTimes.length
: 0,
memoryUsage: this.estimateMemoryUsage(),
topKeys
};
}
enableMetrics(enabled: boolean): void {
this.metricsEnabled = enabled;
if (!enabled) {
this.accessTimes = [];
this.keyMetrics.clear();
}
}
private estimateMemoryUsage(): number {
// Rough estimation
return this.size() * 100; // bytes per item estimate
}
}
// ๐ Test the implementation
interface User {
id: number;
name: string;
email: string;
}
async function testCacheSystem() {
console.log('=== Cache System Test ===\n');
// Create LRU cache
const userCache = createCache<string, User>({
maxSize: 3,
strategy: 'lru',
defaultTTL: 5000, // 5 seconds
onEvict: (key, value) => {
console.log(`Evicted user ${value.name} (${key})`);
}
});
// Add users
userCache.set('user:1', { id: 1, name: 'Alice', email: '[email protected]' });
userCache.set('user:2', { id: 2, name: 'Bob', email: '[email protected]' });
userCache.set('user:3', { id: 3, name: 'Charlie', email: '[email protected]' });
// Access pattern
console.log('User 1:', userCache.get('user:1')?.name);
console.log('User 2:', userCache.get('user:2')?.name);
console.log('User 1 again:', userCache.get('user:1')?.name);
// This should evict user:3 (least recently used)
userCache.set('user:4', { id: 4, name: 'David', email: '[email protected]' });
console.log('\nCache contents:');
console.log('Has user:1?', userCache.has('user:1'));
console.log('Has user:2?', userCache.has('user:2'));
console.log('Has user:3?', userCache.has('user:3')); // Should be false
console.log('Has user:4?', userCache.has('user:4'));
console.log('\nCache stats:', userCache.stats());
// Test with metrics
console.log('\n=== Metrics Cache Test ===\n');
const metricsCache = new MetricsCacheImpl<string, User>({
maxSize: 100,
strategy: 'lfu'
});
// Simulate usage
for (let i = 0; i < 20; i++) {
metricsCache.set(`user:${i}`, {
id: i,
name: `User${i}`,
email: `user${i}@example.com`
});
}
// Access pattern
for (let i = 0; i < 50; i++) {
const userId = Math.floor(Math.random() * 25);
metricsCache.get(`user:${userId}`);
}
console.log('Metrics:', metricsCache.getMetrics());
// Test TTL
console.log('\n=== TTL Test ===\n');
const ttlCache = createCache<string, string>({
defaultTTL: 1000 // 1 second
});
ttlCache.set('temp', 'This will expire');
console.log('Immediate get:', ttlCache.get('temp'));
setTimeout(() => {
console.log('After 1.5 seconds:', ttlCache.get('temp')); // Should be undefined
}, 1500);
}
testCacheSystem();
๐ฏ Summary
Youโve mastered generic interfaces in TypeScript! ๐ You learned how to:
- ๐ Create flexible, parameterized interface contracts
- ๐จ Build complex interface hierarchies with composition
- ๐ Design type-safe API definitions
- ๐ Implement functional programming patterns
- ๐ฎ Create sophisticated state management systems
- โจ Maintain complete type safety across generic interfaces
Generic interfaces are fundamental for building professional TypeScript applications. They enable you to create flexible, reusable contracts that adapt to any type while providing excellent developer experience and type safety!
Keep building amazing generic interfaces! ๐