Recap of Fundamental Concepts
Variables, Lists, Tuples, Dictionaries, and Arrays
In the previous part, we discussed how variables store data, lists and tuples group related items, dictionaries store key-value pairs, and arrays efficiently handle numerical operations. These structures are the building blocks for more complex data representations.
Modeling Objects Without OOP
We also demonstrated how to model a bank account using dictionaries and lists, showcasing the limitations of this approach as complexity increases. Now, let's transition to using OOP principles to create more structured and maintainable code.
Transition to OOP: Introduction to Classes and Objects
What is a Class?
A class is a blueprint for creating objects. It defines a set of attributes and methods that the objects created from the class can use. Think of a class as a template for creating specific instances of an object.
What is an Object?
An object is an instance of a class. It contains data (attributes) and functions (methods) defined by its class. Each object created from a class can have its own unique values for the attributes.
Example: Defining a Simple Class
class BankAccount:
def __init__(self, account_number, name, balance=0):
self.account_number = account_number
self.name = name
self.balance = balance
self.transactions = []
def deposit(self, amount):
self.balance += amount
self.transactions.append(amount)
def withdraw(self, amount):
if amount <= self.balance:
self.balance -= amount
self.transactions.append(-amount)
else:
print("Insufficient funds")
def get_balance(self):
return self.balance
def get_transactions(self):
return self.transactions
Core Principles of OOP
1. Encapsulation
Encapsulation is the concept of wrapping data (variables) and methods (functions) that operate on the data into a single unit, or class. This hides the internal state of the object and only exposes the necessary methods to interact with that object. It ensures better control over the data and prevents external interference and misuse.
Python Implementation
In Python, you can define private attributes by prefixing the attribute name with two underscores (__). Private attributes cannot be accessed directly from outside the class; they can only be accessed through methods defined within the class.
class BankAccount:
def __init__(self, account_number, name, balance=0):
self.__account_number = account_number # Private attribute
self.__balance = balance # Private attribute
self.__transactions = []
def deposit(self, amount):
self.__balance += amount
self.__transactions.append(amount)
def withdraw(self, amount):
if amount <= self.__balance:
self.__balance -= amount
self.__transactions.append(-amount)
else:
print("Insufficient funds")
def get_balance(self):
return self.__balance
def get_transactions(self):
return self.__transactions
Java Implementation
In Java, you use access modifiers like private to encapsulate data. Getter and setter methods are used to access and modify private attributes.
public class BankAccount {
private String accountNumber;
private double balance;
public BankAccount(String accountNumber, double balance) {
this.accountNumber = accountNumber;
this.balance = balance;
}
public void deposit(double amount) {
this.balance += amount;
}
public void withdraw(double amount) {
if (amount <= this.balance) {
this.balance -= amount;
} else {
System.out.println("Insufficient funds");
}
}
public double getBalance() {
return this.balance;
}
}
// Usage
public class Main {
public static void main(String[] args) {
BankAccount account = new BankAccount("001", 1000);
account.deposit(500);
System.out.println(account.getBalance()); // Output: 1500
account.withdraw(200);
System.out.println(account.getBalance()); // Output: 1300
}
}
2. Inheritance
Inheritance is a mechanism where a new class (child class) inherits properties and behaviors (methods) from an existing class (parent class). This promotes code reuse and establishes a relationship between the parent and child classes.
Python Implementation
To create a child class that inherits from a parent class, you simply pass the parent class as an argument to the child class definition.
class BankAccount:
def __init__(self, account_number, name, balance=0):
self.account_number = account_number
self.name = name
self.balance = balance
self.transactions = []
def deposit(self, amount):
self.balance += amount
self.transactions.append(amount)
def withdraw(self, amount):
if amount <= self.balance:
self.balance -= amount
self.transactions.append(-amount)
else:
print("Insufficient funds")
class SavingsAccount(BankAccount):
def calculate_interest(self):
return self.balance * 0.05
class CheckingAccount(BankAccount):
def deduct_fees(self):
self.balance -= 10
savings = SavingsAccount("001", "John Doe", 1000)
print(savings.calculate_interest()) # Output: 50.0
checking = CheckingAccount("002", "Jane Doe", 1000)
checking.deduct_fees()
print(checking.balance) # Output: 990
Java Implementation
In Java, the extends keyword is used to create a subclass that inherits from a superclass.
public class BankAccount {
protected String accountNumber;
protected double balance;
public BankAccount(String accountNumber, double balance) {
this.accountNumber = accountNumber;
this.balance = balance;
}
public void deposit(double amount) {
this.balance += amount;
}
public void withdraw(double amount) {
if (amount <= this.balance) {
this.balance -= amount;
} else {
System.out.println("Insufficient funds");
}
}
}
public class SavingsAccount extends BankAccount {
public SavingsAccount(String accountNumber, double balance) {
super(accountNumber, balance);
}
public double calculateInterest() {
return this.balance * 0.05;
}
}
// Usage
public class Main {
public static void main(String[] args) {
SavingsAccount savings = new SavingsAccount("001", 1000);
System.out.println(savings.calculateInterest()); // Output: 50.0
savings.deposit(500);
System.out.println(savings.balance); // Output: 1500
}
}
3. Abstraction
Abstraction hides the internal implementation details and exposes only the necessary functionality. It can be achieved using abstract classes and methods.
from abc import ABC, abstractmethod
class Account(ABC):
@abstractmethod
def calculate_interest(self):
pass
class SavingsAccount(Account):
def __init__(self, balance):
self.balance = balance
def calculate_interest(self):
return self.balance * 0.05
savings = SavingsAccount(1000)
print(savings.calculate_interest()) # Output: 50.0
4. Polymorphism
Polymorphism allows objects of different classes to be treated as objects of a common superclass. It provides a way to perform a single action in different forms.
Python Implementation
Polymorphism is typically implemented using method overriding, where a child class provides a specific implementation of a method that is already defined in its parent class.
class BankAccount:
def account_type(self):
pass
class SavingsAccount(BankAccount):
def account_type(self):
return "Savings Account"
class CheckingAccount(BankAccount):
def account_type(self):
return "Checking Account"
def print_account_type(account):
print(account.account_type())
savings = SavingsAccount()
checking = CheckingAccount()
print_account_type(savings) # Output: Savings Account
print_account_type(checking) # Output: Checking Account
Java Implementation
In Java, polymorphism is achieved through method overriding and the use of the @Override annotation.
public class BankAccount {
public String accountType() {
return "Generic Account";
}
}
public class SavingsAccount extends BankAccount {
@Override
public String accountType() {
return "Savings Account";
}
}
public class CheckingAccount extends BankAccount {
@Override
public String accountType() {
return "Checking Account";
}
}
public class Main {
public static void printAccountType(BankAccount account) {
System.out.println(account.accountType());
}
public static void main(String[] args) {
SavingsAccount savings = new SavingsAccount();
CheckingAccount checking = new CheckingAccount();
printAccountType(savings); // Output: Savings Account
printAccountType(checking); // Output: Checking Account
}
}
Conclusion
In this second part of our series, we focused on the basic principles of OOP: encapsulation, inheritance, abstraction, and polymorphism. We explored how these principles differ from procedural programming and the syntax rules involved in their implementation. Additionally, we compared the implementation of these principles in Python with Java to highlight the differences in syntax and approach. Understanding these principles is crucial for creating structured, maintainable, and reusable code in Python and also, if you are from a Java, c++, or PHP background, the Java code provides a fallback comparison to what you may already be familiar with.
In the next part, we will delve into more advanced OOP concepts, including composition, aggregation, association, and the Method Resolution Order (MRO). Stay tuned!
Stay tuned for more advanced topics and practical examples in the next part of our series!