Loading learning content...
If nouns become objects, then verbs become methods. This linguistic parallel completes the picture: just as nouns in requirements suggest the things in your system, verbs suggest the behaviors those things exhibit.
But verb-to-method mapping is more subtle than noun-to-object mapping. When you read 'the customer places an order,' which object gets the place() method—Customer, Order, or something else entirely? When you read 'the system calculates the total,' what class should own that calculation?
This page teaches you to extract methods from verbs systematically and—crucially—assign them to the right classes.
You will learn how to identify verbs in requirements, decide which class should own each behavior, apply principles like 'Expert' and 'Tell Don't Ask' for responsibility assignment, and avoid common pitfalls like behavior hoarding and anemic models.
Objects without behavior are just data structures. What makes object-oriented design powerful is encapsulating both state and behavior within objects. The behaviors—the methods—define what an object can do, not just what it is.
Consider two designs for a BankAccount:
123456789101112131415161718192021
// Data structure - no behaviorclass BankAccount { public balance: number; public ownerName: string; public accountNumber: string; public isActive: boolean;} // All behavior lives outsidefunction withdraw( account: BankAccount, amount: number): void { if (!account.isActive) { throw new Error("Inactive"); } if (account.balance < amount) { throw new Error("Insufficient"); } account.balance -= amount;}123456789101112131415161718192021222324252627
// Object with encapsulated behaviorclass BankAccount { private balance: number; private ownerName: string; private accountNumber: string; private isActive: boolean; public withdraw(amount: number): void { this.ensureActive(); this.ensureSufficientBalance(amount); this.balance -= amount; } private ensureActive(): void { if (!this.isActive) { throw new Error("Inactive"); } } private ensureSufficientBalance( amount: number ): void { if (this.balance < amount) { throw new Error("Insufficient"); } }}The rich model encapsulates the business rules (must be active, must have sufficient balance) inside the object. This is the power of well-assigned methods:
withdraw()BankAccount shows you what accounts can doAn 'anemic domain model' is one where objects have only getters and setters, with all behavior living in separate service classes. This violates object-oriented principles and leads to fragile, hard-to-maintain code. Proper verb-to-method mapping prevents this.
Just as we extracted nouns systematically, we extract verbs by reading requirements carefully and marking action words.
Types of verbs to look for:
| Verb Type | Examples | Common Pattern |
|---|---|---|
| Action verbs | create, delete, update, send, calculate | Direct operations on objects |
| State-change verbs | activate, suspend, complete, cancel | Status transitions |
| Query verbs | find, search, get, retrieve, list | Read operations returning data |
| Validation verbs | validate, verify, check, ensure | Constraint enforcement |
| Event verbs | notify, trigger, publish, submit | Communication between objects |
| Transformation verbs | convert, format, parse, serialize | Data transformation operations |
Let's extract verbs from a requirement:
"Users can register by providing their email and password. The system validates the email format and checks that the password meets security requirements. After successful registration, a confirmation email is sent, and the user account is activated. Users can then login, update their profile, and reset their password if forgotten."
12345678910111213141516
Extracted Verbs:─────────────────1. register (user action)2. providing (user action - part of register)3. validates (system action)4. checks (system action)5. meets (constraint check)6. is sent (system action - passive voice)7. is activated (state change - passive voice)8. login (user action)9. update (user action)10. reset (user action) Passive Voice Note:- "is sent" → send- "is activated" → activateRequirements often use passive voice ('The email is sent,' 'The account is validated'). Convert these to active form to identify the verb clearly: 'send email,' 'validate account.' The active form also helps identify who's doing the action.
Extracting verbs is the easy part. The harder question: Which class gets which method?
This is the responsibility assignment problem—one of the most important decisions in object-oriented design. A method placed on the wrong class leads to:
Consider: 'The system calculates the order total.'
Where does calculateTotal() go?
The guiding principle: Put behavior where the data is.
An Order knows its items, quantities, and prices. It should calculate its own total. A Customer knows their orders. They should provide their purchase history. A Product knows its inventory. It should check its own availability.
This principle is formalized as the Information Expert pattern from GRASP (General Responsibility Assignment Software Patterns):
Assign responsibility to the class that has the information necessary to fulfill it.
GRASP is a collection of principles for assigning responsibilities. Information Expert is the most fundamental: the expert on a topic should handle questions about that topic. We'll see more GRASP patterns as we discuss responsibility assignment.
Beyond Information Expert, several heuristics help you assign methods correctly:
order.applyDiscount() not discount.applyTo(order).The 'Tell, Don't Ask' principle in action:
Requirement: "Apply a discount to the order if the customer is a VIP."
12345678910
// Asking for data, acting externallyfunction checkout(order: Order) { const customer = order.getCustomer(); if (customer.isVIP()) { const discount = 0.10; const total = order.getTotal(); const newTotal = total * (1 - discount); order.setTotal(newTotal); }}1234567891011121314
// Telling the object what to dofunction checkout(order: Order) { order.applyCustomerDiscounts(); // Order knows about its customer // Order knows its own discount rules // Order handles the calculation internally} // Inside Order classapplyCustomerDiscounts(): void { if (this.customer.isVIP()) { this.applyDiscount(0.10); }}When verbs have multiple candidate classes:
Sometimes a verb could reasonably belong to multiple classes. "The system sends a notification when an order ships."
Resolution: Separate concerns.
This separation respects single responsibility: Order handles order logic, NotificationService handles notification mechanics.
Different verb categories tend to land on different types of classes. Understanding these patterns accelerates assignment decisions.
| Verb Category | Typical Home | Examples |
|---|---|---|
| CRUD operations (create, read, update, delete) | Repository/DAO or the entity itself | UserRepository.create(), User.updateProfile() |
| Business calculations | The entity with the data | Order.calculateTotal(), Loan.computeInterest() |
| State transitions | The entity changing state | Order.cancel(), Account.activate(), Task.complete() |
| Validations | The entity being validated or a Validator | Email.validate(), PasswordValidator.check() |
| Cross-cutting queries | Repository or Query Service | OrderRepository.findByCustomer(), ReportService.generateSalesReport() |
| External integrations | Gateway/Adapter/Service | PaymentGateway.charge(), EmailService.send() |
| Orchestration/workflow | Application Service | CheckoutService.processCheckout(), OnboardingService.registerUser() |
Don't put everything in services! Application services should orchestrate, not implement. Business logic belongs in domain objects. If your OrderService has 50 methods while Order has only getters/setters, you've inverted the model.
Pattern recognition for verb assignment:
| Requirement Phrase | Likely Method | Likely Class |
|---|---|---|
| "User creates an account" | create() or register() | AccountRepository or RegistrationService |
| "Account is activated" | activate() | Account |
| "System validates email" | isValid() or validate() | Email (value object) or EmailValidator |
| "Order totals are calculated" | calculateTotal() | Order |
| "Customer views order history" | getOrderHistory() | Customer or OrderRepository |
| "Payment is processed" | process() | Payment or PaymentProcessor |
| "Notification is sent" | send() | NotificationService |
Once you've assigned a verb to a class, naming the method well improves readability and expresses intent. Good method names:
| Pattern | Purpose | Examples |
|---|---|---|
verb() | Action with no object | activate(), submit(), cancel() |
verbNoun() | Action on something | calculateTotal(), sendEmail(), validatePassword() |
getNoun() | Return data | getBalance(), getOrderHistory(), getName() |
isCondition() | Boolean query | isActive(), isOverdue(), isEmpty() |
hasNoun() | Ownership/existence check | hasPermission(), hasItems(), hasPendingOrders() |
canVerb() | Ability check | canWithdraw(), canEdit(), canBeCancelled() |
findByProperty() | Query by criteria | findByEmail(), findByStatus(), findActiveUsers() |
toFormat() | Conversion | toString(), toJSON(), toDTO() |
Bad vs Good Method Names:
process() — Process what?handle() — Handle how?doIt() — Do what?execute() — Too genericdata() — Noun, not verbupdateState() — What state?processPayment() — Specific actionhandleCheckout() — Clear contextsubmitOrder() — Explains intentexecuteTransfer() — Clear actionfetchUserData() — Verb + nounmarkAsShipped() — Specific stateUse the language of the business domain. If the business says 'book a reservation,' name it book() not create(). If they say 'fulfill an order,' name it fulfill() not process(). Code that speaks the domain language is easier for everyone to understand.
Some verbs in requirements don't map cleanly to single methods. They represent complex operations spanning multiple steps or involving multiple objects.
Compound operations:
"The system processes the checkout: validates the cart, reserves inventory, charges payment, and creates the order."
This single verb 'processes' encompasses four sub-operations. You have choices:
123456789101112131415161718192021222324252627282930313233343536
class CheckoutService { constructor( private inventory: InventoryService, private payment: PaymentGateway, private orderRepository: OrderRepository ) {} async processCheckout(cart: Cart): Promise<Order> { // Step 1: Validate cart cart.validate(); // Step 2: Reserve inventory const reservation = await this.inventory.reserve(cart.items); try { // Step 3: Charge payment const paymentResult = await this.payment.charge( cart.customer.paymentMethod, cart.total ); // Step 4: Create order const order = Order.createFrom(cart, paymentResult); await this.orderRepository.save(order); // Step 5: Finalize reservation await reservation.confirm(); return order; } catch (error) { // Rollback reservation if payment fails await reservation.release(); throw error; } }}When to use orchestrators vs domain methods:
| Use Domain Method When | Use Orchestrator When |
|---|---|
| Operation uses one object's data | Operation spans multiple aggregates |
| No external integrations | External services involved |
| Transaction is simple | Transaction needs coordination |
| Business rule, not workflow | Workflow with multiple steps |
Implied verbs:
Some behaviors are implied rather than stated:
"Customers can view their order history."
'View' implies 'retrieve' or 'get.' The method is getOrderHistory() on Customer (or queried from OrderRepository by customer ID).
"The discount expires after 30 days."
'Expires' implies checking expiration: isExpired() and possibly checkExpiration() that marks expired discounts.
Complex methods should be broken into smaller, focused methods. Each method name then documents a step in the process. processCheckout() might call validateCart(), reserveInventory(), chargePayment(), and createOrder(). Reading the orchestrator tells you the workflow.
Let's apply verb extraction and assignment to a hotel booking requirement:
"Guests search for available rooms by date and room type. They can book a room by selecting dates and providing payment details. The system confirms the booking and sends a confirmation email. Guests can view, modify, or cancel their bookings. Cancellations made less than 24 hours before check-in incur a fee. Hotel staff can mark rooms as cleaned, under maintenance, or occupied."
Step 1: Extract verbs
12345678910111213
Extracted Verbs:─────────────────1. search (for rooms)2. book (a room)3. select (dates)4. provide (payment details)5. confirm (booking)6. send (email)7. view (bookings)8. modify (bookings)9. cancel (bookings)10. incur (fee) - passive, implies charging11. mark (room status)Step 2: Identify classes (from earlier noun analysis)
Step 3: Assign verbs to classes
| Verb | Method | Assigned To | Rationale |
|---|---|---|---|
| search | searchAvailableRooms(criteria) | RoomRepository or RoomSearchService | Query across rooms—repository concern |
| book | book(dates, guest, payment) | Booking (static factory) or BookingService | Creates booking record |
| select | (UI action) | Not a domain method | User interaction, not domain logic |
| provide | (UI action) | Not a domain method | Data input, not domain logic |
| confirm | confirm() | Booking | Booking confirms itself after payment |
| send | sendConfirmation(booking) | NotificationService | External integration—service responsibility |
| view | getBookings() | Guest or BookingRepository | Guest's view of their bookings |
| modify | modify(newDetails) | Booking | Booking knows its modification rules |
| cancel | cancel() | Booking | Booking handles cancellation logic |
| incur fee | calculateCancellationFee() | Booking | Booking knows 24-hour rule |
| mark | setStatus(status) | Room | Room manages its own status |
Step 4: Define method signatures
123456789101112131415161718192021222324252627
class Room { setStatus(status: RoomStatus): void; isAvailable(dateRange: DateRange): boolean;} class Booking { static create(room: Room, guest: Guest, dates: DateRange): Booking; confirm(payment: Payment): void; modify(newDates: DateRange): void; cancel(): CancellationResult; calculateCancellationFee(): Money; isWithin24HoursOfCheckIn(): boolean;} class Guest { getBookings(): Booking[]; getUpcomingBookings(): Booking[];} interface RoomSearchService { searchAvailableRooms(criteria: SearchCriteria): Room[];} interface NotificationService { sendBookingConfirmation(booking: Booking): void; sendCancellationNotice(booking: Booking): void;}Each verb found a home: domain methods on entities (Booking, Room, Guest), queries in repositories (RoomRepository), and integrations in services (NotificationService). This distribution follows OO principles and creates a maintainable architecture.
What's Next:
With objects (nouns) and behaviors (verbs) identified, the final piece is practicing object identification—bringing it all together with exercises that reinforce the mental model. You'll work through additional scenarios to build fluency in this foundational skill.
You now understand how to extract methods from verbs in requirements and assign them to appropriate classes using principles like Information Expert and Tell Don't Ask. Combined with noun extraction, you have a complete toolkit for object identification.