LogoDart Beginner Tutorials

Advanced Pet Care Object-Oriented Programming and Class Structure

We will encapsulate our pets attributes and behaviors into a DigitalPet class demonstrating the use of methods private variables getters and constant values.

Welcome to Part 5! In the previous parts, you built a functional digital pet simulator. Now, we'll elevate your code by introducing object-oriented programming (OOP) concepts. We'll encapsulate our pet's attributes and behaviors within a DigitalPet class, making the code more organized, reusable, and maintainable.

Step 1: Creating the DigitalPet Class – Encapsulation#

Let's create a class to represent our digital pet. This class will hold the pet's attributes (like name, hunger, and happiness) and methods (like feed, play, etc.). Replace your existing feed and play functions and the content of your main function with the following code:

import 'dart:io';

class DigitalPet {
  String name;
  int _hunger; // Private variable for hunger
  int _happiness; // Private variable for happiness

  // Constant values for the maximum and minimum stat values
  static const int MAX_STAT_VALUE = 10;
  static const int MIN_STAT_VALUE = 0;


  DigitalPet(this.name)
      : _hunger = 5,
        _happiness = 5; // Constructor to initialize hunger and happiness


  int get hunger => _hunger; // Getter for hunger
  int get happiness => _happiness; // Getter for happiness


  void displayStatus() {
    print("--- ${name}'s Status ---");
    print('Hunger: $_hunger');
    print('Happiness: $_happiness');
    print('-------------------------');
  }


  Future<void> feed() async {
    print('$name is eating...');
    await Future.delayed(Duration(seconds: 2));
    _hunger = (_hunger - 2).clamp(MIN_STAT_VALUE, MAX_STAT_VALUE);
    _happiness = (_happiness + 1).clamp(MIN_STAT_VALUE, MAX_STAT_VALUE);
    print('$name finished eating! Hunger decreased, happiness increased.');
  }


  Future<void> play() async {
    print('$name is playing...');
    await Future.delayed(Duration(seconds: 3));
    _happiness = (_happiness + 3).clamp(MIN_STAT_VALUE, MAX_STAT_VALUE);
    _hunger = (_hunger + 1).clamp(MIN_STAT_VALUE, MAX_STAT_VALUE);
    print('$name finished playing! Happiness increased, hunger increased.');
  }
}


void main() async {
  stdout.write('What would you like to name your pet? ');
  String? petName = stdin.readLineSync()?.trim();
  petName ??= 'Buddy';
  print('Hello, $petName!');

  DigitalPet myPet = DigitalPet(petName);
  await myPet.feed();
  await myPet.play();
  myPet.displayStatus();
}

Notice how the hunger and happiness variables are now private (_hunger, _happiness), and we use getters (hunger, happiness) to access them. This is a fundamental OOP principle of data encapsulation – protecting the internal state of the object. The clamp method ensures that the hunger and happiness values stay within the 0-10 range. The static const values for minimum and maximum values are used for better code readability and maintainability. The constructor (DigitalPet(this.name)) simplifies object creation.

Step 2: Adding More Methods – Expanding Functionality#

Let's add more methods to our DigitalPet class to make it more complete. Add these methods inside the DigitalPet class:

  Future<void> timePasses() async {
    print('Time passes...');
    await Future.delayed(Duration(seconds: 1));
    _hunger = (_hunger + 1).clamp(MIN_STAT_VALUE, MAX_STAT_VALUE);
    _happiness = (_happiness - 1).clamp(MIN_STAT_VALUE, MAX_STAT_VALUE);
    if (_hunger >= MAX_STAT_VALUE) {
      print('$name is very hungry!');
    }
    if (_happiness <= MIN_STAT_VALUE) {
      print('$name is very sad!');
    }
  }

  bool needsAttention() {
    return _hunger >= MAX_STAT_VALUE || _happiness <= MIN_STAT_VALUE;
  }

The timePasses method simulates the passage of time, increasing hunger and decreasing happiness. The needsAttention method checks if the pet needs immediate care. This demonstrates method functionality within a class structure.

Step 3: Updating the main Function – Using the Class#

Now, modify your main function to use the DigitalPet class and its methods. This refactors the code, making it more structured and readable. Replace your main function with this improved version:

void main() async {
  stdout.write('What would you like to name your pet? ');
  String? petName = stdin.readLineSync()?.trim();
  petName ??= 'Buddy';
  print('Hello, $petName!');

  DigitalPet myPet = DigitalPet(petName);
  await myPet.feed();
  await myPet.play();
  await myPet.timePasses();
  myPet.displayStatus();
  if (myPet.needsAttention()) {
    print('Warning: ${myPet.name} really needs your attention!');
  }
}

This streamlined version utilizes the methods of the DigitalPet class, showcasing better code organization and readability.

Congratulations! You’ve successfully implemented object-oriented programming principles, creating a well-structured and reusable DigitalPet class.

What's Next?#

In the final part, "The Complete Digital Pet Simulator: Bringing It All Together," we'll integrate all the features we've developed into a complete, interactive pet simulator with a menu-driven interface. Get ready for the grand finale!