"What is Object-Oriented Programming, and How Does Encapsulation Work?"

Programação orientada a objetos, também conhecida como OOP, é um estilo de programação no qual os desenvolvedores tratam tudo no seu código como um objeto do mundo real. Uma classe é como um modelo para criar objetos. Cada objeto criado a partir de uma classe tem atributos que definem dados e métodos que definem os comportamentos dos objetos. Em uma lição anterior, você aprendeu como criar classes. Aqui está um lembrete da sintaxe:
class ClassName:
   def __init__(self, parameters):
       attribute = value

   def method_name(self):
       # method logic
Aqui está um exemplo de uma classe que usa o método especial __init__ para inicializar os atributos brand e color sempre que um objeto é criado usando a classe:
class Car:
   def __init__(self, brand, color):
       self.brand = brand
       self.color = color

# create two objects from the Car class
car1 = Car('Toyota', 'red')
car2 = Car('Lambo', 'green')

print('Car 1 Brand:', car1.brand) # Car 1 Brand: Toyota
print('Car 1 Color:', car1.color) # Car 1 Color: red

print('Car 2 Brand:', car2.brand) # Car 2 Brand: Lambo
print('Car 2 Color:', car2.color) # Car 2 Color: green
A programação orientada a objetos tem quatro princípios-chave que ajudam você a organizar e gerenciar o código de forma eficaz. Eles são encapsulamento, herança, polimorfismo e abstração. O restante desta lição vai se concentrar em como a encapsulação funciona. Encapsulamento é o agrupamento dos atributos e métodos de um objeto em uma única unidade, a classe. Com encapsulamento, você pode ocultar o estado interno do objeto atrás de um conjunto simples de métodos e atributos públicos que funcionam como portas. Atrás dessas portas estão atributos e métodos privados que controlam como os dados mudam e quem pode vê-los. Vamos supor que você queira rastrear o saldo de uma carteira. Você quer permitir que as pessoas depositem ou retirem dinheiro da carteira, mas ninguém deve ser capaz de alterar o saldo diretamente. Nesse caso, você pode tornar os métodos deposit() e withdraw() públicos e esconder o saldo no atributo _balance:
class Wallet:
   def __init__(self, balance):
       self._balance = balance # For internal use by convention

   def deposit(self, amount):
       if amount > 0:
           self._balance += amount # Add to the balance safely

   def withdraw(self, amount):
       if 0 < amount <= self._balance:
           self._balance -= amount # Remove from the balance safely
Por convenção, prefixar atributos e métodos com um único underscore significa que eles são destinados ao uso interno. Ninguém deve acessá-los diretamente de fora da classe, pois isso vai contra os princípios de encapsulamento e pode levar a bugs. Enquanto um prefixo de um único underscore é apenas uma convenção, prefixar atributos e métodos com um duplo underscore efetivamente impede que eles sejam acessados de fora da sua classe, tornando esses atributos e métodos privados.
class Wallet:
   def __init__(self, balance):
       self.__balance = balance # Private attribute

   def deposit(self, amount):
       if amount > 0:
           self.__balance += amount # Add to the balance safely

   def withdraw(self, amount):
       if 0 < amount <= self.__balance:
           self.__balance -= amount # Remove from the balance safely

account = Wallet(500)
print(account.__balance) # AttributeError: 'Wallet' object has no attribute '__balance'
Para obter o valor atual de __balance, você pode definir um método get_balance. Por exemplo:
class Wallet:
   def __init__(self, balance):
       self.__balance = balance

   def deposit(self, amount):
       if amount > 0:
           self.__balance += amount

   def withdraw(self, amount):
       if 0 < amount <= self.__balance:
           self.__balance -= amount
  
   def get_balance(self):
       return self.__balance


acct_one = Wallet(100)
acct_one.deposit(50)
print(acct_one.get_balance()) # 150

acct_two = Wallet(450)
acct_two.withdraw(28)
print(acct_two.get_balance()) # 422

acct_two.deposit(150)
print(acct_two.get_balance()) # 572
Você também pode definir um método privado __validate para verificar se todo valor de depósito ou retirada é um número positivo:
class Wallet:
   def __init__(self):
       self.__balance = 0

   def __validate(self, amount):
       if amount < 0:
           raise ValueError('Amount must be positive')

   def deposit(self, amount):
       self.__validate(amount)
       self.__balance += amount

   def withdraw(self, amount):
       self.__validate(amount)
       if amount > self.__balance:
           raise ValueError('Insufficient funds')
       self.__balance -= amount

   def get_balance(self):
       return self.__balance

acct_one = Wallet()
acct_one.deposit(3)
print(acct_one.get_balance()) # 3

acct_one.deposit(50)
print(acct_one.get_balance()) # 53

acct_one.deposit(-4)  # ValueError: Amount must be positive
acct_one.withdraw(-8) # ValueError: Amount must be positive
acct_one.withdraw(58) # ValueError: Insufficient funds
Como você pode ver, o método __validate é privado e é executado nos bastidores nos métodos públicos deposit() e withdraw() para garantir que o valor seja sempre válido. Em uma próxima lição, você aprenderá mais sobre como atributos prefixados com um duplo underscore funcionam. Em resumo, encapsulamento protege os dados internos por trás de métodos públicos claros. É assim que você mantém suas classes seguras contra alterações indevidas e centraliza a validação em um único lugar. Você pode atualizar ou estender seu código livremente, sabendo que o código externo só acessa as interfaces que você expõe.
Este módulo não possui perguntas. Marque como concluído.