1. $METHOD y $CLASSMETHOD para despacho dinámico
Puntos Clave
- $CLASSMETHOD(class, method, args...): Invoca un método de clase por nombre en tiempo de ejecución
- $METHOD(oref, method, args...): Invoca un método de instancia en una referencia de objeto en tiempo de ejecución
- Despacho dinámico: Los nombres de método y clase pueden ser variables, permitiendo flexibilidad en tiempo de ejecución
- Casos de uso: Arquitecturas de plugins, patrones de fábrica, comportamiento dirigido por configuración
- Valor de retorno: Ambas funciones devuelven el valor de retorno del método
- Manejo de errores: Envuelva en TRY-CATCH para manejar clases/métodos inexistentes
Notas Detalladas
Descripción general
$CLASSMETHOD y $METHOD le permiten invocar métodos donde el nombre de la clase, el nombre del método, o ambos se determinan en tiempo de ejecución (almacenados en variables). Este es el mecanismo de ObjectScript para despacho dinámico, análogo a la invocación de métodos basada en reflexión en Java o getattr() de Python.
$CLASSMETHOD: Llamadas dinámicas a métodos de clase
// Static call (compile-time)
SET obj = ##class(User.Person).%New()
// Dynamic equivalent using $CLASSMETHOD
SET className = "User.Person"
SET obj = $CLASSMETHOD(className, "%New")
// Dynamic method name too
SET methodName = "%New"
SET obj = $CLASSMETHOD(className, methodName)
// With arguments
SET className = "User.Calculator"
SET result = $CLASSMETHOD(className, "Add", 5, 3)
WRITE result, ! // 8
// Factory pattern: create objects based on configuration
SET type = ^config("objectType") // e.g., "User.Admin" or "User.Guest"
SET obj = $CLASSMETHOD(type, "%New")
$METHOD: Llamadas dinámicas a métodos de instancia
SET person = ##class(User.Person).%New()
SET person.Name = "Alice"
// Static call
WRITE person.GetFullName(), !
// Dynamic equivalent
SET methodName = "GetFullName"
WRITE $METHOD(person, methodName), !
// With arguments
SET methodName = "SetAge"
DO $METHOD(person, methodName, 30)
// Dynamic method name from data
SET action = "Validate"
SET status = $METHOD(obj, action)
Patrones prácticos de despacho dinámico
// Plugin/handler pattern
SET handlerClass = ^config("handler")
SET status = $CLASSMETHOD(handlerClass, "Process", message)
// Iterate over a list of operations
SET operations = $LB("Validate", "Transform", "Save")
SET ptr = 0
WHILE $LISTNEXT(operations, ptr, op) {
SET status = $METHOD(processor, op, data)
QUIT:$$$ISERR(status)
}
// Error handling for dynamic dispatch
TRY {
SET result = $CLASSMETHOD(className, methodName, arg1)
} CATCH ex {
WRITE "Error: ", ex.DisplayString(), !
// Handle missing class or method
}
$CLASSMETHOD vs sintaxis ##class()
// Static (compile-time resolution):
SET obj = ##class(User.Person).%New()
// Compiler verifies class exists at compile time
// Dynamic (runtime resolution):
SET obj = $CLASSMETHOD("User.Person", "%New")
// Class resolved at runtime -- more flexible but no compile-time check
Referencias de Documentación
2. %IsA y %ClassName para verificación de tipos
Puntos Clave
- obj.%IsA("Package.Class"): Devuelve 1 si el objeto es una instancia de la clase o cualquier subclase
- Consciente de herencia: %IsA devuelve verdadero para cualquier clase ancestro en la jerarquía
- Verificación de interfaces: %IsA funciona con clases que implementan una interfaz dada
- obj.%ClassName(0): Devuelve el nombre corto de la clase (sin paquete)
- obj.%ClassName(1): Devuelve el nombre completo de la clase (con paquete)
- %PackageName: Devuelve el nombre del paquete
Notas Detalladas
Descripción general
%IsA y %ClassName son métodos heredados de %Library.RegisteredObject (la clase raíz para todos los objetos registrados). Permiten la inspección de tipos en tiempo de ejecución, lo cual es útil para código polimórfico, validación y depuración.
%IsA: Verificación de la jerarquía de clases
// Given: User.Employee extends User.Person
SET emp = ##class(User.Employee).%New()
// Check exact class
WRITE emp.%IsA("User.Employee"), ! // 1
// Check parent class
WRITE emp.%IsA("User.Person"), ! // 1
// Check root class
WRITE emp.%IsA("%Library.RegisteredObject"), ! // 1
// Check unrelated class
WRITE emp.%IsA("User.Product"), ! // 0
// Use in conditional logic
IF obj.%IsA("User.Person") {
WRITE "Processing person: ", obj.Name, !
} ELSEIF obj.%IsA("User.Product") {
WRITE "Processing product: ", obj.SKU, !
}
%IsA con interfaces
// If User.Employee implements User.Printable interface
IF obj.%IsA("User.Printable") {
DO obj.Print()
}
%ClassName: Obtener el nombre de la clase
SET emp = ##class(User.Employee).%New()
// Short name (no package)
WRITE emp.%ClassName(0), ! // Employee
// Full name (with package)
WRITE emp.%ClassName(1), ! // User.Employee
// Default (no argument) returns short name
WRITE emp.%ClassName(), ! // Employee
// Package name only
WRITE emp.%PackageName(), ! // User
Patrones prácticos
// Logging with type information
ClassMethod LogObject(obj As %RegisteredObject)
{
SET className = obj.%ClassName(1)
WRITE "Processing object of type: ", className, !
// Type-based dispatch
IF obj.%IsA("User.Employee") {
DO ..ProcessEmployee(obj)
} ELSEIF obj.%IsA("User.Customer") {
DO ..ProcessCustomer(obj)
} ELSE {
WRITE "Unknown type: ", className, !
}
}
// Runtime class comparison
ClassMethod AreSameType(obj1, obj2) As %Boolean
{
QUIT obj1.%ClassName(1) = obj2.%ClassName(1)
}
// Check before casting
IF obj.%IsA("User.Admin") {
SET admin = obj // Safe to treat as Admin
DO admin.GrantAccess(resource)
}
Referencias de Documentación
3. %Dictionary para inspección de clases
Puntos Clave
- %Dictionary.ClassDefinition: Representa una definición de clase -- propiedades, métodos, parámetros
- %Dictionary.PropertyDefinition: Metadatos sobre una propiedad individual
- %Dictionary.MethodDefinition: Metadatos sobre un método individual
- %Dictionary.CompiledClass: Versión compilada con información adicional de tiempo de ejecución
- Consulta de metadatos: Abrir definiciones de clase e iterar sobre sus miembros
- Casos de uso: Generadores de código, herramientas de documentación, marcos de validación
Notas Detalladas
Descripción general
El paquete %Dictionary proporciona clases que representan definiciones de clase como objetos persistentes. Puede abrir cualquier definición de clase e inspeccionar sus propiedades, métodos, parámetros, índices y más. Este es el mecanismo de reflexión/introspección de ObjectScript.
Abrir una definición de clase
// Open a class definition
SET classDef = ##class(%Dictionary.ClassDefinition).%OpenId("User.Person")
IF classDef '= "" {
WRITE "Class: ", classDef.Name, !
WRITE "Super: ", classDef.Super, !
WRITE "Description: ", classDef.Description, !
}
Listar propiedades
SET classDef = ##class(%Dictionary.ClassDefinition).%OpenId("User.Person")
IF classDef = "" WRITE "Class not found", ! QUIT
// Iterate over properties
WRITE "Properties:", !
FOR i = 1:1:classDef.Properties.Count() {
SET prop = classDef.Properties.GetAt(i)
WRITE " ", prop.Name
WRITE " (", prop.Type, ")"
WRITE:prop.Required " [Required]"
WRITE !
}
// Example output:
// Name (%Library.String) [Required]
// Age (%Library.Integer)
// City (%Library.String)
Listar métodos
SET classDef = ##class(%Dictionary.ClassDefinition).%OpenId("User.Person")
WRITE "Methods:", !
FOR i = 1:1:classDef.Methods.Count() {
SET method = classDef.Methods.GetAt(i)
WRITE " ", method.Name
WRITE:method.ClassMethod " [ClassMethod]"
WRITE " Returns: ", method.ReturnType
WRITE !
}
Uso de la clase compilada para información de tiempo de ejecución
// CompiledClass includes inherited members
SET compiled = ##class(%Dictionary.CompiledClass).%OpenId("User.Employee")
IF compiled '= "" {
WRITE "All properties (including inherited):", !
FOR i = 1:1:compiled.Properties.Count() {
SET prop = compiled.Properties.GetAt(i)
WRITE " ", prop.Name, " : ", prop.Type
WRITE " (from ", prop.Origin, ")"
WRITE !
}
}
// Shows properties from User.Employee AND User.Person
Consulta de metadatos de clase vía SQL
// Alternative: query %Dictionary tables via SQL
SET sql = "SELECT Name, Type, Required FROM %Dictionary.PropertyDefinition WHERE parent = ?"
SET stmt = ##class(%SQL.Statement).%New()
SET status = stmt.%Prepare(sql)
SET rs = stmt.%Execute("User.Person")
WHILE rs.%Next() {
WRITE rs.Name, " : ", rs.Type
WRITE:rs.Required " [Required]"
WRITE !
}
Verificar si una propiedad o método existe
// Check if a class has a specific property
SET classDef = ##class(%Dictionary.ClassDefinition).%OpenId("User.Person")
SET found = 0
FOR i = 1:1:classDef.Properties.Count() {
IF classDef.Properties.GetAt(i).Name = "Email" {
SET found = 1
QUIT
}
}
WRITE:found "Email property exists", !
WRITE:'found "Email property not found", !
// Simpler: use compiled class and %ExistsId
SET exists = ##class(%Dictionary.CompiledProperty).%ExistsId("User.Person||Email")
WRITE "Email exists: ", exists, !
Práctica: Acceso dinámico a propiedades
// Get all property values from an object dynamically
SET obj = ##class(User.Person).%OpenId(1)
SET classDef = ##class(%Dictionary.CompiledClass).%OpenId("User.Person")
FOR i = 1:1:classDef.Properties.Count() {
SET propName = classDef.Properties.GetAt(i).Name
CONTINUE:$E(propName)="%" // Skip system properties
SET value = $PROPERTY(obj, propName)
WRITE propName, " = ", value, !
}
Referencias de Documentación
Resumen de Preparación para el Examen
Conceptos críticos a dominar:
- $CLASSMETHOD: Invocación dinámica de métodos de clase -- nombres de clase y método como cadenas/variables
- $METHOD: Invocación dinámica de métodos de instancia en una referencia de objeto
- %IsA: Verifica la cadena de herencia -- devuelve verdadero para la propia clase Y todas las clases ancestro
- %ClassName(0) vs %ClassName(1): Nombre corto vs nombre completo (con paquete)
- %Dictionary.ClassDefinition: Metadatos de clase a nivel de código fuente (solo miembros definidos explícitamente)
- %Dictionary.CompiledClass: Metadatos compilados (incluye miembros heredados)
- $PROPERTY: Acceso dinámico a propiedades por nombre -- complemento de $METHOD
- Tiempo de compilación vs tiempo de ejecución: ##class() es en tiempo de compilación; $CLASSMETHOD es resolución en tiempo de ejecución
Escenarios comunes de examen:
- Elegir entre ##class() y $CLASSMETHOD para un escenario dado
- Usar %IsA para verificar herencia antes de realizar operaciones
- Determinar qué devuelve %IsA para clases padre/hijo/no relacionadas
- Consultar metadatos de clase para descubrir propiedades o métodos
- Usar %Dictionary para construir código dinámico o reflexivo
- Distinguir ClassDefinition (fuente) de CompiledClass (incluye heredados)
- Escribir métodos de fábrica usando $CLASSMETHOD con nombres de clase variables
Recomendaciones de práctica:
- Crear una jerarquía de clases y probar %IsA en cada nivel
- Usar $CLASSMETHOD para instanciar objetos con nombres de clase variables
- Usar $METHOD para llamar métodos dinámicamente en objetos
- Abrir objetos %Dictionary.ClassDefinition y explorar las colecciones Properties y Methods
- Comparar ClassDefinition vs CompiledClass para ver miembros heredados
- Consultar tablas %Dictionary vía SQL para metadatos de clase
- Construir una utilidad que muestre todas las propiedades y valores de cualquier objeto usando %Dictionary y $PROPERTY