Object-Oriented Analysis, UML, SOLID, and Design PatternsIntroductionObject-oriented programming is not only a technique for writing classes. In professional software development, programmers must first understand a problem, represent its important concepts, assign responsibilities, and select designs that can evolve safely. Object-oriented analysis and design provide this bridge between requirements and implementation. This chapter presents a disciplined path from requirements to Java software. It introduces use cases, domain models, UML class and sequence diagrams, responsibility assignment, SOLID principles, GRASP patterns, refactoring, and commonly used creational, structural, and behavioral design patterns. The goal is not to apply patterns mechanically. A good design solves a real problem with the least unnecessary complexity. Learning ObjectivesBy the end of this chapter, you should be able to: - transform stakeholder requirements into actors, goals, and use cases;
- identify domain concepts and represent them with UML class models;
- explain interactions using sequence diagrams;
- assign responsibilities using SOLID and GRASP principles;
- recognize design smells and refactor Java code safely;
- select suitable creational, structural, and behavioral patterns;
- evaluate the benefits, costs, and risks of a proposed design;
- communicate design decisions clearly to technical and non-technical stakeholders.
From Requirements to ObjectsA requirement describes a capability, constraint, or quality expected from a system. Functional requirements state what the system should do; non-functional requirements describe qualities such as security, performance, availability, usability, or maintainability. For example, a library system may contain the following functional requirement: A librarian can register a book loan for a member if the book is available and the member has not exceeded the permitted loan limit. A poor implementation begins immediately with classes such as LibraryManager and DatabaseHelper. A stronger approach asks: - Who requests the capability?
- What goal does that person have?
- What information is required?
- What rules must hold?
- What event starts and completes the interaction?
- Which objects should own the relevant responsibilities?
Object-oriented analysis identifies concepts in the problem domain. Object-oriented design decides how software objects collaborate to support those concepts. Analysis and design are related but not identical: analysis emphasizes understanding; design emphasizes construction. Important Terminology- Actor: A person, organization, external system, or device that interacts with the system.
- Use case: A goal-oriented description of interactions between an actor and a system.
- Domain model: A conceptual representation of important domain concepts and their relationships.
- Class model: A design representation of classes, attributes, operations, and associations.
- Responsibility: An obligation assigned to an object, including knowing information or doing work.
- Coupling: The degree of dependency between modules or classes.
- Cohesion: The degree to which the responsibilities of a module belong together.
- Abstraction: A simplified representation that emphasizes relevant characteristics.
- Polymorphism: The ability to use a common abstraction while allowing varying implementations.
Use-Case AnalysisA use case should describe observable behavior rather than internal code structure. It should be written from the perspective of an actor pursuing a goal. A concise use-case template includes: | Element | Meaning | Example |
|---|
| Name | Goal-oriented title | Borrow book | | Primary actor | Main initiator | Member | | Trigger | Event that starts the use case | Member requests a loan | | Preconditions | Conditions already true | Member account exists | | Main success scenario | Normal interaction | Check availability, create loan, confirm | | Alternative flows | Valid variations or failures | Book unavailable | | Postconditions | Results after completion | Loan recorded | | Business rules | Policies that constrain behavior | Maximum five active loans |
A use case should not become a list of user-interface clicks unless those clicks are themselves important requirements. “Member borrows a book” is generally more useful than “Member clicks the green button.” The former remains valid if the system later gains a mobile application or voice interface. Use-Case RelationshipsAn include relationship indicates behavior that is required as part of another use case. For example, “Borrow book” may include “Authenticate member.” An extend relationship represents optional or conditional behavior, such as offering a reservation when a book is unavailable. These relationships should be used sparingly; excessive decomposition makes requirements difficult to read. Actors are external to the system boundary. A payment gateway, for example, may be an actor even though it is another software system. A database is usually not an actor because it is an implementation detail, unless it independently initiates or participates in a business interaction. UML Class ModelsThe Unified Modeling Language (UML) is a standardized visual language for communicating software models. UML is not a programming language and does not prescribe one development process. A class is commonly represented by a rectangle divided into three compartments: name, attributes, and operations. Visibility symbols include + for public, - for private, and # for protected. A UML operation such as +borrow(member: Member): Loan indicates a public operation named borrow, accepting a Member and returning a Loan. Relationships- Association: A structural relationship between classes.
- Multiplicity: The number of related objects, such as
1, 0..1, , or 1... - Generalization: An inheritance relationship, shown as a line with a hollow triangle.
- Realization: An implementation relationship between a class and an interface.
- Aggregation: A weak whole-part relationship; the part may exist independently.
- Composition: A strong whole-part relationship; the part's lifecycle is controlled by the whole.
- Dependency: A temporary “uses” relationship.
Consider a Library and BookCopy. If a library owns and controls the lifecycle of its inventory records, composition may be appropriate. If books can exist independently and move between libraries, a simple association is more accurate. UML notation must reflect domain meaning rather than merely decorate a diagram. A domain model should avoid premature technical detail. At analysis level, Loan, Member, and BookCopy are useful concepts. At design level, interfaces such as LoanRepository and infrastructure classes such as JdbcLoanRepository may be introduced. Sequence Models and CollaborationA sequence diagram shows objects or participants and the messages exchanged over time. Time generally moves from top to bottom. The diagram helps answer questions that a class diagram cannot answer easily: - Which object starts the interaction?
- Which object knows the required information?
- Where is a decision made?
- Which object creates another object?
- What external services are called, and in what order?
For the borrowing example, a reasonable interaction is: LibrarianUI sends borrow(memberId, copyId) to LoanController.LoanController asks MemberRepository for the member.- It asks
BookRepository for the copy. - It asks a domain service or member object whether borrowing is permitted.
- A
Loan is created. - The repositories save the changed state.
- The controller returns confirmation.
A controller should coordinate the use case but should not contain every business rule. If a controller validates member limits, calculates fines, checks availability, formats database queries, and sends email, it has become a “god object.” Responsibilities should be distributed to cohesive collaborators. Responsibility-Driven DesignA responsibility is either: - Knowing: knowing private data, related objects, or derived information; or
- Doing: performing an operation, creating an object, coordinating an interaction, or enforcing a rule.
The Information Expert principle assigns a responsibility to the class that has the information necessary to fulfill it. If a Member owns its active loans, it may be the expert for determining whether another loan is permitted. If a Catalog owns book-copy availability, it may be the expert for finding an available copy. The Creator principle suggests that class A should create instances of class B when A aggregates, contains, closely uses, or has the data needed to initialize B. A Member or LoanService may create a Loan, depending on the domain and transaction boundary. The Controller principle assigns a system operation to a non-user-interface object that represents the use case or subsystem. In Java applications, this often appears as an application-service class rather than a web controller containing all business logic. High cohesion and low coupling are complementary goals. High cohesion keeps related responsibilities together. Low coupling reduces the number and strength of dependencies. Neither is an absolute rule: a small amount of coupling is necessary for collaboration, and excessive fragmentation can reduce cohesion and increase complexity. SOLID PrinciplesSOLID is a group of design principles that support maintainable object-oriented software. They are heuristics, not laws, and should be applied in context. Single Responsibility PrincipleA class should have one reason to change. This does not necessarily mean that a class may contain only one method. A LoanService may contain several operations related to loan application, but printing reports and sending marketing messages represent different responsibilities. A useful diagnostic question is: “Which stakeholder or requirement change would cause this class to change?” If unrelated changes affect the same class, separation may be needed. Open–Closed PrincipleSoftware entities should be open for extension but closed for modification. New behavior should often be introduced through polymorphism rather than repeated edits to a large conditional statement. For example, a payment system may define: interface PaymentMethod {
PaymentResult pay(Money amount);
}
final class CardPayment implements PaymentMethod {
public PaymentResult pay(Money amount) {
// Card-specific processing
return PaymentResult.success();
}
}A new WalletPayment can implement the interface without modifying existing payment algorithms. However, abstraction has a cost. If there are only two stable cases and no likely variation, introducing many interfaces may be unnecessary. Liskov Substitution PrincipleObjects of a subtype should be usable wherever objects of the supertype are expected without breaking correctness. A subtype must honor the behavioral contract of its base abstraction. If ReadOnlyAccount extends Account but throws an exception from withdraw, it may violate substitutability if all Account objects are promised to support withdrawal. The issue is not inheritance syntax; it is incompatible behavior. Prefer a more accurate abstraction, such as separate ReadableAccount and WithdrawableAccount interfaces. Interface Segregation PrincipleClients should not be forced to depend on methods they do not use. Several focused interfaces are often better than one large interface: interface Printable { void print(); }
interface Scannable { void scan(); }A printer need not implement Scannable merely because a multifunction device can do both. Dependency Inversion PrincipleHigh-level policy should not depend directly on low-level implementation details. Both should depend on abstractions. Details should depend on policy rather than defining it. final class LoanService {
private final LoanRepository repository;
LoanService(LoanRepository repository) {
this.repository = repository;
}
}The service depends on LoanRepository, not on a particular JDBC or file implementation. Constructor injection makes the dependency explicit and simplifies testing. GRASP and Design Trade-OffsGRASP—General Responsibility Assignment Software Patterns—provides principles for assigning responsibilities: - Information Expert: assign behavior to the object with the required information.
- Creator: assign creation to a suitable closely related object.
- Controller: use an application-level object to receive system operations.
- Low Coupling: minimize unnecessary dependencies.
- High Cohesion: keep responsibilities focused.
- Polymorphism: use varying implementations behind a common type.
- Pure Fabrication: create a non-domain class when it improves cohesion or coupling, such as a repository.
- Indirection: insert an intermediary to reduce direct dependency.
- Protected Variations: isolate likely points of change behind stable interfaces.
These principles can conflict. A controller may be simpler if it directly accesses a repository, but an application service can protect the domain from interface changes. A highly generalized design may reduce future modification cost but increase present cognitive load. Good design is therefore an optimization of competing qualities, not a checklist exercise. Refactoring and Code SmellsRefactoring is the disciplined restructuring of existing code without changing its externally observable behavior. It should be supported by automated tests and small, reviewable changes. Common smells include: | Smell | Typical symptom | Possible response |
|---|
| Long method | Too many steps or nested branches | Extract method | | Large class | Many unrelated responsibilities | Extract class | | Long parameter list | Difficult construction and calling | Introduce parameter object | | Duplicate code | Same logic in several places | Extract shared abstraction carefully | | Feature envy | Method uses another class's data excessively | Move method or behavior | | Switch on type | Repeated type-based branching | Replace with polymorphism when variation is real | | Primitive obsession | Domain concepts represented by raw strings or numbers | Introduce value objects | | Shotgun surgery | One change requires edits in many classes | Consolidate responsibility or introduce abstraction |
Refactoring is not automatically beneficial. Duplicated code may be clearer than an abstraction that hides two genuinely different concepts. A refactoring should improve a measurable quality such as readability, testability, coupling, or changeability. Design PatternsA design pattern is a reusable design idea for a recurring problem in a context. It is not a copy-and-paste class library and not a guarantee of good design. Patterns introduce vocabulary and suggest collaborations. Creational PatternsFactory Method delegates object creation to a method that subclasses or implementations can vary. It is useful when clients should depend on a product abstraction rather than concrete classes. Abstract Factory creates families of related objects, such as a set of user-interface widgets for different platforms. It is useful when products must be mutually compatible. Builder separates construction of a complex object from its representation. It is valuable when an object has many optional parameters or construction steps. In Java, builders can improve readability and support immutable objects. Prototype creates new objects by copying an existing prototype. It can be useful when initialization is expensive or when objects are configured dynamically, but copying mutable state requires care. Singleton ensures one logical instance and provides global access. It is frequently overused. Global mutable state damages testability and hides dependencies. A dependency-injected service with controlled lifecycle is often preferable. Structural PatternsAdapter converts one interface into another expected by clients. It is useful when integrating legacy or third-party code without spreading incompatible interfaces through the system. Facade provides a simpler interface to a complex subsystem. A LibraryFacade might coordinate authentication, catalog search, and loan creation for a client application. Decorator attaches additional behavior to an object dynamically while preserving its interface. Logging, compression, authorization, and caching can often be represented as decorators. Composite lets clients treat individual objects and compositions uniformly, as in a file system containing files and directories. Proxy controls access to another object. Examples include lazy loading, remote access, access control, and caching proxies. Behavioral PatternsStrategy encapsulates interchangeable algorithms behind a common interface. A shipping system could select StandardShipping, ExpressShipping, or PickupShipping without changing checkout logic. Observer establishes a one-to-many dependency so that subscribers are notified when a subject changes. It is useful for events and user-interface updates, but uncontrolled observer networks can make execution difficult to trace. Command encapsulates a request as an object. It supports queues, logging, undo, and delayed execution. State allows an object to change behavior when its internal state changes. It is often clearer than a large conditional statement for workflows such as order processing. Template Method defines an algorithm skeleton and lets subclasses vary selected steps. It relies on inheritance, so Strategy may be preferable when composition and runtime substitution are more important. Chain of Responsibility passes a request through potential handlers. It is suitable for validation pipelines and middleware, provided termination and ordering are clear. Choosing Without OverengineeringSelect a pattern only when a concrete problem exists. Ask: - What variation or complexity is present?
- What is the simplest design that handles it?
- Which principle or constraint does the pattern support?
- What new classes, indirections, and testing costs will it introduce?
- Is the variation likely enough to justify abstraction?
A pattern is inappropriate when it is added merely because its name appears in a textbook. Start with a clear design, use tests to protect behavior, and refactor toward a pattern when repeated evidence justifies it. Applied Case: A Library Loan SystemThe following is an illustrative case, not a report of a real organization. BackgroundA university library wants a system that allows librarians to register loans. Members may borrow up to five items. A book copy can be loaned to only one member at a time. The system must calculate due dates differently for undergraduate students and staff, and it should support future loan policies. ProblemAn initial implementation uses one LibraryManager class. It reads database records, checks member types with if statements, calculates dates, creates loans, sends email, and formats screen messages. Every new policy requires modifying this class. Unit tests require a live database and are slow. AnalysisThe main use case is “Register loan.” The primary actor is a librarian. Important concepts are Member, BookCopy, Loan, and LoanPolicy. The system needs repositories as pure fabrication: they are technical objects that isolate persistence. The changing rule is loan duration. This suggests Strategy or a policy abstraction. The stable application flow belongs in a service. Persistence should be injected through interfaces. Approachinterface LoanPolicy {
LocalDate dueDate(Member member, LocalDate startDate);
}
final class LoanService {
private final MemberRepository members;
private final BookRepository books;
private final LoanRepository loans;
private final LoanPolicy policy;
LoanService(MemberRepository members,
BookRepository books,
LoanRepository loans,
LoanPolicy policy) {
this.members = members;
this.books = books;
this.loans = loans;
this.policy = policy;
}
Loan registerLoan(MemberId memberId, CopyId copyId, LocalDate today) {
Member member = members.require(memberId);
BookCopy copy = books.require(copyId);
if (!copy.isAvailable()) {
throw new IllegalStateException("Copy is unavailable");
}
if (!member.mayBorrow()) {
throw new IllegalStateException("Loan limit reached");
}
Loan loan = new Loan(member, copy, today,
policy.dueDate(member, today));
copy.markLoaned();
loans.save(loan);
return loan;
}
}The service coordinates the use case, while Member owns the borrowing rule, BookCopy owns availability state, and LoanPolicy supplies variable policy behavior. Repositories can be replaced with in-memory fakes during unit testing. OutcomeThe design reduces direct coupling to the database and isolates policy variation. Adding a staff policy generally requires a new policy implementation rather than edits throughout the service. The result is not pattern-heavy: it uses interfaces where variation is already a stated requirement and leaves stable concepts straightforward. Learning Points- Begin with an actor goal, not a class list.
- Put business rules near the information they govern.
- Use application services for coordination, not for every rule.
- Protect external variation behind interfaces.
- Test domain behavior independently of infrastructure.
- Avoid adding abstractions for hypothetical changes.
Challenges, Limitations, and GovernanceUML diagrams can become obsolete when they are maintained separately from source code. Teams should keep diagrams focused on important decisions and update them during design review. A diagram that attempts to show every getter and database column rarely improves communication. SOLID principles can also be misapplied. Excessive interfaces may create indirection without flexibility. Inheritance can be technically valid but semantically wrong. Dependency inversion does not mean that every class requires an interface; it means that volatile details should not control stable policy. Design decisions have governance implications. A system must protect personal data, enforce authorization, log sensitive operations appropriately, and avoid exposing confidential information in exception messages. Audit trails should be proportionate and access-controlled. In regulated contexts, retention, consent, accessibility, and data-subject rights may affect the design. Secure design includes validating inputs, applying least privilege, protecting secrets, and separating authorization policy from user-interface assumptions. AI-assisted programming can generate classes or patterns quickly, but generated code may introduce unnecessary abstractions, insecure defaults, licensing concerns, or incorrect assumptions about requirements. Developers remain responsible for reviewing behavior, tests, dependencies, and data handling. AI is most useful as a design-review aid when its suggestions are checked against explicit requirements and architectural constraints. Best Practices- Establish a clear system boundary and vocabulary.
- Write use cases around actor goals and observable outcomes.
- Model concepts before choosing frameworks or databases.
- Prefer composition over inheritance when behavior must vary.
- Keep constructors explicit and dependencies visible.
- Separate policy from infrastructure.
- Use tests before significant refactoring.
- Favor small, cohesive classes over universal manager classes.
- Introduce patterns in response to real forces and recurring structure.
- Record important design decisions and their trade-offs.
- Review diagrams with stakeholders; do not treat UML as decoration.
- Measure maintainability through change effort, defect rates, testability, and clarity rather than pattern count.
Chapter SummaryObject-oriented analysis translates requirements into actors, goals, use cases, domain concepts, and business rules. UML class models describe structural relationships, while sequence models explain time-ordered collaboration. Responsibility-driven design assigns knowing and doing responsibilities to suitable objects and seeks high cohesion with low coupling. SOLID principles guide changeable designs: classes should have focused responsibilities, abstractions should support extension, subtypes must preserve contracts, interfaces should remain focused, and stable policy should not depend directly on volatile details. GRASP complements SOLID by providing practical responsibility-assignment heuristics. Refactoring improves internal structure without changing observable behavior, but it should be guided by tests and real design problems. Design patterns provide reusable solutions: factories and builders address creation, adapters and facades address structure, and strategies, commands, observers, and state objects address behavior. Patterns must be selected deliberately because every abstraction imposes complexity. The central professional skill is not recognizing patterns but making justified design decisions that satisfy requirements while preserving clarity, testability, security, and adaptability. Key TermsObject-oriented analysis: Investigation of domain concepts, goals, rules, and system responsibilities. Object-oriented design: Planning software objects and collaborations that implement analyzed requirements. Actor: An external role that interacts with a system. Use case: A description of interactions supporting an actor's goal. UML: A standardized visual language for modeling software systems. Multiplicity: The permitted number of objects participating in a relationship. Sequence diagram: A model showing participants and messages ordered over time. Responsibility: An object's obligation to know information or perform work. Cohesion: The degree to which a class's responsibilities belong together. Coupling: The degree of dependency between software elements. SOLID: Five principles supporting maintainable object-oriented design. Dependency inversion: Dependence of policy on abstractions rather than implementation details. GRASP: A set of principles for assigning responsibilities in object-oriented design. Refactoring: Behavior-preserving restructuring of existing code. Design pattern: A reusable solution structure for a recurring design problem. Strategy: A pattern that encapsulates interchangeable algorithms. Facade: A simplified interface to a complex subsystem. Adapter: An object that converts one interface into another. Composition: A strong whole-part relationship or a technique of assembling behavior from objects. Overengineering: Adding unnecessary complexity, abstraction, or flexibility to a solution. Review QuestionsShort Answer Questions- What is the difference between a functional requirement and a non-functional requirement?
- Why should use cases be written around actor goals rather than interface clicks?
- Distinguish cohesion from coupling.
- State the purpose of constructor injection.
- What is refactoring, and why are automated tests valuable during refactoring?
Long Answer Questions- Explain how a requirement can be transformed into a use case, domain model, and sequence model. Use a suitable example.
- Discuss all five SOLID principles and explain one possible misuse of each.
- Compare Factory Method, Builder, Adapter, Facade, and Strategy in terms of the problems they address.
- Explain how GRASP principles support responsibility assignment in an object-oriented system.
- Discuss why design patterns should not be applied mechanically. Include technical and organizational consequences of overengineering.
Analytical / Application Questions- A
UniversityManager class validates students, calculates fees, writes database records, sends email, and generates PDF receipts. Identify likely design smells and propose a responsibility-oriented redesign. - A payment system contains a large
switch statement on payment type. Analyze whether Strategy, Factory Method, or both would be appropriate. State the assumptions that affect your decision. - Design the main use case, classes, and sequence of messages for reserving an unavailable library book. Identify at least one variation point and justify whether an abstraction is warranted.
Multiple Choice Questions with AnswersQuestion 1Which statement best describes a use case? A. A list of database tables used by an application B. A goal-oriented description of actor-system interaction C. A Java class containing all business rules D. A deployment diagram showing servers Correct Answer: B. A goal-oriented description of actor-system interaction Question 2Which SOLID principle requires subtypes to preserve the behavioral expectations of their supertypes? A. Single Responsibility Principle B. Interface Segregation Principle C. Liskov Substitution Principle D. Dependency Inversion Principle Correct Answer: C. Liskov Substitution Principle Question 3A class that combines unrelated business rules, persistence, email, and presentation logic most clearly suffers from which problem? A. High cohesion B. Single responsibility violation C. Encapsulation D. Polymorphism Correct Answer: B. Single responsibility violation Question 4Which pattern converts an existing interface into one expected by a client? A. Adapter B. Observer C. Builder D. State Correct Answer: A. Adapter Question 5Which pattern is most directly suited to interchangeable algorithms such as standard and express shipping? A. Composite B. Strategy C. Facade D. Prototype Correct Answer: B. Strategy Question 6In responsibility-driven design, the Information Expert principle assigns behavior primarily to the object that: A. Has the information needed to perform it B. Was created most recently C. Contains the largest number of methods D. Communicates with the user interface Correct Answer: A. Has the information needed to perform it Question 7Why is dependency injection useful in the library loan example? A. It eliminates the need for domain objects B. It makes dependencies explicit and supports substitution in tests C. It guarantees that all classes are singletons D. It automatically generates UML diagrams Correct Answer: B. It makes dependencies explicit and supports substitution in tests Question 8Which relationship represents optional or conditional behavior in UML use-case modeling? A. Generalization B. Realization C. Extend D. Composition Correct Answer: C. Extend Question 9What is the primary purpose of refactoring? A. To change externally observable behavior quickly B. To restructure code while preserving externally observable behavior C. To replace all classes with interfaces D. To increase the number of design patterns Correct Answer: B. To restructure code while preserving externally observable behavior Question 10Which statement best reflects an appropriate approach to design patterns? A. Every class should use at least one pattern B. Patterns should be selected in response to real design forces and trade-offs C. Patterns remove the need for testing D. Patterns are implementations that can be copied without adaptation Correct Answer: B. Patterns should be selected in response to real design forces and trade-offs
|