Practical_OOP
Practical_OOP
Objects are instances of classes, which can represent real-world entities with
certain characteristics (attributes) and behaviors (methods).
OOP is popular because it allows for code that is modular, reusable, and easier
to understand and maintain.
It’s built on four main principles: Encapsulation, Abstraction, Inheritance, and
Polymorphism.
1. Encapsulation in Java
Explanation:
2. Abstraction
Concept Explained:
Abstraction involves hiding the complex implementation details and showing only
the essential features of the object. It allows the user to interact with the object at
a high level without needing to understand the internal workings. This can be
achieved using abstract classes and interfaces in Java.
// Common method
public void displayType() {
System.out.println("This is a vehicle.");
}
}
@Override
void stop() {
System.out.println("Car engine stopping...");
}
}
@Override
void stop() {
System.out.println("Truck engine stopping...");
}
}
myCar.displayType();
myCar.start();
myCar.stop();
myTruck.displayType();
myTruck.start();
myTruck.stop();
}
}
Explanation:
3. Inheritance
Concept Explained:
Real-World Application:
Application1: Employee Management System
class Employee {
protected String name; // Protected allows subclasses to access
protected int id;
@Override
public void displayInfo() {
super.displayInfo(); // Call to the superclass method
System.out.println("Salary: " + salary);
}
}
@Override
public void displayInfo() {
super.displayInfo(); // Call to the superclass method
System.out.println("Hourly Rate: " + hourlyRate);
}
}
fullTimeEmp.displayInfo();
partTimeEmp.displayInfo();
}
}
Explanation:
● Superclass: The Employee class contains common attributes like name and id,
and a method to display employee information.
● Subclasses: FullTimeEmployee and PartTimeEmployee inherit from
Employee, allowing them to use its properties and methods while adding their
specific attributes (salary and hourlyRate).
● Code Reusability: This structure allows for easy expansion of the employee
types without rewriting common functionalities, promoting efficient code reuse.
Application2:
class Product {
double price;
String description;
@Override
public void displayInfo() {
super.displayInfo();
System.out.println("Brand: " + brand + ", Warranty: " + warranty + " years");
}
}
@Override
public void displayInfo() {
super.displayInfo();
System.out.println("Size: " + size + ", Material: " + material);
}
}
4. Polymorphism
Concept Explained:
Real-World Application:
interface Notification {
void send(String message); // Method signature
}
// Array of notifications
Notification[] notifications = {email, sms, push};
Explanation:
1. Book Class
2. LibraryMember Class
3. Library Class
Using these classes, we can create multiple objects like individual books and
members with specific values. Below is a Java code example for the Book and
LibraryMember classes, showing how they work together.
The Book class will represent a single book in the library. It has attributes like
title, author, and ISBN, and behaviors like borrowing and returning the book.
class Book {
// Attributes
// Constructor
this.title = title;
this.author = author;
this.ISBN = ISBN;
if (isAvailable) {
isAvailable = false;
return true;
} else {
return false;
}
isAvailable = true;
System.out.println("Title: " + title + ", Author: " + author + ", ISBN: " + ISBN +
", Available: " + isAvailable);
The LibraryMember class represents a library member who can borrow books.
Each member has a name, memberID, and a list of borrowed books.
import java.util.ArrayList;
import java.util.List;
class LibraryMember {
// Attributes
// Constructor
this.name = name;
this.memberID = memberID;
if (book.borrow()) {
borrowedBooks.add(book);
if (borrowedBooks.contains(book)) {
book.returnBook();
borrowedBooks.remove(book);
}
}
book.displayInfo();
Now, let’s create some objects for Book and LibraryMember and interact with
them to see how classes and objects work together.
book1.displayInfo();
book2.displayInfo();
// Create a LibraryMember object
member.viewBorrowedBooks();
member.viewBorrowedBooks();
1. Class Definitions:
○ We defined two classes: Book and LibraryMember.
○ Each class has specific attributes and methods related to the type of
entity it represents. Book manages book information and availability,
while LibraryMember manages borrowed books for each member.
2. Creating Objects:
○ In Main, we created specific book objects (book1 and book2) and a
library member object (member).
○ Each Book object represents a unique book with specific details like
title, author, and ISBN.
○ The LibraryMember object (member) represents a library member
named "Alice" with a unique ID "M001".
3. Using Objects:
○ Alice (the LibraryMember object) borrows book1 and book2.
The borrowBook method checks if the book is available and, if so,
marks it as borrowed.
○ We display the list of books Alice has borrowed.
○ When Alice returns book1, the returnBook method updates the
book’s availability and removes it from her borrowed list.
4. Encapsulation in Action:
○ The Book class’s borrow and returnBook methods control how
each book's availability is updated, ensuring consistency and
avoiding direct manipulation of the isAvailable attribute.
○ Similarly, LibraryMember manages the list of borrowed books,
encapsulating the borrowing and returning behavior.
Real-Life Context
In a real library system, each Book object would represent a physical or digital
book available to members. Each LibraryMember object would represent a
registered library member. Together, classes and objects help simulate and
manage real-world interactions, such as borrowing and returning books, by
organizing data and functions logically and efficiently.
Real-Life Application of OOP: Library Management System in Java
Let’s create a simplified Library Management System in Java using the OOP
principles.
this.title = title;
this.author = author;
this.isAvailable = true;
if (isAvailable) {
isAvailable = false;
} else {
System.out.println(title + " is not
available.");
isAvailable = true;
return isAvailable;
return title;
2) Abstraction: A Library class with methods for adding and finding books
hides the internal storage of the book list.
import java.util.ArrayList;
public class Library {
books.add(book);
if (book.getTitle().equalsIgnoreCase(title)) {
return book;
return null;
this.name = name;
this.borrowLimit = borrowLimit;
super(name, 3);
super(name, 5);
library.addBook(book1);
library.addBook(book2);
foundBook.checkout();
Here's a simple example: In any reasonable language, you can write something equivalent to
print("Hello world") and it will print "Hello world" to your terminal -- but do you know how print
(or its equivalent) actually works? I would bet that the overwhelming majority of people who've ever
written code probably don't.
That's because print is an abstraction. The actual steps between invoking a hello world program
and seeing "hello world" pop up on your screen go deep into the internals of the operating system
and CPU, but you don't have to know that -- you just have to know that you can invoke print and it'll
work. (And, in fact, print is an abstraction on top of abstractions. The programming language
maintainers who implemented print don't have to know all the OS internals, they just have to know
what interfaces the OS exposes. And the OS maintainers don't have to know the details of the CPU's
inner circuitry, they just have to know what instruction set the CPU exposes.)
Where abstraction and OOP come together is in the form of interfaces and composition. In OOP, you
can have multiple different classes implement the same interface but work in different ways. For
example, say you needed to write a program that sends messages to people in a variety of ways
(e.g. Discord, Slack, email, and SMS). With OOP, you could define a MessagingClient interface with
a sendMessage method and have classes that implement that interface and method. Each class's
implementation of sendMessage would be different, but code that uses it wouldn't have to worry
about which implementation it's using. You could write some code like this and just rely on each
individual client object's class to handle the specifics:
for (MessagingClient client : messagingClients) {
client.sendMessage(message);
The key here is that each client in messagingClients is pre-populated with all the information it
needs to send a message. It might have credentials for Discord and information about which server
and channel to post a message in. It might have an email address and information about an SMTP
server to use. It might have something completely different. Whatever it has, it doesn't matter to this
code that's trying to send the message. You don't have to alter that loop to support a new
MessagingClient implementation. Likewise, you can use MessagingClient anywhere and those
places don't have to know what implementation they're using or where it comes from. If you wanted
to support sending messages via Reddit DM or fax or something, you'd just have to implement a new
MessagingClient and then wire it up so it gets passed in the right places in your code. (Okay, "just"
is doing a lot of work there. I think actually learning how to do this is one of the harder parts of
figuring out OOP.)
For example, you could have messagingClients be pulled from a config file that can be updated to
include arbitrary messaging destinations. You could write code that has a registry of all your
MessagingClient implementations and then use that registry to read entries from the config file
and instantiate instances of each appropriate implementation. Then you can just pass that array of
MessagingClient instances to the function with that for loop. The loop and its containing function
don't care where those messagingClients came from -- it just cares that they implemented
sendMessage. Likewise, whoever updates the config file doesn't have to worry about how the
messages get sent, they just have to worry about providing the right details to configure each service
properly. If you do it right, this enables you to completely decouple the code that's responsible for
building the messagingClients collection and the code that actually invoke each client to send each
message.