Study guide

6.4 Classes, Interfaces & OOP Patterns

Salesforce Certified Platform Developer I Exam

Track your progress as you read

Finished sections get a check mark.

1 Introduction & Exam Overview

1.1 Current Exam Facts 1.2 Blueprint Weights & Study Strategy

2 Platform Fundamentals

2.1 Multi-Tenant Architecture & Shared Resources 2.2 MVC Mapping on the Lightning Platform 2.3 Declarative vs Programmatic Decisions 2.4 Agentforce for Developers: Use Cases & Limitations

3 Data Modeling & Schema

3.1 Objects, Fields & Schema Design 3.2 Relationship Types & Implications 3.3 Schema Design Impact on Apex 3.4 Data Import & Export for Development

4 Platform Data Features

4.1 Formula Fields & Roll-Up Summaries 4.2 Core CRM Objects & Standard Schema 4.3 AppExchange Extensions & Platform Boundaries

5 Declarative Process Automation

5.1 Record-Triggered Flows & Declarative Automation 5.2 Approval Processes & Business Rules 5.3 When to Use Apex vs Declarative Tools

6 Apex Language Basics

6.1 Variables, Constants & Apex Data Types 6.2 Lists, Sets & Maps 6.3 Control Flow & Exception Handling 6.4 Classes, Interfaces & OOP Patterns

7 SOQL, SOSL & DML

7.1 SOQL Query Fundamentals 7.2 SOSL Search Fundamentals 7.3 DML Operations & Bulkification 7.4 Governor Limits & Transaction Boundaries

8 Apex Triggers & Order of Execution

8.1 Trigger Structure & Context Variables 8.2 Bulk Trigger Patterns & Best Practices 8.3 Save Order of Execution 8.4 Recursion, Cascading & Transaction Control

9 Asynchronous Apex

9.1 @future Methods 9.2 Queueable Apex 9.3 Batch Apex 9.4 Schedulable Apex & Job Orchestration

10 Security, Events & Schema Access

10.1 Sharing, FLS, CRUD & Injection Prevention 10.2 Platform Events Publish & Subscribe 10.3 Programmatic Schema Access (Describe)

11 Visualforce

11.1 Visualforce Pages & Markup 11.2 Standard, Custom Controllers & Extensions 11.3 Visualforce in Lightning Experience

12 Lightning Components

12.1 Lightning Component Framework Benefits 12.2 Aura Component Structure & Resources 12.3 Lightning Web Components Fundamentals 12.4 LWC Data Access: Wire, Imperative & Events

13 UI Composition & Apex Integration Patterns

13.1 Composing UI in Lightning Apps 13.2 UI Security, Performance & Accessibility Patterns 13.3 Apex Actions for Flow, LWC, and Agentforce

14 Apex Testing

14.1 Testing Framework & Coverage Requirements 14.2 Writing Unit Tests for Triggers & Classes 14.3 Test Data Strategies & Isolation 14.4 Executing Tests & Execute Anonymous Differences

15 Debugging & Developer Tools

15.1 Debug Logs & Monitoring 15.2 Developer Console, CLI, VS Code & Workbench 15.3 Sandboxes, Scratch Orgs & Environment Strategy

16 Deployment & Release

16.1 Change Sets & Metadata Deployment 16.2 Unlocked Packages, Metadata API & Salesforce DX 16.3 Release Readiness & Credential Maintenance

6.4 Classes, Interfaces & OOP Patterns

Key Takeaways Explain This Topic AI

Apex classes group methods and state; access modifiers include private, public, global, and protected (with inheritance nuances)

static members belong to the type; instance members belong to an object—triggers and utilities often use static service methods

Interfaces define method contracts that implementing classes must provide—useful for polymorphism and test doubles

abstract classes provide partial implementation; inner classes nest helper types; sharing keywords control record access enforcement

Service-layer and trigger-handler patterns keep triggers thin: one-line trigger delegates to a handler/service class

Last updated: August 2026

6.4 Classes, Interfaces & OOP Patterns

Quick Answer: Production Apex is organized into classes with clear access modifiers , static vs instance members, and often interfaces for contracts. Triggers should stay thin and call handler/service classes. with sharing / without sharing / inherited sharing control whether record sharing rules apply to Apex—details deepen in the security chapter; here you need the vocabulary and default intent.

Platform Developer I expects more than syntax: you must choose structures that stay bulk-safe, testable, and maintainable under Salesforce packaging and security rules.

  • Classes and Methods public with sharing class AccountService {
  • public static void renumber(List<Account> accounts) {
  • // bulk logic
  • }
  • public Account findPrimary(Id accountId) {
  • return [SELECT Id, Name FROM Account WHERE Id = :accountId LIMIT 1];
  • }
  • }

A class encapsulates related behavior. Methods can return values or be void . Overloading (same method name, different parameter lists) is supported. Constructors initialize instance state: public class PricingEngine {

private final Decimal taxRate;

  • public PricingEngine(Decimal taxRate) {
  • this.taxRate = taxRate;
  • }
  • public Decimal totalWithTax(Decimal net) {
  • return net * (1 + taxRate);
  • }
  • }
  • Access Modifiers

Modifier Visibility

private Only the containing class (default for methods/variables if unspecified in many contexts—know class vs member defaults for the exam)

public Any Apex in the application (same namespace)

global Any Apex that can see the class, including across namespaces/packages—use sparingly for managed package APIs

protected Containing class and subclasses (inner/subclass patterns)

Top-level classes you save as Apex class metadata are typically public or global . Inner classes can use additional nesting visibility rules.

Exam mindset: Prefer the narrowest modifier that still works. global is for published APIs in packages, not everyday org code. Service methods used only inside the org are usually public (or @TestVisible private for tests).

static vs Instance

  • static instance
  • Belongs to The class itself A specific object
  • Memory One shared copy per transaction type usage Per constructed instance
  • Call style AccountService.renumber(list) engine.totalWithTax(100)
  • Typical use Utilities, trigger entry points, caches for the transaction Engines with configuration state

Static variables persist for the duration of the Apex transaction and are a common way to implement recursion guards in triggers: public class TriggerGuard {

  • public static Boolean hasRun = false;
  • }

Use static flags carefully—tests and bulk multiple operations can leave unexpected state if you do not reset in test setup or design clearer handler entry points.

Interfaces and Implementation

An interface defines method signatures without bodies. Classes implement the contract: public interface IDiscountPolicy {

  • Decimal apply(Decimal amount);
  • }
  • public class PercentOffPolicy implements IDiscountPolicy {
  • private final Decimal percent;
  • public PercentOffPolicy(Decimal percent) { this.percent = percent; }
  • public Decimal apply(Decimal amount) {
  • return amount * (1 - percent);
  • }
  • }
  • public class NoDiscountPolicy implements IDiscountPolicy {
  • public Decimal apply(Decimal amount) { return amount; }
  • }

Callers depend on IDiscountPolicy , not a concrete class—swap implementations for regions, products, or tests. Interfaces are also how many platform features plug in (for example, batchable/queueable/schedulable style contracts you will meet in async chapters).

Abstract Classes (High Level)

An abstract class can mix implemented methods with abstract methods subclasses must provide. Use when implementations share substantial code but differ in one or more steps (template-style design). Prefer an interface when you only need a pure contract with no shared code; prefer abstract class when shared helpers justify a base type.

You cannot construct an abstract class directly—only concrete subclasses.

  • Inner Classes
  • Classes can nest helper types: public class OrderService {
  • public class LineRequest {
  • public Id productId;
  • public Decimal quantity;
  • }
  • public static void place(List<LineRequest> lines) { /* ... */ }
  • }

Inner classes organize DTOs and small helpers next to the outer service without proliferating top-level metadata files. They are still subject to governor limits—nesting is structural, not a sandbox escape.

with sharing / without sharing / inherited sharing

These class-level keywords control record-level sharing enforcement for SOQL/DML in that class (field- and object-level security are separate topics).

  • Keyword Meaning (intro level)
  • with sharing Enforce the current user’s sharing rules

without sharing Bypass sharing rules (still not a full “system mode” for FLS/CRUD unless you also handle those)

inherited sharing Inherit the caller’s sharing context—preferred default for many reusable services public with sharing class SecureAccountSelector { /* ... */ }

  • public without sharing class NightlyCleanupService { /* elevated data access by design */ }
  • public inherited sharing class AccountService { /* respects caller */ }

Exam caution: without sharing is not a shortcut for “ignore all security.” It is intentional elevation for system-style processes and must be justified. Deeper CRUD/FLS, stripInaccessible, and user-mode DB operations appear in the security chapter—here, remember which keyword enforces sharing vs bypasses it.

If a class omits a sharing declaration, historical default behavior is effectively without sharing for the class—modern best practice is to declare inherited sharing or with sharing explicitly so intent is obvious.

Service Layer and Trigger-Handler Patterns (Overview)

Problem: Fat triggers mix bulkification, business rules, and recursion control in one untestable file.

  • Pattern: trigger ContactTrigger on Contact (before insert, before update) {
  • ContactTriggerHandler.run();
  • }
  • public with sharing class ContactTriggerHandler {
  • public static void run() {
  • if (Trigger.isBefore && Trigger.isInsert) {
  • ContactService.applyDefaults(Trigger.new);
  • }
  • if (Trigger.isBefore && Trigger.isUpdate) {
  • ContactService.validateChanges(Trigger.new, Trigger.oldMap);
  • }
  • }
  • }
  • public inherited sharing class ContactService {
  • public static void applyDefaults(List<Contact> contacts) { /* bulk logic */ }
  • public static void validateChanges(List<Contact> contacts, Map<Id, Contact> oldMap) { /* ... */ }
  • }

Roles:

  • Trigger — routing only (context checks, one call)
  • Handler — maps trigger context to service methods; may hold recursion guards

Service — pure-ish business operations that accept Lists/Maps and are unit-testable without full trigger setup (though integration tests still use Test.startTest patterns)

This separation appears conceptually on the exam even when exact class names differ. Choose answers that keep bulk lists, avoid logic duplication across events, and centralize rules in classes rather than copy-paste in multiple triggers.

Putting OOP Choices on the Exam

Ask for each scenario:

Does this need shared state for the call (instance) or a stateless utility (static)?

Is a contract needed for multiple implementations (interface)?

Must the code respect the running user’s sharing (with/inherited sharing)?

Is logic living in a trigger body that should move to a handler/service ?

Mastering these patterns prepares you for triggers, async Apex, and testing chapters that assume class-structured code rather than anonymous-style scripts.