Polimorfismo y WordPress: Clases abstractas

Cuando vimos la herencia en el pasado, había algunas preguntas sobre las interfaces y clases abstractas.

  • ¿Para qué las puedes usar?
  • ¿Cuáles son las ventajas?
  • ¿Cuándo deberías usarlas?

Estas son todas buenas preguntas que valen la pena de explorar. Como el título sugiere, “Polimorfismo” es una característica de la programación orientada a objetos que te ayuda a responder a estas preguntas. No es una característica muy fácil a comprender.

Es por eso que la mayoria del artículo va a estar sobre un ejemplo en profundidad. Vas a ver el proceso de pensamiento implicado con usar esta característica. Este va a ayudarte a comprenderla mejor así puedes aplicarla en tus proprios proyectos.

¿Qué es el polimorfismo?

El polimorfismo se centra sobre una sola idea. Es que puedes usar una interfaz o clase comuna para representar diferentes tipos de objetos. Ahora, hay diferentes tipos de polimorfismo. El uno que nos interesa es el polimorfismo de subtipo. Es el tipo de polimorfismo referenciando en la programación orientada a objetos.

¿Así que es el polimorfismo? Si recuerdas, herencia es sobre la creación de relaciones “como es” entre tus clases. Bien, polimorfismo se trata de estas relaciones. Es como construyes tus clases a su alrededor. Se trata de aprovechar estos elementos comunes entre tus clases.

Cuando trabajas con polimorfismo, te haces preguntas como:

  • ¿Qué tiene mi clase Mi_Widget en común con todas las otras clases widget?
  • ¿Hay una manera de reutilizar elementos widget comunes?
  • ¿Estos elementos comunes están relacionados con algo más?

Estas son preguntas específicas a WordPress. Eso dijo, puedes hacer preguntas generales sin importar el contexto. El objetivo es identificar lo que necesitas extractar y reutilizar entre tus clases. Dependerás de las herramientas que viste en el artículo sobre la herencia para hacerlo.

Copiar y pegar es una manera excelente de detectar el polimorfismo

Como programadores, somos infames para copiar y pegar código. Lo haces para aprender de otros. Pero también, lo haces simplemente para copiar código entre proyectos o dentro de un proyecto.

Si te encuentras copiando código y modificando pocas líneas, es posible que quieras usar polimorfismo. Mira a través este código y te haces las mismas preguntas que antes. Es posible que veas un patrón.

Esto va a ayudarte a lidiar con el código duplicado y arreglar errores. (¡Solo necesitas arreglar un error una vez!) Tu código es más fuerte como resultado. También es posible que aprendas alguna cosa sobre tu código durante el proceso.

Clases abstractas: donde vive tu código común

Ya encontraste algunos elementos comunes en tu código. ¿Qué haces ahora? Aquí es donde entran las clases abstractas. Puedes poner tu código en una y después tus otras clases la heredan.

Entonces encontraste algunos elementos comunes en tu código. ¿Qué haces ahora? Aquí es donde entran clases abstractas. Puedes poner tu código en una y después de tus otras clases la heredan.

Un ejemplo: páginas admin de WordPres

Hagamos un ejemplo practico para repasar el proceso de pensamiento involucrado. Abajo son dos clases que representan dos diferentes páginas admin en WordPress. Se basan sobre el código en el artículo sobre el principio de responsabilidad única.

namespace MiPlugin;
 
/**
 * Página shortcode.
 */
class PaginaShortcode
{
    /**
     * Opciones de mi plugin.
     *
     * @var array
     */
    private $opciones;
 
    /**
     * Inscribir la página shortcode con todos los hooks de WordPress apropiados.
     */
    public static function inscribir()
    {
        $pagina = new self(get_option('mi_plugin_opciones', array()));
 
        add_action('admin_init', array($pagina, 'configurar'));
        add_action('admin_menu', array($pagina, 'anadir_pagina_admin'));
    }
 
    /**
     * Constructor.
     *
     * @param array $opciones
     */
    public function __construct(array $opciones)
    {
        $this->opciones = $opciones;
    }
 
    /**
     * Añadir la página shortcode al menú. 
     */
    public function anadir_pagina_admin()
    {
        add_options_page(__('Mi Plugin Shortcodes', 'my_plugin'), __('Mi Plugin Shortcodes', 'mi_plugin'), 'install_plugins', 'mi_plugin_shortcode', array($this, 'representar'));
    }
 
    /**
     * Configurar la página shortcode.
     */
    public function configurar()
    {
        // Registrar settings
        register_setting('mi_plugin_shortcode', 'mi_plugin_opcions');
 
        // Sección general
        add_settings_section('mi_plugin_shortcode', __('Shortcodes', 'mi_plugin'), array($this, 'representar_seccion'), 'mi_plugin_shortcode');
        add_settings_field('mi_plugin_shortcode_titulo', __('Título del shortcode', 'mi_plugin'), array($this, 'representar_campo_shortcode'), 'mi_plugin_shortcode', 'mi_plugin_shortcode');
    }
 
    /**
     * Representar la página shortcode.
     */
    public function representar()
    {
        ?>
        <div class="wrap" id="mi-plugin-shortcodes">
            <h1><?php echo __('Mi Plugin Shortcodes', 'mi_plugin') ?></h1>
            <form action="options.php" method="POST">
                <?php settings_fields('mi_plugin_shortcode'); ?>
                <?php do_settings_sections('mi_plugin_shortcode'); ?>
                <?php submit_button(); ?>
            </form>
        </div>
        <?php
    }
 
    /**
     * Representar la sección.
     */
    public function representar_seccion()
    {
        ?>
        <p><?php _e('Configurar MiPlugin shortcodes.', 'mi_plugin'); ?></p>
        <?php
    }
 
    /**
     * Representar el campo shortcode.
     */
    public function representar_shortcode_field()
    {
        ?>
        <input id="mi_plugin_shortcode" name="mi_plugin_opcions[shortcode]" type="text" value="<?php echo $this->opciones['shortcode']; ?>" />
        <?php
    }
}
 
/**
 * Página opciones
 */
class PaginaOpciones
{
    /**
     * Opciones de mi plugin.
     *
     * @var array
     */
    private $opciones;
 
    /**
     * Inscribir la página opciones con todos los hooks de WordPress apropiados.
     */
    public static function inscribir()
    {
        $pagina = new self(get_option('mi_plugin_opciones', array()));
 
        add_action('admin_init', array($pagina, 'configurar'));
        add_action('admin_menu', array($pagina, 'anadir_pagina_admin'));
    }
 
    /**
     * Constructor.
     *
     * @param array $opciones
     */
    public function __construct(array $opciones)
    {
        $this->opciones = $opciones;
    }
 
    /**
     * Añadir la página opciones al menú. 
     */
    public function anadir_pagina_admin()
    {
        add_options_page(__('Mi Plugin Opciones', 'mi_plugin'), __('Mi Plugin Opciones¿, 'mi_plugin'), 'install_plugins', 'mi_plugin_shortcode', array($this, 'representar'));
    }
 
    /**
     * Configurar the la página opciones.
     */
    public function configurar()
    {
        // Registrar settings
        register_setting('mi_plugin_opciones', 'mi_plugin_opciones');
 
        // Sección general
        add_settings_section('mi_plugin_opciones', __('Opciones', 'mi_plugin'), array($this, 'representar_section'), 'mi_plugin_opciones');
        add_settings_field('mi_plugin_opciones_color', __('Color personalizado', 'mi_plugin'), array($this, 'representar_campo_color'), 'mi_plugin_opciones', 'mi_plugin_opciones');
    }
 
    /**
     * Representar la página opciones.
     */
    public function representar()
    {
        ?>
        <div class="wrap" id="mi-plugin-options">
            <h1><?php echo __('Mi Plugin Opciones', 'my_plugin') ?></h1>
            <p>Un paragrafo de introducción a tu plugin aquí</p>
            <form action="options.php" method="POST">
                <?php settings_fields('mi_plugin_opciones'); ?>
                <?php do_settings_sections('mi_plugin_opciones'); ?>
                <?php submit_button(); ?>
            </form>
        </div>
        <?php
    }
 
    /**
     * Representar la sección.
     */
    public function representar_seccion()
    {
        ?>
        <p><?php _e('Personaliza MiPlugin con estas opciones', 'mi_plugin'); ?></p>
        <?php
    }
 
    /**
     * Representar el campo color.
     */
    public function representar_campo_color()
    {
        ?>
        <input id="mi_plugin_color" name="mi_plugin_opcion[color]" type="color" value="<?php echo $this->opciones['color']; ?>" />
        <?php
    }
}

Analizando los elementos comunes de las dos clases

Solo por ser páginas admin, sabes que estas dos clases deberían tener elementos comunes. Déjanos mirar el código. ¿Cuál es lo mismo? ¿Cuál es diferente? Cada uno de ustedes tendrá único elementos comunes a tu plugin o tema.

Es posible que los elementos que miras abajo no van a aplicar a ti. Eso dijo, el proceso de pensamiento es lo mismo para todos.

Déjanos empezar con lo que es lo mismo. Toma un poco de tiempo para pensarlo antes de continuar. Aquí es una lista aproximada:

  • Necesitas inscribir la página admin con WordPress.
  • Inscribes los mismos métodos con WordPress.
  • Necesitas acceder las opciones de tu plugin.
  • El constructor es lo mismo.
  • Necesitas representar los campos de formulario en los dos casos.

Ahora, ¿cuál es diferente? Bien, hay pocas cosas.

  • El título de la página es diferente.
  • El slug del menú es diferente.
  • Tus formularios son diferentes y necesitan diferentes interacciones con el API de settings.
  • Representar estos formularios van a ser diferentes.

Crear una clase abstracta

Con nuestro análisis completado, déjanos construir una clase abstracta con esa información. Esta clase abstracta representa la esencia de lo que es una página admin.

namespace MiPlugin;
 
/**
 * Clase base para todas las páginas de admin WordPress.
 */
abstract class AbstractPaginaAdmin
{
    /**
     * Opciones de mi plugin.
     *
     * @var array
     */
    private $opciones;
 
    /**
     * Inscribir la página admin con todos los hooks de WordPress apropiados.
     */
    public static function inscribir()
    {
        $page = new static(get_option('mi_plugin_opciones', array()));
 
        add_action('admin_init', array($pagina, 'configurar'));
        add_action('admin_menu', array($pagina, 'anadir_pagina_admin'));
    }
 
    /**
     * Constructor.
     *
     * @param array $opciones
     */
    public function __construct(array $opciones)
    {
        $this->opciones = $opciones;
    }
 
    /**
     * Añadir la página admin al menú. 
     */
    public function anadir_pagina_admin()
    {
        add_options_page($this->obtenga_titulo(), $this->obtenga_titulo(), 'install_plugins', $this->obtenga_menu_slug(), array($this, 'representar'));
    }
 
    /**
     * Configurar the la página admin con el API de settings.
     */
    abstract public function configurar();
 
    /**
     * Representar la página admin.
     */
    abstract public function representar();
 
    /**
     * Representar el campo de formulario.
     *
     * @param string $id
     * @param string $nombre
     * @param string $valor
     * @param string $tipo
     */
    protected function representar_campo_formulario($id, $nombre, $valor = '', $tipo = 'text')
    {
        ?>
        <input id="<?php echo $id; ?>" name="<?php echo $nombre; ?>" type="<?php echo $tipo; ?>" value="<?php echo $valor; ?>" />
        <?php
    }
 
    /**
     * Obtenga el menu slug de la página admin.
     *
     * @return string
     */
    abstract protected function obtenga_menu_slug();
 
    /**
     * Obtenga el título de la página admin.
     *
     * @return string
     */
    abstract protected function obtenga_titulo();
}

Vamos a repasar algunas decisiones de diseño. Mientras aprendes más conceptos orientados a objetos, estas decisiones devienen más complicadas. No son obvias porque los problemas son más difíciles. Pero de eso se trata la programación orientada a objetos al final.

Es por qué sea útil para ti mirar por qué y como estas decisiones ocurren. Esto te permite desarrollar tu propia opinión y buscar tus propias soluciones.

Inscribir e iniciar cada página admin

Para solucionar este problema, necesitamos los tres métodos definidos abajo. Utilizas el método iniciar que nuestro constructor personalizado. Llama el constructor y añade los hooks comunes. También necesitas inscribir las páginas de opciones con WordPress.

Una preocupación valida sería que no quisieras utilizar ‘get_option’ para cada página admin. En este caso, el constructor no es necesario. Podrías utilizar simplemente la sobrescritura de métodos en la clase niña para cargar las opciones. Aquí como podrías hacerlo.

namespace MiPlugin;
 
/**
 * Clase base para todas las páginas de admin WordPress.
 */
abstract class AbstractPaginaAdmin
{
    /**
     * Inscribir la página admin con todos los hooks de WordPress apropiados.
     */
    public static function inscribir($pagina = null)
    {
        if (null === $pagina) {
            $pagina = new static();
        }
 
        add_action('admin_init', array($pagina, 'configure'));
        add_action('admin_menu', array($pagina, 'add_admin_page'));
    }
 
    // ...
}
 
/**
 * Página admin utilizando la sobrescritura de métodos
 */
class PaginaAdmin extends AbstractPaginaAdmin
{
    /**
     * Opciones de mi plugin.
     *
     * @var array
     */
    private $opciones;
 
    /**
     * Inscribir la página admin con todos los hooks de WordPress apropiados.
     */
    public static function inscribir()
    {
        // Create an instance of the admin page with the options
        $page = new self(get_option('my_plugin_options', array()));
 
        // Pass it up to the main register function
        parent::register($page);
    }
 
    /**
     * Constructor.
     *
     * @param array $opciones
     */
    public function __construct(array $opciones)
    {
        $this->opciones = $opciones;
    }
    
    // ...
}

Métodos abstractos

La clase tiene cuatro métodos abstractos definidos: ‘configurar’, ‘representar’, ‘obtenga_menu_slug’ y ‘obtenga_titulo’. La clase abstracta inscribió estos métodos con WordPress porque cada página admin los necesita.

Eso dijo, cada página admin no necesita que estos métodos hagan la misma cosa. Cada página tiene su propio título único, menu slug, configuración y representación. Es por eso que los definimos como abstractos con la palabra clave abstract.

Ahora, si no te habías dado cuenta, estos cuatro métodos tienen nada en común. Representan lo que analizamos que es diferente entre las dos clases. Métodos abstractos son una de las soluciones posibles para manejar estos elementos diferentes.

Funciona bien en este ejemplo, pero podrías hacer esto todo el tiempo. Podrías querer utilizar sobrescritura de métodos en lugar. Cada situación es única. Esto es donde conocimiento de la herencia es útil.

Método representar_campo_formulario

Esto es un método helper que cualquier página admin puede utilizar. Métodos helper como esto valen la pena de poner en la clase abstracta. Al hacer eso, cualquier clase niña puede aprovechar ellos. En este caso, la mayoría de las páginas admin necesitan representar un campo de formulario.

Vale la pena de mencionar que no deberías añadir todos los métodos en la clase abstracta. Quieres tomar el tiempo para pensarlo. No quieres que tu clase abstracta sea para todo. Terminarás con una clase que es larga y difícil de trabajar.

Como se mencionó anteriormente, copiar y pegar es una buena manera de testear lo que necesitas poner en la clase. ¿Copias y pegas código entre dos clases de páginas admin? Toma un buen vistazo a lo que copias. ¿Cómo puedes cambiarlo de manera de reutilizarlo en otro lugar? Una vez que lo sabes, eso es lo que podría ir en tu clase abstracta.

Las clases niñas

Vamos a ver las clases niñas resultantes. Ellas son más pequeñas. Su código es más claros. Es porque extractas el código común fuera de ellas. Lo que quedas es código que enfoca sobre el trabajo único que cada clase necesita hacer.

namespace MiPlugin;

class PaginaShortcode extends AbstractPaginaAdmin
{
    /**
     * Configurar la página shortcode.
     */
    public function configurar()
    {
        // Registrar settings
        register_setting('mi_plugin_shortcode', 'mi_plugin_opcions');
 
        // Sección general
        add_settings_section('mi_plugin_shortcode', __('Shortcodes', 'mi_plugin'), array($this, 'representar_seccion'), 'mi_plugin_shortcode');
        add_settings_field('mi_plugin_shortcode_titulo', __('Título del shortcode', 'mi_plugin'), array($this, 'representar_campo_shortcode'), 'mi_plugin_shortcode', 'mi_plugin_shortcode');
    }
 
    /**
     * Representar la página shortcode.
     */
    public function representar()
    {
        ?>
        <div class="wrap" id="mi-plugin-shortcodes">
            <h1><?php echo $this->obtenga_titulo() ?></h1>
            <form action="options.php" method="POST">
                <?php settings_fields($this->obtenga_menu_slug()); ?>
                <?php do_settings_sections($this->obtenga_menu_slug()); ?>
                <?php submit_button(); ?>
            </form>
        </div>
        <?php
    }
 
    /**
     * Representar la sección.
     */
    public function representar_seccion()
    {
        ?>
        <p><?php _e('Configurar MiPlugin shortcodes.', 'mi_plugin'); ?></p>
        <?php
    }
 
     /**
     * Representar el campo shortcode.
     */
    public function representar_campo_shortcode()
    {
       $this->representar_campo_formulario('mi_plugin_shortcode', 'mi_plugin_opcions[shortcode]', $this->opciones['shortcode']);
    }
 
    /**
     * Obtenga el menu slug de la página admin.
     *
     * @return string
     */
    protected function obtenga_menu_slug()
    {
        return 'mi_plugin_shortcode';
    }
 
    /**
     * Obtenga el título de la página admin.
     *
     * @return string
     */
    protected function obtenga_titulo()
    {
        return __('Configurar MiPlugin shortcodes.', 'mi_plugin');
    }
}

class PaginaOpciones extends AbstractPaginaAdmin
{
    /**
     * Configurar the la página opciones.
     */
    public function configurar()
    {
        // Registrar settings
        register_setting('mi_plugin_opciones', 'mi_plugin_opciones');
 
        // Sección general
        add_settings_section('mi_plugin_opciones', __('Opciones', 'mi_plugin'), array($this, 'representar_section'), 'mi_plugin_opciones');
        add_settings_field('mi_plugin_opciones_color', __('Color personalizado', 'mi_plugin'), array($this, 'representar_campo_color'), 'mi_plugin_opciones', 'mi_plugin_opciones');
    }
 
    /**
     * Representar la página opciones.
     */
    public function representar()
    {
        ?>
        <div class="wrap" id="my-plugin-opciones">
            <h1><?php echo $this->obtenga_titulo() ?></h1>
            <p>Un paragrafo de introducción a tu plugin aquí</p>
            <form action="options.php" method="POST">
                <?php settings_fields($this->obtenga_menu_slug()); ?>
                <?php do_settings_sections($this->obtenga_menu_slug()); ?>
                <?php submit_button(); ?>
            </form>
        </div>
        <?php
    }
 
    /**
     * Representar la sección.
     */
    public function representar_seccion()
    {
        ?>
        <p><?php _e('Personaliza MiPlugin con estas opciones', 'mi_plugin'); ?></p>
        <?php
    }
 
    /**
     * Representar el campo color.
     */
    public function representar_campo_color()
    {
        $this->representar_campo_formulario('mi_plugin_color', 'mi_plugin_options[color]', $this->opciones['color'], 'color');
    }
 
    /**
     * Obtenga el menu slug de la página admin.
     *
     * @return string
     */
    protected function obtenga_menu_slug()
    {
        return 'mi_plugin_options';
    }
 
    /**
     * Obtenga el título de la página admin.
     *
     * @return string
     */
    protected function obtenga_titulo()
    {
        return __('Mi Plugin Opciones', 'mi_plugin');
    }
}

Estamos entrando en territorio del diseño

El objetivo de estos ejemplos te está mostrando el proceso de pensamiento cuando utilizando el polimorfismo. Es para facilitarte la idea del diseño con objetos. Es cómo utilizas las herramientas sobre tu cinturón para resolver tus problemas únicos.

Problemas que no siempre tienen soluciones predefinidas porque cada situación es única. También algunas veces hay más que una manera de hacer lo que quieres hacer.

Un ejemplo sobre copiar y pegar

Aquí es un ejemplo de decisión de diseño utilizando la clase abstracta mostrada antes. La primera versión de la clase tenía un método abstracto para añadir una página admin pero ninguno para representarla. Cada página necesita añadir su propia página admin y métodos para representarla. A primera vista, tenía sentido.

namespace MiPlugin;
 
/**
 * Clase base para todas las páginas de admin WordPress.
 */
abstract class AbstractPaginaAdmin
{
    /**
     * Mi plugin opciones.
     *
     * @var array
     */
    protected $opciones;
 
    /**
     * Inscribir la página admin con todos los hooks de WordPress apropiados.
     */
    public static function inscribir()
    {
        $pagina = new static(get_option('mi_plugin_opciones', array()));
 
        add_action('admin_init', array($pagina, 'configurar'));
        add_action('admin_menu', array($pagina, 'anadir_pagina_admin'));
    }
 
    /**
     * Constructor.
     *
     * @param array $opciones
     */
    public function __construct(array $opciones)
    {
        $this->opciones = $opciones;
    }

    /**
     * Añadir la página admin al menú. 
     */
    abstract public function anadir_pagina_admin()
 
    /**
     * Configurar la página admin.
     */
    abstract public function configurar();
 
    /**
     * Representar el campo de formulario.
     *
     * @param string $id
     * @param string $nombre
     * @param string $valor
     * @param string $tipo
     */
    protected function representar_campo_formulario($id, $nombre, $valor = '', $tipo = 'text')
    {
        ?>
        <input id="<?php echo $id; ?>" name="<?php echo $nombre; ?>" type="<?php echo $tipo; ?>" value="<?php echo $valor; ?>" />
        <?php
    }
}

Cuando fue el tiempo de crear las clases niñas, copié el mismo código todo el tiempo. Tuve el método anadir_pagina_admin, pero solo cambié el título de la página. Esto levantó una bandera de inmediato.

¿”Qué necesito realmente en mi clase niña”?, me pregunté.

La respuesta fue que necesité representar la página y necesité el título y slug de la página. Así modifiqué el código para obtener el resultado que vistes antes. Añadí los métodos representar, obtenga_menu_slug y obtenga_titulo come métodos abstractos. Reemplacé el método anadir_pagina_admin con un método utilizando estos tres métodos abstractos.

¿Qué pasa con las interfaces?

El plan inicial fue que este artículo debería cubrir ambos interfaces y clases abstractas. A medida que el articulo creció, se convirtió obvio que interfaces necesitaban su propio artículo. Necesitan su propia discusión, ejemplos y conclusión.

Vamos cubrir esto en otro artículo.

Deja un comentario

Tu dirección de correo electrónico no será publicada.