DEV Community

SameX
SameX

Posted on

HarmonyOS Next Property: Advanced Tips for Encapsulating Data Access

In HarmonyOS Next development, Property is the core mechanism for implementing data encapsulation and behavior abstraction.Through the properties of getter and setter, developers can flexibly control the reading and modification of data without exposing internal implementations.This article combines the "Cangjie Programming Language Development Guide" to analyze advanced application scenarios and best practices for attributes.

1. The essence of attributes: the abstract layer of data access

The attribute is declared through the prop keyword, decoupling the data access logic from the storage.Unlike member variables, attributes do not directly store values, but control read and write through custom logic.

1. Basic syntax and read and write control

class TemperatureSensor {
    private var _temp: Float64 = 25.0
    private let minTemp: Float64 = -20.0
    private let maxTemp: Float64 = 80.0

// Read-only attribute: exposed temperature value, restricted modification
    public prop temperature: Float64 {
        get() { _temp }
    }

// Read and write properties: Verify the temperature range
    public mut prop targetTemperature: Float64 {
        get() { _temp }
        set(value) {
            if value >= minTemp && value <= maxTemp {
                _temp = value
            } else {
throw Error("Temperature out of range:\(value)")
            }
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

2. Implicit Closure Property

For simple logic, the get keyword can be omitted and the expression can be directly returned:

class Circle {
    private let radius: Float64
public prop area: Float64 = 3.14 * radius * radius // Implicit getter
    public init(radius: Float64) { self.radius = radius }
}
Enter fullscreen mode Exit fullscreen mode

2. Advanced features and design patterns of attributes

1. Static properties: type-level data abstraction

Static properties belong to the class itself, not instances, and are modified by static:

class AppConfig {
    public static prop appVersion: String {
        get() { "1.2.3" }
    }
    public static mut prop debugMode: Bool {
        get() { false }
set { /* Write logic */ }
    }
}

// Use example
println(AppConfig.appVersion) // Output: 1.2.3
AppConfig.debugMode = true
Enter fullscreen mode Exit fullscreen mode

2. Property Observer

Listen to the change of property values ​​through the didSet and willSet hook functions:

class User {
    public mut prop email: String {
        didSet {
if email != oldValue { // oldValue is the old value
sendNotification("The email has been changed to:\(email)")
            }
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

3. Abstract properties in interfaces

The interface can declare abstract properties and force the implementation class to provide read and write logic:

interface Observable {
mut prop data: String // Abstract read and write properties
prop version: Int // Abstract read-only attributes
}

class DataModel <: Observable {
    private var _data: String = ""
    private var _version: Int = 0

    public mut prop data: String {
        get() { _data }
        set { _data = value; _version += 1 }
    }

    public prop version: Int { get() { _version } }
}
Enter fullscreen mode Exit fullscreen mode

3. Coordinated application of attributes and other characteristics

1. Attributes and Access Modifiers

Attributes can control visibility through access modifiers (public/private, etc.):

class SecureStorage {
    private mut prop encryptionKey: String {
        get() { loadKey() }
        set { saveKey(newValue) }
    }
    public func getDecryptedData() -> String {
// Use private attributes internally
        let key = encryptionKey
// Decryption logic
    }
}
Enter fullscreen mode Exit fullscreen mode

2. Attribute overlay and polymorphism

Subclasses can override the parent class attributes, and need to keep the type consistent and use override to modify:

open class Base {
    public open mut prop value: Int = 0
}
class Derived <: Base {
    public override mut prop value: Int {
get() { super.value * 2 } // Zoom twice when reading
set { super.value = newValue / 2 } // Shrink twice when writing
    }
}
Enter fullscreen mode Exit fullscreen mode

3. Attribute and type conversion

Attributes can play a role in type conversion, such as dynamic access to interface properties:

interface Measurable {
    prop value: Float64
}
class Thermometer <: Measurable {
    public prop value: Float64 = 25.0
}

let device: Any = Thermometer()
if let measurable = device as? Measurable {
println("Measured value: \(measurable.value)") // Dynamic access to properties
}
Enter fullscreen mode Exit fullscreen mode

4. Practical scenario: Dynamic management of equipment parameters

Scenario: Design the parameter configuration module of the intelligent device, requiring automatic verification of parameter values, historical record tracking and network synchronization.

1. Parameter base class: define attribute interface

abstract class DeviceParam<T> {
public abstract mut prop value: T // Abstract read and write properties
    public prop history: [T] = []
    protected func logChange(oldValue: T, newValue: T) {
        history.append(newValue)
syncToCloud(oldValue, newValue) // Abstract function, subclass implementation
    }
    protected abstract func syncToCloud(oldValue: T, newValue: T)
}
Enter fullscreen mode Exit fullscreen mode

2. Specific parameters implementation: Temperature parameters

class TemperatureParam <: DeviceParam<Float64> {
    private var _value: Float64 = 25.0
    public override mut prop value: Float64 {
        get() { _value }
        set {
if newValue >= -40.0 && newValue <= 125.0 { // Temperature range verification
                let oldValue = _value
                _value = newValue
                logChange(oldValue: oldValue, newValue: newValue)
            } else {
throw Error("Temperature parameter is invalid: \(newValue)")
            }
        }
    }

    protected override func syncToCloud(oldValue: Float64, newValue: Float64) {
// Implement network synchronization logic
println("Synchronous temperature change: \(oldValue) → \(newValue)")
    }
}
Enter fullscreen mode Exit fullscreen mode

3. Parameter management and polymorphic operations

let tempParam = TemperatureParam()
tempParam.value = 28.5 // Trigger verification, logging and synchronization
println("History: \(tempParam.history)") // Output: [28.5]

func updateParam(param: DeviceParam<Float64>, newValue: Float64) {
param.value = newValue // Polymorphic call, automatically adapts to specific parameter logic
}
Enter fullscreen mode Exit fullscreen mode

5. Common traps and optimization strategies

1. Avoid recursive calls of properties

Avoid calling itself directly or indirectly in getter/setter to prevent dead loops:

class Counter {
    private var _count: Int = 0
    public mut prop count: Int {
get() { count + 1 } // Recursively call getter, causing stack overflow
        set { _count = newValue }
    }
}
Enter fullscreen mode Exit fullscreen mode

2. Prefer attributes over public variables

Public variables destroy encapsulation, and properties can flexibly add verification logic:

// Counterexample: Directly expose public variables
class BadDesign {
public var volume: Int = 0 // Unlimited modification
}

// Formal example: limit the volume range through attributes
class GoodDesign {
    public mut prop volume: Int {
        set { volume = max(0, min(100, newValue)) }
    }
}
Enter fullscreen mode Exit fullscreen mode

3. Thread safety of static properties

In a multi-threaded environment, thread safety needs to be ensured for static attribute access, which can be achieved through mutex locks:

class ThreadSafeConfig {
    private static var _instance: ThreadSafeConfig?
    private static var lock = Mutex()
    public static prop instance: ThreadSafeConfig {
        get() {
            lock.lock()
            defer { lock.unlock() }
            return _instance ?? createNewInstance()
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

6. Summary: The design philosophy of attributes

The attribute mechanism of HarmonyOS Next reflects the design idea of ​​"data is interface":

  • Encapsulation: Hide internal storage details and provide a unified access portal through getter/setter;
  • Flexibility: Supports complex logic such as verification, logging, synchronization, etc., to adapt to changes in business rules;
  • Polymorphism: Combining interface and inheritance, realizing dynamic distribution of attribute behavior.

Top comments (0)

Some comments may only be visible to logged-in visitors. Sign in to view all comments.