OOP Concepts

  • Encapsulation: hiding internal details of object and exposing only what's necessary by using getter/setter methods and private, public, protected
  • Abstraction: focus on essential features while hiding complex implmentation by using abstract classes or interfaces
  • Inheritance: mechanism to create a new class from an existing class, reusing its code and supports "is-a" relationship; uses superclass/subclass
  • Polymorphism: ability of an object to take many forms by overloading/overriding methods

Inheritance

  • Define a "is-a" relation - subclasses extend a base class
  • Allow code sharing where subclasses inherit methods, variables and constructors
  • Can only inherit from one parent (superclass)
  • Subclasses depends on base implementation
  • Best for when classes share common logic and state
  • Inheritance is a blueprint that all subclasses share

Interface

  • Define a "can-do" relationship - classes implement abilities
  • No code sharing, only gives methods and properties name
  • One class can implements multiple interface
  • Class follows a structure
  • Best for when unrelated classes need to share the same API
    • Like when integrating third party libraries; use interface so even if the library is changed/updated the purpose stays the same, but implemented in different ways
  • Interface is a set of rules that the implementing classes share
    • The classes do the same thing, but in different ways

Abstract vs Interface

FeatureAbstract ClassInterface
MethodsCan be abstract and concreteMostly abstract (can have default or static methods since Java 8)
FieldsCan have instance variablesOnly public static final constants
ConstructorsYesNo
Multiple InheritanceNo (one parent class)Yes (can implement many interfaces)
PurposeTo share code/state & enforce structure among subclassesTo define behavior contracts or capabilities that can be applied to any class
Example Usageabstract class Animal (shared behavior)interface Flyable, interface Serializable (capabilities)

Java Example

Example for coding different ways to pay

// All payments need to be processed
interface Payment {
  processPayment(amount: number): void;
}

// Base class for reusable logic
abstract class PaymentProvider implements Payment {
  constructor(protected amount: number) {}

  validateAmount() {
    if (this.amount <= 0) throw new Error("Invalid amount");
  }

  abstract processPayment(): void;
}
---------------------------------------------------------
// Stripe reuses common logic
class StripePayment extends PaymentProvider {
  processPayment() {
    this.validateAmount();
    console.log(`Stripe processed ${this.amount}`);
  }
}

// ApplePay skips base class but still respects interface
class ApplePayPayment implements Payment {
  processPayment(amount: number) {
    console.log(`ApplePay processed ${amount}`);
  }
}
-----------------------------------------------------------
// Common handler for all payments
function handlePayment(payment: Payment, amount: number) {
  payment.processPayment(amount);
}

handlePayment(new StripePayment(100), 100);
handlePayment(new ApplePayPayment(), 50);

Javascript Example with API Route

Example for coding different ways to pay alongside Next.js API routes

// All payments need to be processed
export interface Payment {
  processPayment(amount: number): Promise<string>;
}

// Base class for reusable logic
export abstract class PaymentProvider implements Payment {
  constructor(protected amount: number) {}

  protected validateAmount() {
    if (this.amount <= 0) {
      throw new Error("Amount must be greater than 0");
    }
  }

  abstract processPayment(): Promise<string>;
}
-----------------------------------------------------------
// Stripe reuses common logic
export class StripePayment extends PaymentProvider {
  async processPayment(): Promise<string> {
    this.validateAmount();
    // Simulate Stripe API call
    return `Stripe successfully processed ${this.amount}`;
  }
}

// ApplePay skips base class but still respects interface
export class ApplePayPayment implements Payment {
  async processPayment(amount: number): Promise<string> {
    // ApplePay doesn’t need base class logic
    return `ApplePay processed ${amount}`;
  }
}

Putting it together with Next.js API routes

import { NextRequest, NextResponse } from "next/server";
import { StripePayment } from "@/lib/payments/StripePayment";
import { PayPalPayment } from "@/lib/payments/PayPalPayment";
import { ApplePayPayment } from "@/lib/payments/ApplePayPayment";
import { CryptoPayment } from "@/lib/payments/CryptoPayment";
import { Payment } from "@/lib/payments/Payment";

export async function POST(req: NextRequest) {
  try {
    const { provider, amount } = await req.json();

    let payment: Payment;

    switch (provider) {
      case "stripe":
        payment = new StripePayment(amount);
        return NextResponse.json({ message: await payment.processPayment() });

      case "paypal":
        payment = new PayPalPayment(amount);
        return NextResponse.json({ message: await payment.processPayment() });

      case "applepay":
        payment = new ApplePayPayment();
        return NextResponse.json({ message: await payment.processPayment(amount) });

      case "crypto":
        payment = new CryptoPayment();
        return NextResponse.json({ message: await payment.processPayment(amount) });

      default:
        return NextResponse.json({ error: "Invalid payment provider" }, { status: 400 });
    }
  } catch (error: any) {
    return NextResponse.json({ error: error.message }, { status: 500 });
  }
}