T4.6: Executes Methods and Queries Objects

Knowledge Review - InterSystems ObjectScript Specialist

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, !
 }

Resumen de Preparación para el Examen

Conceptos críticos a dominar:

  1. $CLASSMETHOD: Invocación dinámica de métodos de clase -- nombres de clase y método como cadenas/variables
  2. $METHOD: Invocación dinámica de métodos de instancia en una referencia de objeto
  3. %IsA: Verifica la cadena de herencia -- devuelve verdadero para la propia clase Y todas las clases ancestro
  4. %ClassName(0) vs %ClassName(1): Nombre corto vs nombre completo (con paquete)
  5. %Dictionary.ClassDefinition: Metadatos de clase a nivel de código fuente (solo miembros definidos explícitamente)
  6. %Dictionary.CompiledClass: Metadatos compilados (incluye miembros heredados)
  7. $PROPERTY: Acceso dinámico a propiedades por nombre -- complemento de $METHOD
  8. 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

Report an Issue