Identifying Objects in Problem Statements
Verbs as Potential Methods
Read aloud is unavailable in this browser.
From Actions to Methods
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.
Why Verbs Matter
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:
- Encapsulation: Rules are enforced regardless of who calls
withdraw() - Discoverability: Looking at
BankAccountshows you what accounts can do - Cohesion: Related logic stays together
- Evolution: Adding new rules (withdrawal limits, audit logging) happens in one place
An '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.
Identifying Verbs in Requirements
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.
The Responsibility Assignment Problem
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:
- High coupling (classes depend on each other's internals)
- Low cohesion (unrelated behaviors clumped together)
- Feature envy (methods that use another class's data more than their own)
- Shotgun surgery (one change requires editing many classes)
Consider: 'The system calculates the order total.'
Where does calculateTotal() go?
- •OrderService.calculateTotal(order)
- •Service reaches into Order for items
- •Order becomes just data container
- •Business logic scattered in services
- •order.calculateTotal()
- •Order uses its own items internally
- •Method is where the data is
- •Business logic co-located with data
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.
Heuristics for Assigning Methods
Beyond Information Expert, several heuristics help you assign methods correctly:
- •Information Expert — Assign to the class with the necessary data. 'Order knows its items, so Order calculates total.'
- •Creator — Assign creation to the class that contains, aggregates, or closely uses the created object. 'Order creates OrderItems.'
- •Tell, Don't Ask — Instead of asking for data and acting on it externally, tell the object to act.
order.applyDiscount()notdiscount.applyTo(order). - •High Cohesion — Keep related methods together. If two methods always need to be called together, they belong on the same class.
- •Low Coupling — Avoid methods that require excessive imports or dependencies. If a method needs 5 other classes, consider refactoring.
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."
- Does Order send the notification? (It knows it shipped)
- Does NotificationService send it? (It knows how to send)
- Does Customer receive it? (They care about the notification)
Resolution: Separate concerns.
- Order.ship() — Marks order as shipped, raises an event
- NotificationService.sendOrderShippedEmail(order) — Handles delivery mechanics
- The connection is made through an event handler or orchestrator
This separation respects single responsibility: Order handles order logic, NotificationService handles notification mechanics.
Verb Categories and Their Homes
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 |
Method Naming Conventions
Once you've assigned a verb to a class, naming the method well improves readability and expresses intent. Good method names:
- Start with a verb (or read like a verb phrase)
- Describe what the method does, not how
- Match the domain vocabulary
- Are specific, not generic
| 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 generic - •
data()— Noun, not verb - •
updateState()— What state?
- •
processPayment()— Specific action - •
handleCheckout()— Clear context - •
submitOrder()— Explains intent - •
executeTransfer()— Clear action - •
fetchUserData()— Verb + noun - •
markAsShipped()— Specific state
Use 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.
Handling Complex Verbs
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:
- Facade method: One high-level method that calls sub-operations
- Orchestrator: A service that coordinates multiple objects
- Chain: Objects that pass control to each other
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.
KEEP LEARNING
There’s more to understand.
You’re reading the introduction. Create a free account to read the complete lesson, save notes and keep track of your progress.