Files
flutter_projects/dart_ex_1/ex_2.dart
2026-03-27 18:34:00 +07:00

161 lines
5.4 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// A Mixin: Adds specific behavior without being a parent class
mixin Connectable {
bool isConnected = false;
void toggleConnection() => isConnected = !isConnected;
}
abstract class SmartDevice {
final String id; // Final: Set once
String _name; // Private field (starts with _)
bool _powerStatus = false;
static const String brand = "GeminiHome"; // Static: Shared by all instances
// 1. Generative Constructor
SmartDevice(this.id, this._name);
// 2. Named Constructor
SmartDevice.temp(this.id) : _name = "Unknown Device";
// 3. Factory Constructor: Decides which subclass to return
factory SmartDevice.create(String type, String id, String name) {
if (type == 'light') return SmartLight(id, name);
return SmartSpeaker(id, name);
}
// Getter and Setter
String get name => _name;
set name(String value) => _name = value.trim();
// Abstract method: Must be implemented by children
void performAction();
// Regular method
void togglePower() {
_powerStatus = !_powerStatus;
print('$_name is now ${_powerStatus ? "ON" : "OFF"}');
}
}
class SmartLight extends SmartDevice {
int brightness = 100;
// Uses 'super' to pass data to the parent constructor
SmartLight(super.id, super.name);
@override
void performAction() {
print('Adjusting brightness to $brightness%');
}
}
// 'with Connectable' adds the toggleConnection() method to this class
class SmartSpeaker extends SmartDevice with Connectable {
double volume;
// 4. Initializer List: Sets volume before the constructor body runs
SmartSpeaker(String id, String name, {this.volume = 0.5})
: assert(volume >= 0 && volume <= 1.0),
super(id, name);
// 5. Redirecting Constructor: Forwards to the main constructor
SmartSpeaker.loud(String id, String name) : this(id, name, volume: 1.0);
@override
void performAction() {
print('Playing music at ${volume * 100}% volume. WiFi: $isConnected');
}
}
void main() {
// Using the Factory
final myLight = SmartDevice.create('light', 'L1', 'Living Room Lamp');
// Using the Redirecting Constructor
final partySpeaker = SmartSpeaker.loud('S1', 'Bass Blaster');
myLight.togglePower(); // Parent method
partySpeaker.toggleConnection(); // Mixin method
partySpeaker.performAction(); // Overridden method
}
// You're absolutely right. Understanding *why* we use these features is more important than just knowing the syntax. Lets break down the rationale and real-world use cases for the structure I built.
// ---
// ## 1. The Abstract Class (`SmartDevice`)
// **Rationale:** You want to define a "contract." Every smart device must have an ID, a name, and an action, but a "generic" smart device doesn't exist in reality—its always a specific light, speaker, or thermostat.
// * **Use Case:** In a Large App, you might have a `List<SmartDevice> devices`. You can loop through this list and call `.togglePower()` on every item without knowing if it's a light or a speaker. This is the heart of **Polymorphism**.
// ---
// ## 2. Factory Constructors (`SmartDevice.create`)
// **Rationale:** Normal constructors always create a new instance of the exact class they belong to. A `factory` constructor is more flexible: it can return a subclass or even a cached instance.
// * **Use Case:** **API Parsing.** When you get JSON data from a server, you don't know the device type until you read the data. A factory constructor can look at the "type" field in the JSON and decide to return a `SmartLight` object or a `SmartSpeaker` object automatically.
// ---
// ## 3. Mixins (`with Connectable`)
// **Rationale:** Dart doesn't allow a class to have two parents (Multiple Inheritance) because it gets messy. Mixins allow you to "plug in" shared behavior across unrelated classes.
// * **Use Case:** Imagine you have a `SmartSpeaker` (a device) and a `PhoneApp` (not a device). Both need "WiFi Connection" logic. Instead of copying the code twice, you create a `Connectable` mixin and apply it to both.
// ---
// ## 4. Getters and Setters
// **Rationale:** This is about **Encapsulation**. You should never let outside code touch your internal variables (`_name`) directly. By using a setter, you can "sanitize" the data.
// * **Use Case:** Validation. If a user tries to set a device name to a string full of empty spaces, your setter `set name(value) => _name = value.trim();` automatically cleans it up before its saved to your database.
// ---
// ## 5. Redirecting Constructors (`SmartSpeaker.loud`)
// **Rationale:** To avoid "Boilerplate" (repetitive code). If you have a common configuration for a class, you don't want to rewrite the initialization logic.
// * **Use Case:** **Presets.** In a UI, you might have a "Default" button and a "Pro" button. Instead of manually setting 10 different parameters each time, you create a named constructor like `Button.primary()` that redirects to the main constructor with all the "Pro" settings pre-filled.
// ---
// ## 6. Static Members (`static const brand`)
// **Rationale:** Memory efficiency. If you have 1,000 smart lights, you don't need the string "GeminiHome" stored in memory 1,000 times.
// * **Use Case:** **Configuration & Constants.** Use `static` for things that are "Universal truth" for the class, such as a maximum volume limit or a shared API version number.
// Would you like me to show you how to handle **Interfaces** in Dart, which is the final piece of the OO puzzle?