לדלג לתוכן

5.3 אינטרפייסים וטייפים פתרון

פתרון - אינטרפייסים וטייפים

פתרון תרגיל 1

א. אינטרפייס Book:

interface Book {
    title: string;
    author: string;
    pages: number;
    isbn: string;
    publishedYear: number;
}

let book1: Book = {
    title: "Clean Code",
    author: "Robert C. Martin",
    pages: 464,
    isbn: "978-0132350884",
    publishedYear: 2008
};

let book2: Book = {
    title: "The Pragmatic Programmer",
    author: "David Thomas",
    pages: 352,
    isbn: "978-0135957059",
    publishedYear: 1999
};

ב. formatBook:

function formatBook(book: Book): string {
    return `${book.title} by ${book.author} (${book.publishedYear}) - ${book.pages} pages`;
}

ג. getOldestBook:

function getOldestBook(books: Book[]): Book {
    let oldest = books[0];
    for (let book of books) {
        if (book.publishedYear < oldest.publishedYear) {
            oldest = book;
        }
    }
    return oldest;
}

פתרון תרגיל 2

א. אינטרפייס UserProfile:

interface UserProfile {
    readonly id: number;
    readonly username: string;
    email: string;
    bio?: string;
    website?: string;
    readonly createdAt: string;
}

ב. ניסיון שינוי:

let profile: UserProfile = {
    id: 1,
    username: "alice",
    email: "alice@example.com",
    createdAt: "2025-01-01"
};

profile.id = 2;                   // ERROR: Cannot assign to 'id' because it is a read-only property
profile.email = "new@example.com"; // OK - email is not readonly

ג. updateProfile:

function updateProfile(
    profile: UserProfile,
    updates: { email?: string; bio?: string; website?: string }
): UserProfile {
    return {
        ...profile,
        ...updates
    };
}

let updated = updateProfile(profile, { email: "new@example.com", bio: "Hello!" });

פתרון תרגיל 3

א. נקודה - interface כי זה אובייקט (אפשר גם type, שניהם תקינים):

interface Point {
    x: number;
    y: number;
}

ב. HttpMethod - type כי זה union:

type HttpMethod = "GET" | "POST" | "PUT" | "DELETE";

ג. ApiError - interface כי זה אובייקט:

interface ApiError {
    code: number;
    message: string;
    details?: string;
}

ד. Result - type כי זה union של אובייקטים:

type Result =
    | { success: true; data: string }
    | { success: false; error: string };

ה. EventHandler - type כי זה פונקציה:

type EventHandler = (event: string, data: unknown) => void;

פתרון תרגיל 4

א. EnvVariables:

interface EnvVariables {
    [key: string]: string;
}

let env: EnvVariables = {
    NODE_ENV: "development",
    PORT: "3000",
    DATABASE_URL: "postgres://localhost:5432/mydb",
    API_KEY: "abc123"
};

ב. ScoreBoard:

interface ScoreBoard {
    [playerName: string]: number;
}

function getTopPlayer(scores: ScoreBoard): string {
    let topPlayer = "";
    let topScore = -Infinity;

    for (let player in scores) {
        if (scores[player] > topScore) {
            topScore = scores[player];
            topPlayer = player;
        }
    }

    return topPlayer;
}

let scores: ScoreBoard = {
    Alice: 150,
    Bob: 230,
    Charlie: 180
};

console.log(getTopPlayer(scores)); // "Bob"

ג. TypedDictionary:

interface TypedDictionary {
    [key: string]: string | number; // must include number because of 'version'
    version: number;
}

let dict: TypedDictionary = {
    version: 1,
    name: "my-config",
    env: "production"
};

שימו לב: ה-index signature חייב להיות תואם לכל השדות המוגדרים. version הוא number, אז ה-index signature חייב לכלול number.

פתרון תרגיל 5

א. צורות עם extends ו-discriminator:

interface Shape {
    color: string;
}

interface Circle extends Shape {
    type: "circle";
    radius: number;
}

interface Rectangle extends Shape {
    type: "rectangle";
    width: number;
    height: number;
}

interface Triangle extends Shape {
    type: "triangle";
    base: number;
    height: number;
}

type AnyShape = Circle | Rectangle | Triangle;

function getArea(shape: AnyShape): number {
    switch (shape.type) {
        case "circle":
            return Math.PI * shape.radius ** 2;
        case "rectangle":
            return shape.width * shape.height;
        case "triangle":
            return (shape.base * shape.height) / 2;
    }
}

let circle: Circle = { type: "circle", color: "red", radius: 5 };
console.log(getArea(circle)); // 78.54

ב. TimestampedUser:

type User = {
    name: string;
    email: string;
};

type Timestamps = {
    createdAt: string;
    updatedAt: string;
};

type TimestampedUser = User & Timestamps;

let user: TimestampedUser = {
    name: "Alice",
    email: "alice@example.com",
    createdAt: "2025-01-01",
    updatedAt: "2025-06-15"
};

ג. קונפליקט ב-intersection:

type A = { x: number };
type B = { x: string };
type C = A & B;

// C.x is type 'never' - because there's no value that is both number AND string
// You can't create a valid object of type C:
let obj: C = { x: 42 }; // ERROR: Type 'number' is not assignable to type 'never'

פתרון תרגיל 6

interface Product {
    name: string;
    price: number;
    category: string;
    stock: number;
}

interface CartItem {
    product: Product;
    quantity: number;
}

interface Address {
    street: string;
    city: string;
    zipCode: string;
}

interface Customer {
    name: string;
    email: string;
    address: Address;
}

interface Order {
    readonly id: string;
    customer: Customer;
    items: CartItem[];
    status: "pending" | "shipped" | "delivered";
    readonly createdAt: string;
}

function calculateOrderTotal(order: Order): number {
    let total = 0;
    for (let item of order.items) {
        total += item.product.price * item.quantity;
    }
    return total;
}

function getOrderSummary(order: Order): string {
    let itemCount = order.items.reduce((sum, item) => sum + item.quantity, 0);
    let total = calculateOrderTotal(order);
    return `Order for ${order.customer.name}: ${itemCount} items, total $${total.toFixed(2)}`;
}

פתרון תרגיל 7

א. Logger:

interface Logger {
    info(message: string): void;
    warn(message: string): void;
    error(message: string, error?: Error): void;
}

let logger: Logger = {
    info(message: string): void {
        console.log(`[INFO] ${message}`);
    },
    warn(message: string): void {
        console.log(`[WARN] ${message}`);
    },
    error(message: string, error?: Error): void {
        console.log(`[ERROR] ${message}`);
        if (error) {
            console.log(`  Stack: ${error.stack}`);
        }
    }
};

logger.info("Server started");
logger.warn("Disk space low");
logger.error("Connection failed", new Error("timeout"));

ב. Validator:

type Validator = (value: string) => { valid: boolean; message?: string };

let isNotEmpty: Validator = (value) => {
    if (value.trim() === "") {
        return { valid: false, message: "Value must not be empty" };
    }
    return { valid: true };
};

let isEmail: Validator = (value) => {
    if (!value.includes("@")) {
        return { valid: false, message: "Value must be a valid email" };
    }
    return { valid: true };
};

function isMinLength(min: number): Validator {
    return (value) => {
        if (value.length < min) {
            return { valid: false, message: `Value must be at least ${min} characters` };
        }
        return { valid: true };
    };
}

function validate(value: string, validators: Validator[]): string[] {
    let errors: string[] = [];
    for (let validator of validators) {
        let result = validator(value);
        if (!result.valid && result.message) {
            errors.push(result.message);
        }
    }
    return errors;
}

// usage
let errors = validate("", [isNotEmpty, isEmail, isMinLength(5)]);
console.log(errors);
// ["Value must not be empty", "Value must be a valid email", "Value must be at least 5 characters"]

let errors2 = validate("hi@a", [isNotEmpty, isEmail, isMinLength(5)]);
console.log(errors2);
// ["Value must be at least 5 characters"]