Encapsulation is one of the fundamental principles of Object-Oriented Programming (OOP). It refers to the bundling of data (attributes) and methods (functions) that operate on the data into a single unit, typically a class. Encapsulation also involves restricting direct access to some of an object's components, which is a means of preventing unintended interference and misuse of the data.

Key Concepts of Encapsulation

  1. Data Hiding: Encapsulation allows the internal state of an object to be hidden from the outside. This is typically achieved using access modifiers.
  2. Access Modifiers: These are keywords used to set the accessibility of classes, methods, and other members. Common access modifiers in Groovy include:
    • private: The member is accessible only within the class.
    • protected: The member is accessible within the class and by subclasses.
    • public: The member is accessible from any other class.
  3. Getters and Setters: Methods that provide controlled access to the attributes of a class. Getters retrieve the value of an attribute, while setters modify the value.

Practical Example

Let's create a simple class Person to demonstrate encapsulation in Groovy.

class Person {
    private String name
    private int age

    // Getter for name
    String getName() {
        return name
    }

    // Setter for name
    void setName(String name) {
        this.name = name
    }

    // Getter for age
    int getAge() {
        return age
    }

    // Setter for age
    void setAge(int age) {
        if (age > 0) {
            this.age = age
        } else {
            println "Age must be positive."
        }
    }
}

// Using the Person class
def person = new Person()
person.setName("John Doe")
person.setAge(30)

println "Name: ${person.getName()}"
println "Age: ${person.getAge()}"

Explanation

  • Private Attributes: The name and age attributes are declared as private, meaning they cannot be accessed directly from outside the class.
  • Getters and Setters: The getName, setName, getAge, and setAge methods provide controlled access to the name and age attributes.
  • Validation: The setAge method includes a validation check to ensure the age is positive.

Practical Exercises

Exercise 1: Create a BankAccount Class

Create a BankAccount class with the following attributes and methods:

  • private double balance
  • public double getBalance()
  • public void deposit(double amount)
  • public void withdraw(double amount)

Ensure that the withdraw method does not allow the balance to go negative.

Solution

class BankAccount {
    private double balance = 0.0

    double getBalance() {
        return balance
    }

    void deposit(double amount) {
        if (amount > 0) {
            balance += amount
        } else {
            println "Deposit amount must be positive."
        }
    }

    void withdraw(double amount) {
        if (amount > 0 && amount <= balance) {
            balance -= amount
        } else {
            println "Invalid withdraw amount."
        }
    }
}

// Using the BankAccount class
def account = new BankAccount()
account.deposit(100.0)
account.withdraw(30.0)

println "Balance: ${account.getBalance()}"

Exercise 2: Create a Student Class

Create a Student class with the following attributes and methods:

  • private String studentId
  • private String name
  • private double gpa
  • public String getStudentId()
  • public void setStudentId(String studentId)
  • public String getName()
  • public void setName(String name)
  • public double getGpa()
  • public void setGpa(double gpa)

Ensure that the setGpa method only accepts values between 0.0 and 4.0.

Solution

class Student {
    private String studentId
    private String name
    private double gpa

    String getStudentId() {
        return studentId
    }

    void setStudentId(String studentId) {
        this.studentId = studentId
    }

    String getName() {
        return name
    }

    void setName(String name) {
        this.name = name
    }

    double getGpa() {
        return gpa
    }

    void setGpa(double gpa) {
        if (gpa >= 0.0 && gpa <= 4.0) {
            this.gpa = gpa
        } else {
            println "GPA must be between 0.0 and 4.0."
        }
    }
}

// Using the Student class
def student = new Student()
student.setStudentId("S12345")
student.setName("Alice")
student.setGpa(3.8)

println "Student ID: ${student.getStudentId()}"
println "Name: ${student.getName()}"
println "GPA: ${student.getGpa()}"

Common Mistakes and Tips

  • Direct Access: Avoid accessing private attributes directly from outside the class. Always use getters and setters.
  • Validation: Always include validation in setters to ensure the integrity of the data.
  • Encapsulation Overhead: While encapsulation adds a layer of protection, it can also introduce some overhead. Use it judiciously to balance between protection and performance.

Conclusion

Encapsulation is a powerful concept in OOP that helps in protecting the internal state of an object and provides controlled access to its attributes. By using access modifiers and getter/setter methods, you can ensure that your classes are robust and maintainable. In the next topic, we will explore more advanced OOP concepts such as inheritance.

© Copyright 2024. All rights reserved