A comprehensive Java project demonstrating fundamental Object-Oriented Programming (OOP) concepts and SOLID principles with practical examples and real-world use cases.
This project is a collection of educational modules that cover essential OOP concepts in Java. Each module includes working examples and demonstrates best practices in software design and architecture.
Target Audience: Beginner to Intermediate Java developers
Location: CallByValueAndReference/
Demonstrates the difference between calling methods with primitive types (call by value) and objects (call by reference).
- Main.java: Contains multiple overloaded
change()methods showing:- Primitives (int, String): Called by value - changes inside methods don't affect original variables
- Arrays: Called by reference - modifications inside methods affect the original array
- Objects: Called by reference - modifications to object properties persist
┌─────────────────────────────────────────┐
│ Call By Value vs Call By Reference │
├─────────────────────────────────────────┤
│ Primitives (int, float, etc.) → VALUE │
│ Objects → REFERENCE │
│ Arrays → REFERENCE │
│ Strings → VALUE (immutable) │
└─────────────────────────────────────────┘
int x = 5;
change(x); // x remains 5 (call by value)
Reference obj = new Reference(1);
change(obj); // obj.x changes (call by reference)Location: Constructor/
Explores different types of constructors and their role in object initialization.
- Constructor.java: Demonstrates:
- Default constructor (no arguments)
- Parameterized constructors (with arguments)
- Constructor chaining (
this()) - Copy constructor
| Type | Purpose | Example |
|---|---|---|
| Default | No arguments, initializes default values | new Constructor() |
| Parameterized | Takes arguments to initialize fields | new Constructor(20, "Nada", 2000f) |
| Constructor Chaining | Calls another constructor using this() |
Reduces code duplication |
| Copy Constructor | Creates a new object copying data from existing | new Constructor(ob4) |
private int age; // Default: 0
private String name; // Default: null
private float amount; // Default: 0.0f
private String color; // Optional parameter// Default constructor
Constructor ob = new Constructor();
// Parameterized constructor
Constructor ob2 = new Constructor(20);
Constructor ob3 = new Constructor(20, "Nada", 2000f);
// Constructor chaining
Constructor ob4 = new Constructor(52, "Ali", 200f, "Red");
// Copy constructor
Constructor ob5 = new Constructor(ob4);Location: Inheritance/
Demonstrates class inheritance where a child class inherits properties and methods from a parent class.
-
Employee.java (Parent Class)
- Properties:
name,email,phone,department,address - Constructors: Default and parameterized
- Methods: Getters and setters for all properties
- Properties:
-
Developer.java (Child Class)
- Extends Employee
- Additional property:
projectName - Overridden/additional methods
┌─────────────┐
│ Employee │ (Parent)
│ (Parent) │
└──────┬──────┘
│
│ extends
│
┌──────▼──────┐
│ Developer │ (Child)
│ (Child) │
└─────────────┘
- Access Modifier:
protectedallows child classes to access parent properties - Constructor Chaining: Child constructor can call parent constructor
- Method Overriding: Child class can override parent methods
- Polymorphism: Parent reference can hold child object
// Parent class
Employee emp = new Employee("Dodo", "abdohosh100@gmail.com", 50152933, "H1", "Cairo");
// Child class
Developer dev = new Developer("Dodo", "abdohosh100@gmail.com", 50152933, "H1", "Cairo", "OOP");
System.out.println(dev.getProjectName()); // OOPLocation: Static_Members/
Demonstrates static variables and methods that belong to the class rather than individual objects.
- Static.java: Student registration system showing:
- Static variables:
idcounter andfacultyconstant - Static methods:
validatePassword() - Instance variables:
name,password
- Static variables:
| Feature | Static | Instance |
|---|---|---|
| Scope | Belongs to class | Belongs to object |
| Memory | Shared by all objects | Unique per object |
| Access | ClassName.staticVar |
this.instanceVar |
| Initialization | Once at class loading | Each object creation |
public class Static {
private static int id = 0; // Static - shared by all
private static String faculty = "CS"; // Static - shared by all
private String name; // Instance - unique per object
public static boolean validatePassword(String password) {
return password.length() >= 6; // Static method - no access to instance vars
}
}Static ob1 = new Static("Ali", "123589");
ob1.display(); // ID: 1
Static ob2 = new Static("Ahmed", "1244841289");
ob2.display(); // ID: 2 (ID incremented)
ob4.login(3, "18552003"); // Login successfullyThe SOLID principles are five design principles that help create maintainable, scalable, and robust software. This project includes practical implementations of each principle.
Location: SOLID Principles/
Location: SOLID Principles/Sinlge Responsibility/
Principle: A class should have only one reason to change.
Each class should have a single, well-defined responsibility. This makes code easier to test, maintain, and understand.
- Employee.java: Represents employee data
- SalaryCalculator.java: Handles salary calculation logic
- EmployeeRepository.java: Handles employee data persistence
❌ BAD DESIGN
┌──────────────────────────────────┐
│ Employee │
├──────────────────────────────────┤
│ - name │
│ - salary │
│ - calculateSalary() │
│ - saveToDatabase() │
│ - generateReport() │
└──────────────────────────────────┘
(Multiple responsibilities - too many reasons to change)
✅ GOOD DESIGN
┌──────────────┐ ┌──────────────────┐ ┌──────────────────┐
│ Employee │ │ SalaryCalculator │ │ EmployeeRepository
├──────────────┤ ├──────────────────┤ ├──────────────────┤
│ - name │ │ - calculateSal.. │ │ - save() │
│ - salary │ │ │ │ - update() │
└──────────────┘ └──────────────────┘ └──────────────────┘
(Each class has ONE reason to change)
// Employee - only data
public class Employee {
private String name;
}
// SalaryCalculator - handles calculations
public class SalaryCalculator {
public double calculateSalary(Employee emp) {
return emp.getSalary() * 1.1; // 10% bonus
}
}
// EmployeeRepository - handles persistence
public class EmployeeRepository {
public void save(Employee emp) {
// Save to database
}
}✓ Easier Testing: Test each responsibility independently
✓ Maintainability: Changes to one responsibility don't affect others
✓ Reusability: Classes can be reused in different contexts
✓ Clarity: Easier to understand what each class does
Location: SOLID Principles/Open_Closed/
Principle: Software entities should be open for extension but closed for modification.
Classes should be designed so new functionality can be added through extension, not by modifying existing code.
- Shape.java: Abstract base class (interface)
- Rectangle.java: Concrete implementation
- Circle.java: Concrete implementation
- AreaCalculator.java: Calculates area for any shape
┌──────────────────────────┐
│ <<abstract>> │
│ Shape │
├──────────────────────────┤
│ + getArea(): double │
└────────────┬─────────────┘
│
┌──────┴──────┐
│ │
┌─────▼─────┐ ┌────▼──────┐
│ Rectangle │ │ Circle │
├───────────┤ ├───────────┤
│ - length │ │ - radius │
│ - width │ │ │
└───────────┘ └───────────┘
┌──────────────────────────┐
│ AreaCalculator │
├──────────────────────────┤
│ + calculate(shape): │
│ return shape.area() │
└──────────────────────────┘
❌ BAD DESIGN (Closed for extension)
calculateArea(shape):
if shape is Rectangle:
return length * width
if shape is Circle:
return π * r²
(Every new shape requires modifying this method!)
✅ GOOD DESIGN (Open for extension)
calculateArea(shape):
return shape.getArea()
(Just add new Shape implementations!)
// Abstract shape - closed for modification
public abstract class Shape {
public abstract double getArea();
}
// Concrete implementations - open for extension
public class Rectangle extends Shape {
private double length, width;
@Override
public double getArea() {
return length * width;
}
}
public class Circle extends Shape {
private double radius;
@Override
public double getArea() {
return Math.PI * radius * radius;
}
}
// Calculator - unchanged, works with any shape
public class AreaCalculator {
public double calculateArea(Shape shape) {
return shape.getArea();
}
}AreaCalculator calc = new AreaCalculator();
Shape rectangle = new Rectangle(5, 6.5);
System.out.println(calc.calculateArea(rectangle)); // 32.5
Shape circle = new Circle(7.9);
System.out.println(calc.calculateArea(circle)); // 196.1...
// Adding new shape doesn't require changing AreaCalculator!✓ Low Risk: No modification to existing code = fewer bugs
✓ Extensibility: Easy to add new shapes
✓ Maintainability: Existing functionality stays stable
✓ Code Reuse: Abstract class used with all implementations
Location: SOLID Principles/Listkov Substitution/
Principle: Subtypes must be replaceable for their base types without breaking behavior.
Objects of a child class should be substitutable for objects of the parent class without altering the correctness of the program.
- Shape.java: Base class with abstract methods
- Rectangle.java: Child class implementation
- Square.java: Child class implementation
Real-world relationship:
Square IS-A Rectangle (mathematically true)
But in OOP:
Square should NOT inherit from Rectangle
(violates LSP because Square changes Rectangle's behavior)
❌ BAD DESIGN (Violates LSP)
Rectangle extends Shape:
- setLength(double l)
- setWidth(double w)
Square extends Rectangle:
- setLength() → sets both length AND width
- setWidth() → sets both length AND width
(Square changes Rectangle's behavior - violates LSP!)
✅ GOOD DESIGN (Respects LSP)
Both inherit from Shape:
- getArea() works correctly for each
No inheritance between Square and Rectangle
(Each implements getArea() according to its own rules)
// Base class
public abstract class Shape {
public abstract double getArea();
}
// Rectangle - standard behavior
public class Rectangle extends Shape {
private double length, width;
public void setLength(double l) { this.length = l; }
public void setWidth(double w) { this.width = w; }
@Override
public double getArea() {
return length * width;
}
}
// Square - follows LSP
public class Square extends Shape {
private double side;
public void setSide(double s) { this.side = s; }
@Override
public double getArea() {
return side * side;
}
}Shape shape = new Rectangle(5, 6);
System.out.println(shape.getArea()); // 30
Shape shape2 = new Square(10);
System.out.println(shape2.getArea()); // 100
// Both can be substituted for Shape without issues!✓ Predictable Behavior: Child class behaves as expected
✓ Polymorphism Safety: Can safely use parent references
✓ Testing: Easier to test with polymorphic types
✓ Reliability: No surprises when substituting objects
Location: SOLID Principles/interface Segregation/
Principle: Clients should not be forced to depend on interfaces they don't use.
Create multiple specific interfaces rather than one large general-purpose interface. Classes implement only the interfaces they need.
- Workable.java: Interface for working entities
- Eatable.java: Interface for entities that eat
- Sleepable.java: Interface for entities that sleep
- Human.java: Implements all interfaces (Workable, Eatable, Sleepable)
- Robot.java: Implements only Workable interface
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ Workable │ │ Eatable │ │ Sleepable │
├──────────────┤ ├──────────────┤ ├──────────────┤
│ + work() │ │ + eat() │ │ + sleep() │
└──────┬───────┘ └──────┬───────┘ └──────┬───────┘
│ │ │
│ ┌───────┴─────────────────┤
│ │ │
┌──▼─────────▼──────┐ ┌─────────▼────┐
│ Human │ │ Robot │
├──────────────────┤ ├──────────────┤
│ + work() │ │ + work() │
│ + eat() │ │ │
│ + sleep() │ │ │
└──────────────────┘ └──────────────┘
❌ BAD DESIGN (Fat Interface)
interface Worker {
void work();
void eat();
void sleep();
void maintain();
}
Robot implements Worker {
void work() { /* OK */ }
void eat() { /* ERROR - Robot can't eat */ }
void sleep() { /* ERROR - Robot doesn't sleep */ }
void maintain() { /* OK */ }
}
✅ GOOD DESIGN (Segregated Interfaces)
interface Workable { void work(); }
interface Eatable { void eat(); }
interface Sleepable { void sleep(); }
Human implements Workable, Eatable, Sleepable {
void work() { /* OK */ }
void eat() { /* OK */ }
void sleep() { /* OK */ }
}
Robot implements Workable {
void work() { /* OK */ }
}
// Segregated interfaces
public interface Workable {
void work();
}
public interface Eatable {
void eat();
}
public interface Sleepable {
void sleep();
}
// Human implements all
public class Human implements Workable, Eatable, Sleepable {
@Override
public void work() { System.out.println("Working..."); }
@Override
public void eat() { System.out.println("Eating..."); }
@Override
public void sleep() { System.out.println("Sleeping..."); }
}
// Robot implements only what it needs
public class Robot implements Workable {
@Override
public void work() { System.out.println("Processing..."); }
}✓ Flexibility: Classes implement only needed interfaces
✓ No Bloated Interfaces: Each interface has focused responsibility
✓ Cleaner Code: No empty/dummy method implementations
✓ Better Design: Easier to understand and maintain
Location: SOLID Principles/Dependency Inversion/
Principle: High-level modules should depend on abstractions, not concrete implementations.
Depend on interfaces/abstractions rather than concrete classes. This reduces coupling and improves flexibility.
- Database.java: Abstract interface (abstraction layer)
- MySqlDatabase.java: Concrete MySQL implementation
- PostgresDatabase.java: Concrete PostgreSQL implementation
- UserService.java: Depends on Database interface, not concrete classes
Without Dependency Inversion (Tightly Coupled):
┌─────────────────┐
│ UserService │
├─────────────────┤
│ - mySqlDb │◄─────── Directly depends on
└────────┬────────┘ concrete class
│
▼
┌──────────────────┐
│ MySqlDatabase │
└──────────────────┘
With Dependency Inversion (Loosely Coupled):
┌─────────────────┐
│ UserService │
├─────────────────┤
│ - database │◄─────── Depends on
└────────┬────────┘ abstraction
│
▼
┌──────────────────┐
│ <<interface>> │
│ Database │
└────────┬─────────┘
│
┌──────┴──────────┐
│ │
┌─▼───────────┐ ┌──▼─────────┐
│ MySql │ │ Postgres │
│ Database │ │ Database │
└─────────────┘ └────────────┘
❌ BAD DESIGN (Tightly Coupled)
class UserService {
private MySqlDatabase db = new MySqlDatabase();
void process() {
db.connect();
db.save();
}
}
// Problem: Hard-coded dependency on MySqlDatabase
// Can't switch to PostgreSQL without changing code
✅ GOOD DESIGN (Loosely Coupled)
interface Database {
void connect();
void save();
}
class UserService {
private Database db; // Depends on abstraction
public UserService(Database db) {
this.db = db; // Inject concrete implementation
}
void process() {
db.connect();
db.save();
}
}
// Easy to switch implementations at runtime
// Abstraction layer
public interface Database {
void connect();
void disconnect();
void save();
void retrieve();
}
// Concrete implementation 1
public class MySqlDatabase implements Database {
@Override
public void connect() { System.out.println("Connecting to MySQL..."); }
@Override
public void save() { System.out.println("Saving to MySQL..."); }
// ... other methods
}
// Concrete implementation 2
public class PostgresDatabase implements Database {
@Override
public void connect() { System.out.println("Connecting to PostgreSQL..."); }
@Override
public void save() { System.out.println("Saving to PostgreSQL..."); }
// ... other methods
}
// High-level module depends on abstraction
public class UserService {
private Database database;
// Constructor injection
public UserService(Database database) {
this.database = database;
}
public void process() {
database.connect();
database.save();
database.disconnect();
}
}// Easy to switch implementations
Database db1 = new MySqlDatabase();
UserService service1 = new UserService(db1);
service1.process();
// Just switch the implementation
Database db2 = new PostgresDatabase();
UserService service2 = new UserService(db2);
service2.process();✓ Flexibility: Easy to switch implementations
✓ Testability: Can inject mock objects for testing
✓ Loose Coupling: High-level modules independent of implementations
✓ Maintainability: Changes to one implementation don't affect others
Java_OOP/
├── README.md
│
├── CallByValueAndReference/
│ ├── src/
│ │ ├── Main.java
│ │ └── Refrence.java
│ └── CallByValueAndReference.iml
│
├── Constructor/
│ ├── src/
│ │ ├── Constructor.java
│ │ └── Main.java
│ └── Constructor.iml
│
├── Inheritance/
│ ├── src/
│ │ ├── Employee.java (Parent class)
│ │ ├── Developer.java (Child class)
│ │ └── Main.java
│ └── Inheritance.iml
│
├── Static_Members/
│ ├── src/
│ │ ├── Static.java
│ │ └── Main.java
│ └── Static_Members.iml
│
└── SOLID Principles/
├── src/
│ └── Main.java
│
├── Single Responsibility/
│ ├── src/
│ │ ├── Employee.java
│ │ ├── SalaryCalculator.java
│ │ ├── EmployeeRepository.java
│ │ └── Main.java
│ └── Single Responsibility.iml
│
├── Open_Closed/
│ ├── src/
│ │ ├── Shape.java (Abstract base)
│ │ ├── Rectangle.java
│ │ ├── Circle.java
│ │ ├── AreaCalculator.java
│ │ └── Main.java
│ └── Open_Closed.iml
│
├── Listkov Substitution/
│ ├── src/
│ │ ├── Shape.java (Abstract base)
│ │ ├── Rectangle.java
│ │ ├── Square.java
│ │ └── Main.java
│ └── Listkov Substitution.iml
│
├── Interface Segregation/
│ ├── src/
│ │ ├── Workable.java (Interface)
│ │ ├── Eatable.java (Interface)
│ │ ├── Sleepable.java (Interface)
│ │ ├── Human.java
│ │ ├── Robot.java
│ │ └── Main.java
│ └── Interface Segregation.iml
│
└── Dependency Inversion/
├── src/
│ ├── Database.java (Interface)
│ ├── MySqlDatabase.java
│ ├── PostgresDatabase.java
│ ├── UserService.java
│ └── Main.java
└── Dependency Inversion.iml
- Java Development Kit (JDK) 8 or higher
- IntelliJ IDEA or any Java IDE
cd CallByValueAndReference
javac src/*.java
java MainOutput:
5 # x unchanged (call by value)
Ali # string unchanged
null # wrapper unchanged
1 2 3 # array changed (call by reference)
2 # object changed (call by reference)
cd Constructor
javac src/*.java
java MainOutput:
age is 0
name is null
amount is 0.0
# ... (multiple constructor outputs)
cd Inheritance
javac src/*.java
java MainOutput:
I'm Parent Class
0
null
# ... (inherited properties and methods)
cd Static_Members
javac src/*.java
java MainOutput:
Done register
name is Ali
ID is 1
Faculty is Computer Science
Done register
name is Ahmed
ID is 2
Faculty is Computer Science
cd "SOLID Principles/Single Responsibility"
javac src/*.java
java MainOutput: (Varies by principle)
- Primitive types are passed by value
- Objects and arrays are passed by reference
- Strings are immutable (behave like primitives)
- Default constructor is called if you don't define one
- Parameterized constructors allow flexible initialization
- Constructor chaining (
this()) reduces code duplication - Copy constructors create independent copies
- Reduces code duplication through code reuse
- Parent class properties/methods inherited by child
- Child can override parent methods
protectedmodifier allows controlled access
- Shared by all instances of a class
- Accessed via
ClassName.staticVar - Useful for counters, constants, utility methods
- Instance variables use
this.var
| Principle | Key Takeaway |
|---|---|
| S | One class = One responsibility |
| O | Add features via extension, not modification |
| L | Child can safely replace parent |
| I | Small focused interfaces, not fat ones |
| D | Depend on abstractions, not concrete classes |
- Abstraction: Use interfaces and abstract classes
- Polymorphism: Code to the interface, not implementation
- Composition: Consider over inheritance
- Dependency Injection: Pass dependencies, don't create them
-
Use Access Modifiers Wisely
privatefor internal stateprotectedfor inheritancepublicfor public API
-
Favor Composition Over Inheritance
- Composition is more flexible
- Inheritance creates tight coupling
-
Program to Interfaces
- Depend on abstractions
- Makes code testable and flexible
-
Keep It Simple
- Single Responsibility Principle
- Avoid over-engineering
-
Test Your Code
- Unit test each class
- Mock dependencies using interfaces
Feel free to:
- Fork this repository
- Add more examples
- Improve documentation
- Create pull requests
This project is open source and available for educational purposes.
This project demonstrates practical implementations of OOP concepts and SOLID principles. Each module is self-contained and can be studied independently. The examples are designed to be clear and educational rather than production-ready.
Happy Learning!