Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

iTechGuides is reader-supported. When you buy through links on our site, we may earn an affiliate commission. As an Amazon Associate I earn from qualifying purchases. Learn more

GRASP Principles Part 3 covers four responsibility-assignment techniques: Polymorphism handles behavior that varies by type, Pure Fabrication keeps technical work out of domain objects, Indirection separates components through an intermediary, and Protected Variations isolates likely change behind a stable boundary. The four techniques work together to improve cohesion, coupling, testability, and maintainability.

GRASP means General Responsibility Assignment Software Patterns. The phrase “Part 3” is a series label rather than a canonical GRASP classification. In Craig Larman’s Applying UML and Patterns, these four concepts appear as the final four GRASP patterns in the responsibility-assignment chapter. The principles do not prescribe one language, framework, or class hierarchy; they help you reason about which object should do what.

Key takeaways

  • Polymorphism assigns varying behavior to implementations of a shared interface or contract instead of scattering type-specific conditionals through client code.
  • Pure Fabrication creates a focused software class, such as a repository or gateway, when assigning technical work to a domain object would damage cohesion or coupling.
  • Indirection inserts an intermediary between components when a direct dependency would create an undesirable boundary, interface mismatch, or testing problem.
  • Protected Variations places a stable abstraction around an existing variation point or credible evolution point so likely changes do not spread through the system.
  • The four techniques overlap: polymorphism handles behavioral variation, Protected Variations isolates the change, Indirection creates the boundary, and Pure Fabrication often supplies the boundary object.

What is GRASP?

GRASP is a family of patterns and principles for assigning responsibilities in object-oriented design. A responsibility can mean knowing information, performing an operation, creating an object, coordinating a use case, or collaborating with another object.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Responsibility assignment is more specific than simply deciding which class contains a method. A design must decide which object owns data, which object applies business rules, which object coordinates a workflow, and which object communicates with infrastructure or external systems. Those choices influence coupling, cohesion, testability, duplication, changeability, and the clarity of the domain model.

The commonly listed nine GRASP items are Controller, Creator, Indirection, Information Expert, Low Coupling, High Cohesion, Polymorphism, Protected Variations, and Pure Fabrication. Terminology varies: some sources call them principles, while Craig Larman presents them primarily as patterns for assigning responsibilities. The GRASP collection overview lists the nine named items, while Larman’s GRASP chapter presents Polymorphism, Indirection, Pure Fabrication, and Protected Variations as the final four in the sequence.

GRASP is related to—but not the same as—SOLID or the Gang of Four design patterns. SOLID provides object-oriented design principles such as the Open-Closed and Liskov Substitution principles. GoF patterns provide recurring implementation structures such as Strategy, Adapter, Factory, Facade, and Observer. GRASP asks an earlier question: which object should receive this responsibility, and why? A GoF pattern may be one way to implement a GRASP decision.

How do the four GRASP principles differ?

The four principles address different design pressures, although a single design can use several at once.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Principle Main question Typical mechanism Main risk
Polymorphism Where should behavior go when it varies by type? Interface, subtype, strategy, function object, or composition Unnecessary hierarchy or abstraction
Pure Fabrication What should receive a responsibility when a domain object is a poor fit? Repository, gateway, exporter, logger, or focused service Anemic domain objects or oversized services
Indirection How can two components avoid an undesirable direct dependency? Adapter, facade, mediator, controller, proxy, or gateway Excessive layers and harder debugging
Protected Variations How can likely change be isolated from the rest of the system? Stable interface, information-hiding boundary, strategy, or adapter Speculative flexibility

How does GRASP Polymorphism work?

GRASP Polymorphism assigns a responsibility to a polymorphic type when the responsibility varies according to an object’s type. Instead of making one client understand every variation, the client calls a common operation and lets the appropriate implementation provide the behavior.

The design change is straightforward:

Client → common abstraction → type-specific implementation

Without polymorphism, client code often grows a chain of type checks:

if type == A:
    perform A behavior
else if type == B:
    perform B behavior
else:
    perform default behavior

With polymorphism, the client uses the shared contract:

paymentMethod.pay()

Each payment method implements its own version of the conceptual operation. The client knows how to request payment, but it does not need to know the details of every payment method.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Example: polymorphic tax calculation

A checkout system may support different tax rules or third-party tax calculators. A conditional design puts every tax rule in the checkout client:

class CheckoutService {
    Money calculateTax(Order order, String region) {
        if (region.equals("US")) {
            return usTax(order);
        } else if (region.equals("EU")) {
            return euTax(order);
        } else {
            return defaultTax(order);
        }
    }
}

The checkout service becomes coupled to every tax rule. Adding another provider requires changing the client and potentially repeating the same decision logic elsewhere.

A polymorphic design gives the varying responsibility to a common abstraction:

interface TaxCalculator {
    Money calculate(Order order);
}

class UsTaxCalculator implements TaxCalculator {
    public Money calculate(Order order) {
        // U.S. tax rules
    }
}

class EuTaxCalculator implements TaxCalculator {
    public Money calculate(Order order) {
        // European tax rules
    }
}

class CheckoutService {
    private final TaxCalculator taxCalculator;

    Money calculateTax(Order order) {
        return taxCalculator.calculate(order);
    }
}

The checkout service now depends on the operation it needs, not on the internal rules of every tax calculator. Larman’s GRASP material uses third-party tax calculators and different Monopoly-square actions as examples of problems addressed by polymorphic responsibility assignment; the book contents and chapter overview identify both examples.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

When should you use Polymorphism?

Polymorphism is a strong fit when behavior genuinely varies, the alternatives share a meaningful contract, the behavior is substantial, or new variants are likely. Polymorphism is especially useful when the same variation affects several clients. Centralizing the variation behind a contract prevents each client from growing its own incomplete conditional logic.

Polymorphism is not automatically better than a conditional. A local conditional may be clearer when there are only a few stable cases, the behavior is tiny, the type set is closed, and adding an abstraction would create more ceremony than value. Refactor a conditional when it becomes a recurring change hotspot, not merely because an if or switch exists.

Polymorphism does not require inheritance. Interfaces, abstract classes, composition, strategy objects, dependency injection, function objects, registries, sealed types, pattern matching, and algebraic data types can all support polymorphic behavior. The goal is to localize varying behavior, not to maximize subclassing.

How does Polymorphism relate to Liskov Substitution?

GRASP Polymorphism answers where varying behavior should be assigned. The Liskov Substitution Principle constrains how implementations must behave relative to the shared abstraction. An implementation should honor the contract expected by clients, including meaningful preconditions, postconditions, error behavior, and side effects.

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Two classes can expose the same method and still be poor substitutions. For example, two storage implementations may differ in durability, transaction guarantees, ordering, or failure behavior. A common method signature alone does not establish a valid behavioral contract. Larman’s discussion of Protected Variations explicitly includes the Liskov Substitution Principle among the related mechanisms for managing variation; the relationship appears in the GRASP chapter contents.

What is Pure Fabrication in GRASP?

Pure Fabrication assigns a responsibility to a deliberately invented class that is not a meaningful domain concept because the assignment improves cohesion, lowers coupling, or increases reuse. The class is fabricated for software design rather than discovered directly as a real-world object.

Pure Fabrication is particularly useful for technical responsibilities such as persistence, messaging, serialization, logging, external integration, and file export. A domain object may possess the information needed for a technical operation, but that does not mean the domain object should perform the operation.

Example: saving a sale

Suppose a Sale object contains all the data needed to be stored. Putting database code in Sale would mix business behavior with infrastructure:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
class Sale {
    void saveToDatabase(Connection connection) {
        // SQL statements
    }

    Money total() {
        // business calculation
    }
}

A fabricated repository keeps the responsibilities focused:

class Sale {
    Money total() {
        // business calculation
    }
}

class SaleRepository {
    void save(Sale sale) {
        // persistence implementation
    }
}

SaleRepository may not represent a physical object in the business domain, but it is a useful software object. Larman’s GRASP chapter uses saving a Sale object in a database as a Pure Fabrication design problem; the example is identified in the chapter overview.

What are good and bad examples of Pure Fabrication?

Good fabrications have a focused responsibility and protect domain objects from technical concerns. Examples include SaleRepository for persistence, PaymentGateway for an external payment service, EmailSender for email delivery, ReportExporter for document output, and FileLogger for technical diagnostics.

Pure Fabrication becomes harmful when it becomes a justification for putting all business behavior into services. A generic UserService containing every user-related operation, an OrderService containing every order rule, or a broad Utils class containing unrelated functions can become a service-layer version of a God object.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Pure Fabrication also does not require an anemic domain model. Domain objects can retain rules intrinsic to the domain while fabricated classes handle persistence, integration, serialization, and other technical mechanisms:

Also popular on Amazon
Sale
HTML and CSS: Design and Build Websites
  • HTML CSS Design and Build Web Sites
  • Comes with secure packaging
  • It can be a gift option
Domain objects: business rules and behavior intrinsic to the domain
Fabricated services: persistence, integration, messaging, and technical coordination

The deciding question is not whether a class is a real-world noun. The deciding question is whether the responsibility assignment produces a coherent, maintainable design without hiding or duplicating important domain behavior.

How does Indirection reduce undesirable coupling?

Indirection assigns responsibility to an intermediate object so that two other components do not depend directly on each other. The intermediary can translate, coordinate, absorb implementation details, or enforce a boundary.

A → intermediary → B

Indirection is useful when direct coupling would expose an unstable implementation, connect inappropriate architectural layers, require duplicated integration code, or make substitution and testing difficult. Direct dependency is not inherently wrong; the question is whether the dependency creates a costly or undesirable relationship.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Example: payment gateway indirection

A checkout service that directly uses a vendor SDK is coupled to that provider:

class CheckoutService {
    private final StripeClient stripeClient;

    void charge(Order order) {
        stripeClient.createCharge(order.total());
    }
}

A gateway introduces an intermediary:

interface PaymentGateway {
    PaymentResult charge(Money amount);
}

class StripePaymentGateway implements PaymentGateway {
    private final StripeClient client;

    public PaymentResult charge(Money amount) {
        return client.createCharge(amount);
    }
}

class CheckoutService {
    private final PaymentGateway paymentGateway;

    void charge(Order order) {
        paymentGateway.charge(order.total());
    }
}

PaymentGateway and StripePaymentGateway separate the checkout use case from the vendor API. The intermediary can translate models, normalize errors, apply provider-specific policies, and provide a test double. Larman’s chapter contents specifically include TaxCalculatorAdapter and PersistentStorage in its discussion of Indirection; those examples are listed in the GRASP chapter overview.

What forms can Indirection take?

Common forms of indirection include adapters, facades, controllers, mediators, repositories, gateways, proxies, service layers, dependency-injection containers, event buses, message queues, and anti-corruption layers. The mechanism is less important than the reason for inserting it.

Indirection can reduce direct coupling, localize integration logic, preserve architectural boundaries, and improve testing. The trade-off is additional conceptual distance. A call that once went from A to B may now pass through an interface, decorator, proxy, adapter, gateway, and client. Clear names, small interfaces, explicit dependency wiring, and traceable logging help keep justified indirection understandable.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

A wrapper that forwards every method without translating, protecting, coordinating, or isolating anything may be needless complexity. An intermediary earns its place when it protects a volatile dependency, translates a mismatched interface, enforces a meaningful boundary, coordinates a real collaboration, or enables a genuine substitution need.

What are Protected Variations in GRASP?

Protected Variations identifies a point of anticipated variation or instability and assigns responsibility so the rest of the system is protected from that change. A stable client depends on an abstraction while implementations or policies can evolve behind the abstraction.

stable client → stable abstraction → changing implementation

Potential variation includes a vendor API, database, file format, pricing policy, deployment environment, user-interface technology, communication protocol, regulatory rule, or product requirement. The principle applies both to variation that already exists and to a credible change that would be expensive if it spread through the system.

What is the difference between a variation point and an evolution point?

A variation point already has multiple possible implementations. Multiple shipping providers or supported document formats are examples. An evolution point has one implementation today but is likely to change, such as a single payment provider selected under a contract that may later be replaced.

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

A repository abstraction can protect application code from a database choice:

Also popular on Amazon
Sale
Game Programming Patterns
  • Brand New in box. The product ships with all relevant accessories
interface UserRepository {
    User findById(UserId id);
}

class UserService {
    private final UserRepository users;

    User load(UserId id) {
        return users.findById(id);
    }
}

class SqlUserRepository implements UserRepository {
    // SQL implementation
}

class InMemoryUserRepository implements UserRepository {
    // test implementation
}

The application depends on the stable concept of retrieving a user, not on SQL details. The same boundary can protect production code from a database replacement and make tests independent of a live database.

Larman’s treatment connects Protected Variations with variation and evolution points, information hiding, data-driven designs, service lookup, interpreter-driven designs, reflective designs, uniform access, standard languages, the Open-Closed Principle, and Liskov Substitution. These related mechanisms are listed in the book’s GRASP chapter overview.

How does Protected Variations relate to information hiding and the Open-Closed Principle?

Information hiding conceals implementation details from clients. Protected Variations applies that idea specifically to likely change or instability. The Open-Closed Principle says that software entities should generally be open to extension and closed to modification. Protected Variations can help create the stable boundary through which that extension occurs, but the concepts are related rather than interchangeable.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Examples include a PaymentGateway around a provider, a repository around database technology, a TaxCalculator around tax rules, an exporter interface around file formats, a clock abstraction around current time, and an adapter around an external protocol.

Why can Protected Variations lead to overengineering?

Protected Variations does not mean abstract every conceivable future change. Speculative abstractions create interfaces, configuration, indirection, and terminology that the current system may not need. A useful boundary is based on evidence such as existing variation, a known roadmap requirement, high replacement cost, external ownership of the dependency, frequent historical change, or regulatory uncertainty.

Before introducing an abstraction, ask whether the change is likely, whether an unprotected change would be expensive, whether the boundary represents a stable concept, and whether the abstraction imposes tolerable complexity. Larman’s material explicitly cautions against speculative Protected Variations and recommends applying the principle selectively; that caution appears in the GRASP chapter overview.

How do Polymorphism, Pure Fabrication, Indirection, and Protected Variations work together?

The four principles often describe different aspects of one design rather than four competing choices. Consider an application that must calculate tax through one of several external providers.

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
  1. Polymorphism: define a common TaxCalculator operation and put provider-specific behavior in separate implementations.
  2. Protected Variations: make the checkout workflow depend on TaxCalculator, protecting the workflow from provider changes.
  3. Indirection: place an adapter or gateway between the application’s tax model and the provider’s API.
  4. Pure Fabrication: use a software-created adapter or gateway because external API integration does not belong inside an order or checkout domain object.
CheckoutService
    → TaxCalculator
        → TaxProviderAdapter
            → ExternalTaxAPI

A combined implementation might look like this:

interface TaxCalculator {
    TaxResult calculate(Order order);
}

class ExternalTaxCalculator implements TaxCalculator {
    private final TaxProviderClient client;

    public TaxResult calculate(Order order) {
        ExternalTaxResponse response =
            client.calculateTax(toProviderRequest(order));

        return fromProviderResponse(response);
    }
}

class CheckoutService {
    private final TaxCalculator taxCalculator;

    TaxResult taxFor(Order order) {
        return taxCalculator.calculate(order);
    }
}

The abstraction expresses the application’s need, the implementation contains provider-specific behavior, and the fabricated adapter keeps integration details outside the domain model. The design is not automatically superior: its value depends on whether provider variation, change cost, or boundary concerns justify the added types.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

How should you choose among the four principles?

Use this principle when… Look for… Do not use it merely because…
Polymorphism Behavior varies by type and alternatives share a meaningful contract. A conditional exists or inheritance seems fashionable.
Pure Fabrication A domain object would become technically coupled or lose cohesion. You want a generic service to hold unrelated logic.
Indirection A direct dependency is unstable, mismatched, hard to test, or crosses an important boundary. Every dependency should be wrapped.
Protected Variations An existing or credible change point could spread costly modifications. You are imagining unlimited hypothetical futures.

These principles should be balanced against simplicity. Every abstraction has a cost in names, files, configuration, debugging distance, and cognitive load. The best design protects a real boundary while remaining easy to explain.

What failure modes should developers watch for?

Every class receives an interface

An interface is valuable at a meaningful substitution or volatility boundary. A one-implementation pair such as IOrderService and OrderServiceImpl is not automatically better than one well-named class. Ask what variation, dependency, or change the interface protects.

Polymorphism hides invalid substitutions

Implementations may share a method but disagree about transaction guarantees, durability, ordering, exceptions, or side effects. Define and test the behavioral contract, not only the method signature.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Pure Fabrication produces anemic domain objects

Separating persistence from a domain object is different from moving every business rule into an application service. Keep rules intrinsic to the domain with the objects that own the relevant concepts, while using fabricated classes for focused technical responsibilities.

Indirection creates an opaque call chain

Adapters, gateways, proxies, and facades should have clear names and narrow purposes. Remove layers that do not translate, protect, coordinate, or isolate anything.

Protected Variations becomes speculative architecture

Protect changes supported by evidence: an existing variant, a known roadmap item, an external vendor boundary, a history of frequent changes, or a costly replacement. Do not build a framework for an imaginary future.

A conditional is treated as a design failure

Small, local, stable conditionals are often clearer than a polymorphic hierarchy. Refactoring is justified when the conditional spreads, changes frequently, duplicates decisions, or contains substantial type-specific behavior.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Dependency injection is mistaken for the principle

Dependency injection is an implementation technique that can support Indirection or Protected Variations. The design principle is the reason for the boundary; dependency injection is only one way to wire the resulting objects.

What checklist can you use before adding an abstraction?

  1. What exact responsibility is being assigned?
  2. Which object currently has the information needed to perform it?
  3. Would assigning the responsibility there reduce cohesion or introduce technical coupling?
  4. Does behavior genuinely vary by type?
  5. Is a direct dependency creating a meaningful problem?
  6. What existing variation or credible evolution point needs protection?
  7. Does the proposed abstraction represent a stable concept rather than an implementation detail?
  8. Will the design preserve important domain behavior instead of creating passive data objects?
  9. What complexity will the abstraction add to testing, debugging, configuration, and onboarding?
  10. Can another developer explain the responsibility assignment without a diagram?

The checklist prevents GRASP from becoming a mechanical rulebook. GRASP is most useful as a set of questions that exposes trade-offs before code structure hardens.

Further reading

Craig Larman’s Applying UML and Patterns: An Introduction to Object-Oriented Analysis and Design and Iterative Development, third edition, is the foundational book associated with the systematic GRASP treatment. Pearson identifies the third edition as a Craig Larman title published in 2004 and describes its coverage of object-oriented analysis and design, patterns, UML, and iterative development on the official Pearson book page. The book is a good fit for readers who want GRASP in the context of object design and case studies; readers seeking a modern language-specific architecture guide may prefer a more recent, language-focused resource.

Frequently Asked Questions

Does GRASP Polymorphism require inheritance?

No. GRASP Polymorphism can use interfaces, composition, strategy objects, functions, dependency injection, registries, sealed types, or pattern matching. The goal is to localize behavior that varies by type, not to maximize subclassing.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Is Pure Fabrication the same as putting all logic in service classes?

No. Pure Fabrication creates a focused software class when assigning a responsibility to a domain object would harm cohesion or coupling. Replacing every domain behavior with oversized services can produce an anemic domain model and is not the purpose of Pure Fabrication.

Should every class have an interface for Protected Variations?

No. Protected Variations is intended for an existing variation point or a credible, costly evolution point. An interface that protects no meaningful change can add complexity without providing useful flexibility.

Is Indirection always beneficial?

No. Indirection can reduce direct coupling and isolate unstable dependencies, but every intermediary adds abstraction and navigational complexity. An adapter, gateway, facade, or other intermediary is justified when it translates, protects, coordinates, or enforces a meaningful boundary.

The Bottom Line

GRASP Principles Part 3 is best understood as four complementary ways to assign responsibility. Use Polymorphism for genuine behavioral variation, Pure Fabrication for focused technical responsibilities, Indirection for an undesirable direct dependency, and Protected Variations for a credible point of change. Use each selectively: a simpler conditional, direct dependency, or domain method is often better when no real design problem needs solving.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Quick Recap

SaleBestseller No. 2
SaleBestseller No. 3
HTML and CSS: Design and Build Websites
HTML and CSS: Design and Build Websites
HTML CSS Design and Build Web Sites; Comes with secure packaging; It can be a gift option
$16.59
SaleBestseller No. 4
Game Programming Patterns
Game Programming Patterns
Brand New in box. The product ships with all relevant accessories
$24.95
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply. Prices as of November 4, 2026 2:40 am GMT.