Diploma in Programming Languages (Java & Python)
Project Development Using Java and Python
•
Diploma
Software Design and Architecture
Introduction
Software design translates requirements into a structure that can be implemented, tested, maintained, and extended. Architecture describes the system’s major components, their responsibilities, communication paths, and deployment boundaries; design explains the internal organization of those components. In Java and Python projects, good design is not measured by the number of classes or frameworks used. It is measured by whether the system clearly separates concerns, protects important business rules, supports change, and remains understandable to its developers.
A poorly designed system may work initially but become expensive to modify. Common symptoms include duplicated logic, classes with too many responsibilities, direct dependence on databases throughout the code, difficult testing, and changes that unexpectedly break unrelated features. This chapter presents practical foundations for avoiding these problems through modularity, abstraction, object-oriented design, SOLID principles, UML, layered architecture, client–server models, design patterns, and technology-selection criteria.
Learning Objectives
By the end of this chapter, you should be able to:
explain modularity, cohesion, coupling, abstraction, and encapsulation;
design classes and collaborations using object-oriented principles;
apply the SOLID principles appropriately rather than mechanically;
interpret and create common UML diagrams;
structure Java and Python applications using layered architecture;
distinguish client–server, monolithic, and service-oriented arrangements;
select and justify common design patterns;
evaluate technologies using functional, technical, economic, operational, and ethical criteria.
1. Foundations of Software Design
1.1 Architecture and design
Software architecture operates at a system level. It identifies subsystems such as user interface, application services, data storage, external integrations, and security boundaries. Design operates at a closer level, defining classes, functions, interfaces, algorithms, data structures, and interactions within those subsystems.
The two levels are related but not identical. Choosing a layered architecture is an architectural decision. Deciding whether a payment calculation belongs in a PaymentService or a domain object is a design decision. Architecture constrains design, while repeated design decisions can reveal architectural weaknesses.
A useful design process is iterative:
understand functional and non-functional requirements;
identify domain concepts and system boundaries;
divide the system into responsibilities and modules;
define interfaces and dependencies;
model important interactions;
evaluate quality attributes and risks;
implement a small vertical slice;
test, review, and refactor.
Functional requirements describe what a system does, such as registering a student. Non-functional requirements describe qualities or constraints, such as response time, availability, security, portability, and maintainability. A design that satisfies features but ignores security or operational cost is incomplete.
1.2 Modularity
Modularity divides a system into units with defined responsibilities and interfaces. A module may be a Python package, a Java package, a service, a library, or a deployable component. A good module hides implementation details and exposes only what other modules need.
Two related properties are central:
Cohesion is the degree to which the elements of a module belong together. A module containing only enrolment operations has higher cohesion than one containing enrolment, invoice formatting, and email delivery.
Coupling is the degree of dependence between modules. Low coupling is generally desirable because changes in one module are less likely to force changes elsewhere.
The objective is not zero coupling. A useful system must communicate. The objective is purposeful, explicit, and stable coupling through interfaces or well-defined data contracts.
1.3 Abstraction and encapsulation
Abstraction represents the essential features of a concept while omitting irrelevant detail. A Repository interface may expose save and find operations without revealing whether data is stored in PostgreSQL, a file, or memory.
Encapsulation places data and the operations that protect its rules behind a boundary. For example, an Account object should not allow arbitrary external code to set a negative balance if the domain forbids it. In Java, access modifiers and interfaces support encapsulation. In Python, convention, properties, name mangling, and disciplined APIs support it, although Python permits more direct access than Java.
Polymorphism allows different implementations to be used through a common contract. A NotificationSender interface may have EmailNotificationSender and SmsNotificationSender implementations. Client code can depend on the abstraction rather than a particular delivery mechanism.
2. Object-Oriented Design
Object-oriented design models a system as collaborating objects. A class describes a group of objects with common state and behaviour; an object is a runtime instance of that class. In professional development, object orientation is not simply the creation of many classes. It is the careful assignment of responsibilities.
2.1 Responsibilities and relationships
A class should have a clear reason to change. Responsibilities may include maintaining valid state, calculating a domain result, coordinating a use case, or adapting an external API. Relationships include:
Association: one object knows or uses another.
Aggregation: a whole groups parts that may exist independently.
Composition: a whole owns parts whose lifecycle is strongly connected.
Inheritance: a specialized type extends a general type.
Dependency: one element temporarily relies on another, such as a method parameter.
Composition is often safer than inheritance because it avoids rigid hierarchies. A ReportService can receive a Formatter object rather than inheriting from every possible report format. Inheritance is appropriate when the subtype genuinely satisfies the behavioural contract of the base type, not merely because the names appear related.
2.2 A small example
Consider a library application. A Book entity stores identity and descriptive information. A Loan entity records which member borrowed which book and the relevant dates. A LoanService coordinates borrowing rules. A BookRepository stores and retrieves books. These responsibilities should not be merged into a single Library class that performs database access, date calculations, email delivery, and user-interface formatting.
A Java interface might be expressed conceptually as follows:
public interface BookRepository {
Book findById(String id);
void save(Book book);
}
A Python equivalent could use a protocol or an abstract base class:
from typing import Protocol
class BookRepository(Protocol):
def find_by_id(self, book_id: str) -> Book: ...
def save(self, book: Book) -> None: ...
The syntax differs, but the design intention is the same: the service depends on a contract, not on a specific database implementation.
3. SOLID Principles
SOLID is a set of object-oriented design principles. They are heuristics for managing change, not laws that must be applied without judgment.
Single Responsibility Principle
A class should have one coherent responsibility and therefore one main reason to change. A class that validates orders, writes SQL, and sends receipts has several unrelated reasons to change. Splitting those concerns improves cohesion and testing.
The principle does not mean every class must contain one method. A cohesive class may contain several operations that serve one responsibility.
Open–Closed Principle
Software entities should be open for extension but closed for modification. If adding a new shipping strategy requires editing a large conditional statement in many locations, the design is fragile. A Strategy interface with separate shipping implementations can permit extension with less modification to stable code.
This principle is not a demand to predict every future variation. Excessive abstraction creates complexity. Apply it where a variation is likely and meaningful.
Liskov Substitution Principle
Objects of a subtype should be usable wherever objects of the base type are expected without breaking the program’s correctness. A subtype must honour the assumptions, preconditions, postconditions, and meaningful behaviour of its abstraction.
For example, if a collection contract promises that items can be added, an implementation that rejects every addition is not a valid substitute merely because it inherits from the collection class. Inheritance should express a valid behavioural relationship.
Interface Segregation Principle
Clients should not be forced to depend on methods they do not use. Several focused interfaces are often better than one large interface. A printer client may need Print, while a scanner client needs Scan; combining unrelated operations can burden simple implementations.
Dependency Inversion Principle
High-level policy should not depend directly on low-level details. Both should depend on abstractions. Details such as databases, frameworks, and email providers should implement interfaces required by application policy.
Dependency injection is a practical technique for achieving this principle. Dependencies are supplied through constructors, parameters, or configuration rather than created internally. Constructor injection is particularly useful because required dependencies are visible and objects can be tested with fakes or mocks.
4. UML as a Design Communication Tool
The Unified Modeling Language is a standardized visual language for describing software structures and behaviour. UML is not a programming language and does not replace source code. Its value lies in communicating decisions at an appropriate level of detail.
4.1 Common diagrams
A use-case diagram shows actors and the goals they pursue, such as Librarian issuing a book. It describes system scope from a user perspective.
A class diagram shows classes, attributes, operations, and relationships. Visibility symbols commonly include + for public, − for private, and # for protected. Multiplicity indicates quantities such as 1, 0..1, or 0..*.
A sequence diagram shows messages over time. It is useful for tracing a scenario such as a user submitting an order, an application service validating it, a repository storing it, and a payment gateway authorizing payment.
An activity diagram represents workflow and decisions. A state-machine diagram represents lifecycle changes, such as an order moving from Pending to Paid, Shipped, and Delivered.
4.2 Modelling guidance
Model the problem rather than every implementation detail. Use diagrams when they answer a design question: What owns this responsibility? Which component calls the payment gateway? What states are legal? Keep diagrams current, version them with the project, and remove obsolete detail. A small accurate diagram is more useful than a large decorative one.
5. Layered Architecture
Layered architecture separates a system according to responsibilities. A common arrangement is:
Layered software architecture
Figure: Layered software architecture
Figure: A conventional layered architecture and its principal dependency direction.
Presentation layer: user interface, controllers, command-line handlers, or API endpoints. It translates external input and output.
Application or service layer: coordinates use cases, transactions, authorization checks, and interactions between domain objects and ports.
Domain layer: business concepts and rules. It should not depend unnecessarily on web frameworks or database drivers.
Infrastructure and data layer: persistence, messaging, file systems, external services, and framework-specific adapters.
The exact names vary. Some projects combine application and domain services; others use hexagonal or clean architecture. The essential goal is to prevent user-interface and infrastructure details from leaking into business rules.
A strict top-down dependency rule is easy to understand but not universal. Dependency inversion may allow an inner domain abstraction to be implemented by an outer infrastructure adapter. Teams should document the actual dependency policy and enforce it through package rules, code review, and automated tests.
6. Client–Server and Deployment Models
In a client–server model, a client requests services and a server provides them. A web browser is a client; an HTTP application server responds; a database server may provide persistence. Communication occurs over a protocol and therefore introduces latency, failure, authentication, serialization, and versioning concerns.
A monolith is deployed as one application unit, although it may still have strong internal modules. A modular monolith often provides a practical starting point because it keeps deployment simple while enforcing boundaries. Microservices split capabilities into independently deployable services. They can support team autonomy and selective scaling, but they also introduce distributed-system complexity, operational cost, network failures, observability requirements, and data-consistency challenges.
A client–server design should be selected because its quality attributes require it, not because distributed systems appear modern. For a small college administration system, a modular monolith with a relational database may be more maintainable than many services.
Important interface concerns include:
stable resource and operation names;
validation and meaningful error responses;
authentication and authorization;
idempotency for retryable operations;
timeouts, retries, and rate limits;
compatibility and versioning;
logging, metrics, and trace correlation.
7. Design Patterns
A design pattern is a reusable solution structure for a recurring design problem. It is not a copy-and-paste class hierarchy.
Factory Method or factory function: centralizes creation when construction varies or should be hidden.
Strategy: encapsulates interchangeable algorithms, such as discount or sorting policies.
Adapter: converts one interface into another, useful for legacy libraries or external APIs.
Facade: provides a simpler interface to a complex subsystem.
Observer or publish–subscribe: allows interested components to receive events, but requires attention to ordering, failure, and memory or subscription management.
Repository: provides a domain-oriented persistence boundary.
Dependency Injection: supplies collaborators externally and supports substitution during testing.
Patterns have costs. A Singleton can hide global state and make tests difficult. An event-driven design can obscure control flow. A factory is unnecessary when direct construction is already clear. Use a pattern when it removes a demonstrated design problem and explain the trade-off.
8. Selecting Technologies
Technology selection is an engineering decision, not a popularity contest. Begin with requirements and constraints. A simple decision matrix can make reasoning explicit. Let each criterion have a weight wᵢ and let a technology receive a score sᵢ on a defined scale. A weighted score is:
S = ∑(wᵢ × sᵢ), for i = 1 to n
Here S is the total score, wᵢ is the weight of criterion i, sᵢ is the technology’s score for that criterion, and n is the number of criteria. If weights are normalized so that their sum is 1, S lies on the same scale as the scores. The method assumes criteria are sufficiently independent and that scores and weights are documented; it does not eliminate judgement or prove that the highest score is objectively best.
Relevant criteria include:
Criterion
Questions to ask
Functional fit
Does the technology provide required capabilities?
Maintainability
Can the team understand, test, upgrade, and debug it?
Performance
Does it meet realistic workload and latency requirements?
Security
Are updates, identity controls, defaults, and vulnerability responses credible?
Integration
Does it work with existing systems and protocols?
Team capability
Can the team operate it effectively?
Cost
What are licensing, hosting, training, and migration costs?
Community and support
Are documentation, suppliers, and long-term support available?
Portability
Can the system avoid unnecessary vendor lock-in?
Compliance
Can privacy, accessibility, audit, and retention obligations be met?
A disciplined selection process defines must-have constraints first, compares a short list, builds a proof of concept for uncertain risks, measures realistic workloads, and records the decision and its assumptions. Java may be appropriate where strong static typing, mature enterprise tooling, and long-lived platforms are important. Python may be appropriate where rapid development, automation, data processing, and a large library ecosystem are valuable. Neither language is universally superior; architecture and operational context matter more than language branding.
9. Illustrative Case: College Equipment Booking System
Background
A college wants students to reserve cameras and laboratory equipment. The existing prototype stores booking data in spreadsheets, permits overlapping reservations, and requires staff to send confirmation emails manually.
Problem
The new system must provide a web interface, prevent overlapping bookings, support staff approval, retain an audit history, and allow future integration with an identity provider. The team has Java and Python experience and a limited operations budget.
Analysis
The core domain concepts are Equipment, Student, Booking, Approval, and TimeInterval. The critical invariant is that approved bookings for the same equipment must not overlap. The user interface, database, email provider, and identity provider are external concerns. The business rule should remain testable without requiring a web server or live email service.
Approach and solution
The team chooses a modular monolith with a layered architecture. Controllers translate HTTP requests into application commands. BookingService coordinates the reservation use case. Booking and TimeInterval contain domain rules. BookingRepository is an abstraction implemented by a relational database adapter. EmailSender is another abstraction implemented by an email adapter.
A simplified conflict rule is expressed as: two intervals overlap when the start of one is earlier than the end of the other and the start of the other is earlier than the end of the first. The team defines whether end times are exclusive, so a booking ending at 10:00 can be followed by one starting at 10:00. This explicit decision prevents ambiguity.
The application uses dependency injection, a repository pattern, and a Strategy for calculating deposits because different equipment categories have different policies. A sequence diagram is prepared for the reserve-equipment scenario. Automated tests cover valid reservations, overlapping reservations, approval transitions, unauthorized access, and repository failures.
Outcome
The design permits the team to replace the email provider without changing BookingService. Domain tests run quickly without a database. A modular monolith keeps deployment affordable, while package boundaries leave open a future integration service if operational evidence justifies it. The system does not initially use microservices because independent scaling and deployment are not yet demonstrated requirements.
Learning points
The case illustrates that architecture should follow requirements, that business rules deserve a technology-independent home, and that explicit boundaries make change safer. It also shows that a design decision includes what the team deliberately does not build.
10. Quality, Governance, and Contemporary Practice
Design quality should be evaluated through evidence. Useful indicators include defect trends, testability, change lead time, dependency complexity, deployment failure rate, security findings, and operational incidents. No single metric proves quality. A low class count, high test coverage percentage, or large number of services can be misleading without context.
Security must be designed rather than added at the end. Apply least privilege, validate input, protect credentials, use secure transport, avoid sensitive data in logs, manage dependencies, and define access controls. Privacy requires collecting only necessary data, limiting retention, controlling access, and supporting lawful handling of personal information. Accessibility should be considered in interface design, keyboard operation, readable presentation, and assistive-technology compatibility.
AI-assisted programming can generate tests, explain code, or propose refactorings, but generated output must be reviewed, tested, and checked for licensing, privacy, security vulnerabilities, and fabricated APIs. Developers remain accountable for architecture and deployed behaviour. Do not submit confidential source code or personal data to an AI tool unless organizational policy and contractual controls permit it.
11. Best Practices and Common Failure Modes
Effective practice includes:
name modules and classes after domain responsibilities;
keep interfaces small and stable;
prefer composition when it reduces inheritance coupling;
inject external dependencies;
make invalid states difficult to create;
test domain rules independently;
document architectural decisions and their trade-offs;
use code review and automated quality checks;
measure real workloads before optimizing;
refactor continuously rather than allowing design debt to accumulate.
Common failures include creating a large service class, using inheritance for code reuse alone, exposing database entities directly as public API contracts, allowing every module to access every other module, introducing patterns without a problem, and selecting technologies without considering operations. A simple design with clear boundaries is usually more valuable than an elaborate design whose benefits cannot be demonstrated.
Chapter Summary
Software architecture defines major components, boundaries, and deployment relationships, while software design assigns detailed responsibilities and collaborations. Modularity depends on high cohesion and purposeful low coupling. Abstraction, encapsulation, and polymorphism help protect changeable implementation details.
Object-oriented design should assign coherent responsibilities and use composition, interfaces, and dependency injection where appropriate. SOLID principles provide guidance for managing change, but they must be applied with judgement. UML diagrams communicate scope, structure, interactions, workflows, and state transitions when they answer real design questions.
Layered architecture separates presentation, application coordination, domain rules, and infrastructure. Client–server, modular monolith, and microservice models involve different operational trade-offs. Design patterns such as Strategy, Adapter, Facade, Repository, and Dependency Injection solve recurring problems but introduce costs when misused. Technology selection should use requirements, explicit criteria, evidence, and documented trade-offs. Security, privacy, accessibility, maintainability, and operational responsibility are part of design quality.
Key Terms
Modularity: Division of a system into units with defined responsibilities and interfaces.
Cohesion: The degree to which elements of a module belong together.
Coupling: The degree of dependency between modules.
Abstraction: Representation of essential features while hiding irrelevant detail.
Encapsulation: Protection of state and implementation behind a controlled boundary.
Polymorphism: Use of different implementations through a common abstraction.
Interface: A contract describing services or behaviour available to clients.
Dependency injection: Supplying collaborators externally instead of constructing them internally.
SOLID: Five object-oriented principles for managing responsibility and change.
UML: A standardized visual language for modelling software.
Layered architecture: Organization of software into responsibility-based layers.
Client–server model: Arrangement in which clients request services from servers.
Monolith: An application deployed as one unit.
Microservice: An independently deployable service focused on a bounded capability.
Design pattern: A reusable solution structure for a recurring design problem.
Repository: An abstraction over persistence operations for domain objects.
Strategy: A pattern that encapsulates interchangeable algorithms or policies.
Quality attribute: A non-functional property such as security, performance, or maintainability.
Technology decision matrix: A structured comparison using criteria, weights, and scores.
Technical debt: Future cost created by expedient or inadequate technical decisions.
Review Questions
Short Answer Questions
Distinguish software architecture from software design.
Define cohesion and coupling.
What problem does dependency injection address?
Name three UML diagrams and state one use for each.
Why might a modular monolith be selected instead of microservices?
Long Answer Questions
Explain all five SOLID principles and illustrate each with a Java or Python example.
Describe a layered architecture and explain how dependency inversion can protect domain logic from infrastructure details.
Compare inheritance and composition, including situations in which each is appropriate.
Explain the purpose and limitations of design patterns, using at least three examples.
Develop a technology-selection process for a small business application, including technical, economic, operational, and ethical criteria.
Analytical / Application Questions
A single OrderManager validates orders, writes database records, calculates discounts, and sends emails. Analyse the design using cohesion, coupling, and SOLID principles, then propose a revised structure.
A team proposes splitting a small college booking application into eight microservices. Evaluate the proposal against requirements, team capability, deployment cost, data consistency, and operational complexity. Recommend an alternative if appropriate.
Design the main classes, interfaces, and sequence of interactions for a library borrowing use case. Identify at least one domain rule, one external dependency, one suitable design pattern, and the UML diagrams that would communicate your design best.
Multiple Choice Questions with Answers
Question 1
Which property describes how closely the responsibilities within a module belong together?
A. Coupling
B. Cohesion
C. Polymorphism
D. Multiplicity
Correct Answer: B. Cohesion
Question 2
Which SOLID principle states that high-level policy should not depend directly on low-level details?
A. Single Responsibility Principle
B. Open–Closed Principle
C. Dependency Inversion Principle
D. Interface Segregation Principle
Correct Answer: C. Dependency Inversion Principle
Question 3
Which UML diagram is most suitable for showing messages exchanged over time in a use case?
A. Class diagram
B. Sequence diagram
C. Deployment diagram
D. Package diagram
Correct Answer: B. Sequence diagram
Question 4
What is the principal purpose of an Adapter pattern?
A. To guarantee only one object exists
B. To convert one interface into another expected by clients
C. To store all objects in a database
D. To represent workflow states
Correct Answer: B. To convert one interface into another expected by clients
Question 5
Which layer should normally contain HTTP request translation in a layered application?
A. Presentation layer
B. Domain layer
C. Data storage layer
D. Operating-system layer
Correct Answer: A. Presentation layer
Question 6
A strategy object is most useful when a system needs to support:
A. Several interchangeable algorithms or policies
B. One immutable configuration value
C. A mandatory global variable
D. Only database table creation
Correct Answer: A. Several interchangeable algorithms or policies
Question 7
If normalized weights in a technology decision matrix sum to 1, what does the weighted total generally represent?
A. The number of source-code files
B. A score on the same scale as the criterion scores
C. The application’s network latency in milliseconds
D. The number of project stakeholders
Correct Answer: B. A score on the same scale as the criterion scores
Question 8
Which choice best illustrates composition over inheritance?
A. A service receives a Formatter object and delegates formatting to it
B. Every class extends a universal BaseClass
C. A subclass changes the meaning of a parent method
D. A database table inherits from a controller
Correct Answer: A. A service receives a Formatter object and delegates formatting to it
Question 9
Why should business rules often be kept independent of web and database frameworks?
A. To make the rules harder to test
B. To reduce portability and increase coupling
C. To improve testability and protect policy from infrastructure changes
D. To eliminate the need for interfaces
Correct Answer: C. To improve testability and protect policy from infrastructure changes
Question 10
Which is a significant risk introduced by microservices compared with a well-structured monolith?
A. They cannot communicate over networks
B. They eliminate all deployment concerns
C. They introduce distributed-system and operational complexity
D. They prevent independent deployment
Correct Answer: C. They introduce distributed-system and operational complexity
Project Development Using Java and Python
Chapter 4
of 15