Nomenclatura en la escritura de código

Nomenclatura en la escritura de código

Existen múltiples aspectos que hay que tener en cuenta en el desarrollo de software com pueda ser la planificación, el diseño de la aplicación, diseño de datos, la estructura de nombres de espacios, estructura de clases, los algoritmos, su eficiencia, la modularidad, por supuesto la abstracción, el encasulapmiento, la herencia, el polimorfismo, la limpieza en el código, el orden, la nomenclatura, etc.En este post, vamos a dedicarnos a ver la nomenclatura en los lenguajes de Java y .NET.

Y, ¿que es la nomenclantura? pues son los procedimientos empleados para nombrar los elementos del código y estos nombres empleados por convención Sun para Java y Microsoft para .NET recomiendan usar. El motivo por el cual debemos usar la convención recomendada nos va a permitir grandes ventajas en el futuro a la hora de modificar, ampliar o simplemente usar nuestras clases.

Para el nombramiento de elementos, utilizaremos un método llamado Camelcase o nomenclatura camello; esto quiere decir que escribiremos las palabras sin espacios y cada vez que se inicia una nueva palabra, se comienza en mayúsculas como por ejemplo, «EstoEsUnaPruebaDeCamelCase». Además, existen dos tipos dependiendo si la primera letra comienza por mayúsculas (UpperCamelCase), o la primera letra comienza por minúsculas (lowerCamelCase).

Nos recomiendan también no usar la notación húngara en la que se nombran los tipos dependiendo de lo que sean añadiéndole un prefijo a cada objeto u otra recomendación es no simplificar o abreviar; si llamo a una propiedad PuedeVerseDeDia, no sería correcto llamarla PuedeVerDia o PVerDia. El procedimiento de nombrar el código de esta manera tan natural como si hablaramos, SalirPorLaPuertaPrincipal, le llamamos nomenclatura Pascal.

De todos estos elementos, también podemos decir que cada uno de estos elementos deben nombrarse del siguiente modo:

  • Espacios de nombres.  Para nombrar un espacio de nombres deberíamos usar un nombre que indique la funcionalidad que proporcionan los tipos incluidos en el espacio de nombres, por ejemplo si tuvieramos una serie de clases que supieran pescar o tuvieran algo que ver con la pesca, mi espacio de nombres se llamaría Waki.Pesca. UpperCamelCase para .NET y lowerCamelCase para Java (en Java se llaman package y agrupan clases.
  • Clases. Para las clases debemos usar nombres que identifiquen el objeto o sintagmas nominales, por ejemplo la clase Persona, Coche, Avion, DireccionIP, Electrodomestico, etc y para el caso de herencia, se aconseja que termine con el nombre de la clase que hereda, de modo que EmpleadoPersona, sería la clase Empleado que hereda de Persona o bien en .NET una clase que hereda de System.EventArgs finalizará con EventArgs o los que heredan de System.Exception que finalizan con Exception. Ahora bien, un objeto List o un ArrayList de la clase Persona debería llamarse personas para indicar que contiene objetos de este tipo aunque también se puede ver acabado con Collection como PersonasCollectionUpperCamelCase para .NET y Java
  • Interfaces. Para las interfaces se usaría el mismo método que las clases pero añadiéndoles una I al inicio del nombre y finalizando con «able» para indicar que un objeto que implementa una interfaz es capaz de realizar las funciones que la interfaz le dice que tiene que hacer. Si tenemos una clase que implementa la interfaz ISpeakable, esta debería hablar porque la interfaz así lo requiere.En .NET puedes encontrar interfaces con la letra I al inicio pero no se les añade able como sufijo como ICloneable e IComparable, IQueryable, simplemente se les trata igual que las clases, pero con la letra I, por ejemplo NotifyPropertyChanged o IAsyncResult. UpperCamelCase para .NET y Java
  • Métodos y funciones. Para los métodos debemos usar verbos o sintagmas verbales, por ejemplo avanzar, volver, venderCoche, añadirArticulo, etc. UpperCamelCase para .NET y lowerCamelCase para Java.
  • Variables y propiedades. Debemos nombralos con un sustantivo, sintagma nominal o un adjetivo, por ejemplo para nombrar la altura de un triángulo, podemos hacerlo con la propiedad Height o Altura, para el radio de una circunferencia Radio, etc. Las propiedades booleanas con un tru o false, es muy recomendable usar Is o Es, Can o Puede y Has o Tiene, por ejemplo si determinamos la propiedad de visibilidad de un componente podemos hacerlo con IsVisible, comprobar si una lista de objetos contiene estos HasObject, o si un objeto puede saltar CanJump Para el caso de .NET es muy recomendable usar propiedades en vez de variables de clase o campos. Las variables privadas comienzan por minúscula y en los parámetros aunque Microsoft no lo recomienda, comienzo los nombres con un guión bajo. si llamo al método Avanzar(50), el método lo escribo como
    
    void Avanzar(double _value){}.
    
    

    UpperCamelCase para .NET y lowerCamelCase para Java.

  • Constantes. Las constantes en Java son nombradas con mayúsculas y los espacios son sustituidos por el guión bajo, como CONSTANTE_GRAVITACIONAL. En .NET se tratan igual que las variables.
  • Eventos. Al igual que los métodos, usaremos verbos o sintagmas verbales pero haciendo alusión al tiempo presente y pasado. Aquí podemos poner como ejemplo de un objeto que salta y el evento se produce, cuando este comienza a saltar genera el evento Saltando o Jumping en ese mismo momento y Saltado o Jumped cuando ha dejado de hacerlo. En .NET si declaramos el evento EventHandler<T> este debería nombrarse acabando en EventHandler, por ejemplo si declaramos un cambio en el texto de una propiedad, podriamos escribir public event EventHandler<EventArgs> ChangedTextEventHandler mientras que para invocar el evento nombraríamos el método de invocación como OnChangedText.
  • Enumeraciones. Las nombraremos como las propiedades pero en plural, por ejemplo si creamos una enumeración de colores, la llamaremos Colores. (que original)

Otra cosa es ¿los escribo en inglés o español? pues como desees, yo escribo mi código en inglés porque es más universal (aunque el español también lo sea, el inglés es más, ya nos gustaría), pero lo que tiene que estar claro es que sigas las recomendaciones. Lo normal es que si creas una clase para que pueda ser usada por otras personas, los miembros públicos de la clase los escribiría en inglés, es decir cuando instancie ese objeto, reconozca y entienda el programador que quiere hacer o a que pertenece; el motor interior o miembros privados ya son cosa tuya, pero si mañana tienes que modificar o ampliar la clase y no has escrito y comentado correctamente el código mal vas a ir.

Bueno pues aún hay más, las bases de datos también tienen su nomenclatura, existen más tipos como delegados, recursos y otros, pero lo principal es el orden y el entendimiento. Espero que os sirva y seguid las recomendaciones.

Un saludo amig@s

Pd: He cambiado mi modo de escribir código muchas veces, pero siempre adaptándolo a las recomendaciones, no he reescrito lo anterior, pero lo nuevo si lo he hecho. Esto es un no parar.

ConsoleTools (2). Java

ConsoleTools (2). Java

En la segunda parte de las herramientas de consola, voy a exponeros un grupo de utilidades y herramientas de uso de archivos y serialización.En el grupo de utilidades disponemos de los siguientes métodos:

  • waiting.Método que realiza una espera controlada; lo utilizo cuando se visualiza un texto en la consola, se realiza una espera y se vuelve al control de programa. Este método contiene varias sobrecargas, una sin parámetros en la que se realzia una espera estándar de 3 segundos, otra con un parámetro de tiempo de espera, una más con tiempo de espera y mensaje inicial en pantalla y por último una con tres, tiempo de espera, mensaje inicial y mensaje final una vez agotado el tiempo. Creo que con estas sobrecargas están cubiertas nuestras necesidades.
        /**
         * Realiza una espera controlada de 3 segundos
         */
        public static void waiting(){
            waiting(3, "", "");
        }
    
        /**
         * Realiza una espera controlada
         * @param seconds Número de segundos de espera
         */
        public static void waiting(long seconds){
            waiting(seconds, "", "");
        }
    
         /**
         * Realiza una espera controlada
         * @param seconds Número de segundos de espera
         * @param initialMessage Mensaje visualizado antes de la espera
         */
        public static void waiting(long seconds, String initialMessage){
            waiting(seconds, initialMessage, "");
        }
    
        /**
         * Realiza una espera controlada
         * @param seconds Número de segundos de espera
         * @param initialMessage Mensaje visualizado antes de la espera
         * @param finalMessage Mensaje visualizado después de la espera
         */
        public static void waiting(long seconds, String initialMessage, String finalMessage){
    
            long initialTime=System.currentTimeMillis();
            long currentTime=0;
    
            System.out.println(initialMessage);
    
            do {
                currentTime=System.currentTimeMillis();
            } while (currentTime-initialTime<seconds*1000);
    
            System.out.println(finalMessage);
        }
    
  • keyDownToContinue. Este método visualiza en pantalla ‘Pulse INTRO para continuar’ y en realidad lo que hace es capturar una entrada vacía que no usa y nos permite continuar.
         /**
         * Solicita una pulsación de la tecla INTRO desde teclado para continuar
         * @throws IOException
         */
        public static void keyDownToContinue() throws IOException{
            Scanner in=new Scanner(System.in);
            System.out.println("Pulse INTRO para continuar...");
            in.nextLine();
        }
    
  • confirmationMessage. Mensaje que se visualiza en pantalla solicitando continuar o no mediante la pulsación de S o N devolviendo true o false.
        /**
         * Visualiza un mensaje a espera de confirmación S/N
         * @param message Mensaje que se visualiza
         * @return True para S o s. False para N o n.
         * @throws IOException
         */
        public static boolean confirmationMessage(String message) throws IOException{
            // Declaración de variables
            BufferedReader keyboardIn=new BufferedReader(new InputStreamReader(System.in));
            String textIn=null;
    
            boolean yes=false;
            boolean boolIn=false;
            System.out.println(message);
            do {
                System.out.println("\n¿Desea continuar?\nIntroduzca S o N");
                textIn = keyboardIn.readLine();
                boolIn=!"S".equals(textIn) && !"s".equals(textIn) && !"N".equals(textIn) && !"n".equals(textIn);
            } while (boolIn);
    
            if (textIn.equals("S") || textIn.equals("s")) {
                yes=true;
            }
    
            return yes;
        }
    
  • clearScreen. En las aplicaciones de consola, no podemos hacer una limpieza de la pantalla como tal, por lo que para hacer este proceso, simplemente añadimos una serie de líneas en blanco. Este método se encuentra swobrecargado dos veces con una inclusión de 20 líneas y otro donde se pueda indicar este número.
         /**
         * Borra la pantalla añadiendo lineas nuevas
         * Valor por defecto 20
         */
        public static void clearScreen(){
            clearScreen(20);
        }
    
        /**
         * Borra la pantalla añadiendo lineas nuevas
         * @param lines Número de líneas que se añadirán
         */
        public static void clearScreen(int lines){
            for (int i = 0; i < lines; i++) {
                System.out.println("");
            }
        }
    
  • getRandomInteger y getRandomDouble. Pocas explicaciones tienen estos métodos. Obtención de un número aleatorio entero u obtención de un número aleatorio doble.
         /**
         * Devuelve un valor aleatorio entre dos números
         * @param m Valor inferior
         * @param n Valor Superior
         * @return Número entero aleatorio
         */
        public static int getRandomInteger(int m, int n){
             return (int)(getRandomDouble(m,n));
        }
    
        /**
         * Devuelve un valor aleatorio entre dos números
         * @param m Valor inferior
         * @param n Valor Superior
         * @return Número double aleatorio
         */
        public static double getRandomDouble(int m, int n){
            double number=(Math.random()*(n-m+1)+m);
            return number;
        }
    
    
  • createMenu. Este método crea un menú en base a un array de texto que será en realidad el que contiene los items del menú. Esto sería el equivalente a un ComboBox con unos items y de los cuales escogemos uno de ellos. Tiene 3 sobrecargas, la primera solo pasa como parámetro el array de texto, la segunda además incluye un texto que se usará como cabecera de menú y por último uno más que añade el caracter utilizado como marco del menú, el cual en su defecto se usa un asterisco ‘*’. Mostramos el código y una muestra de como quedaría.
        /**
         * Crea un menú desde un array de cadenas con asterisco por defecto
         * @param arg Array String. Items del menú
         * @return Cadena de texto con menú de consola
         */
        public static String createMenu(String arg[]){
            return createMenu(arg,"*","");
        }
    
        /**
         * Crea un menú desde un array de cadenas
         * @param arg Array String. Items del menú
         * @param header Cabecera usada antes del menu
         * @return Cadena de texto con menú de consola
         */
        public static String createMenu(String arg[], String header){
            return createMenu(arg,"*",header);
        }
    
        /**
         * Crea un menú desde un array de cadenas
         * @param arg Array String. Items del menú
         * @param stringMenu Caracter utilizado para el marco del menú
         * @param header Cabecera usada antes del menu
         * @return Cadena de texto con menú de consola
         */
        public static String createMenu(String arg [], String stringMarcoMenu, String header){
    
            // Advertencias y validación
            if (stringMarcoMenu.length()>1){
                return "La cadena de menú debe contener solo un carácter.";
            }
            if (arg.length>99) {
                return "La longitud máxima del menú, debe ser inferior a 100";
            }
    
            // Declaración de variables
            int maxLong=0;
            String asterisk="";
            String textOut="";
    
            // Determinar la longitud máxima del texto
            for (int i = 0; i maxLong?arg[i].length():maxLong;
            }
    
            String [] temp=header.split("\\n");
            for (int i = 0; i maxLong?temp[i].length():maxLong;
            }
    
            // Cadena marco del menú
            // Sumo 10 para compensar los espacios, stringMenu, paréntesis y el número
    
            // crear el menú
            for (int i = 0; i < maxLong + 10; i++) {
                asterisk += stringMarcoMenu;
            }
    
            textOut +=asterisk + "\n";
    
            // cabecera si la hay
            textOut += header + dumpChar(" ", maxLong-header.length()) + "\n" + asterisk + "\n";
    
            for (int i = 0; i < arg.length; i++) {
                textOut += stringMarcoMenu + " (" + (i+1) + ") " + arg[i];
                for (int j = arg[i].length(); j <maxLong+3 ; j++) {
                    textOut += " ";
                }
                textOut += stringMarcoMenu+ "\n";
            }
            textOut +=asterisk + "\n";
            return textOut;
        }
    
    

    una muestra de como quedaría el menú del sistema planetario pasándole como parámetro el array String[] menu={"Calcular distancia entre dos objetos", "Calcular atracción gravitacional", "Calcular tiempo (línea recta)", "Listar objetos", "Salir"}; y como texto de cabecera ‘Sistema Solar’.

    
    **********************************************
    Sistema Solar
    **********************************************
    * (1) Calcular distancia entre dos objetos   *
    * (2) Calcular atracción gravitacional       *
    * (3) Calcular tiempo (línea recta)          *
    * (4) Listar objetos                         *
    * (5) Salir                                  *
    **********************************************
    
    Introduzca la opción...

Grupo de herramientas de archivos:

  • createFile.Pasando la ruta de un archivo, crea este.
  • createFolder.Pasando la ruta de la carpeta, crea esta.
  • existFile. Comprueba si un archivo existe en la ruta especificada
    /**
     * Crea un nuevo archivo
     * @param path Ruta del archivo
     * @throws IOException
     */
    public static void createFile(String path) throws IOException{

        try {
            File myFile=new File(path);
            if (myFile.createNewFile())
                System.out.println("El fichero se ha creado correctamente");
            else
                System.out.println("No ha podido ser creado el fichero");
        }
            catch (IOException ioe) {
                    ioe.printStackTrace();
                    }

    }

    /**
     * Crea una nueva carpeta
     * @param path Ruta de la carpeta
     * @throws IOException
     */
    public static void createFolder(String path) throws IOException{
        try
        {
            File myFile=new File(path);
            if (myFile.mkdir())
                System.out.println("La carpeta se ha creado correctamente");
            else
                System.out.println("No ha podido ser creada la carpeta");
        }
        catch (Exception ex){
            System.out.println(ex.getMessage());
        }
    }

    /**
     * Comprueba si existe un archivo
     * @param path Ruta del archivo
     * @return True si existe. False si no existe
     */
    public static boolean existFile(String path){
        File myFile=new File(path);
        return myFile.exists();
    }

Serialización. Por si alguien no sabe lo que es serialización, es guardar en un archivo, en memoria o en una base de datos, un objeto en bytes y deserializar es el proceso inverso, obtener un objeto desde un archivo, memoria o base de datos, de modo que podemos guardar objetos para luego extraerlos y usarlos de nuevo. Por tanto, los métodos de este grupo son serializar y deserializar:

    /**
     * Serializa un objeto en un archivo
     * @param iObject Objeto que se serializará
     * @param filePath Ruta del archivo
     */
    public static void serializar(Object iObject, String filePath){
        FileOutputStream fileOutputStream = null;
        ObjectOutputStream salidaSerializada = null;

        try {
            fileOutputStream = new FileOutputStream(filePath);
            salidaSerializada = new ObjectOutputStream(fileOutputStream);
            salidaSerializada.writeObject(iObject);  

            salidaSerializada.close();
            fileOutputStream.close();

        } catch (FileNotFoundException e) {
            System.out.println(e.getMessage());
        } catch (IOException e) {
            System.out.println(e.getMessage());
        } finally {
            try {
                if(fileOutputStream!=null) fileOutputStream.close();
                if(salidaSerializada!=null) salidaSerializada.close();
            } catch (IOException e) {
                System.out.println(e.getMessage());
            }
        }
    }

    /**
     * Retorna un objeto deserializado desde un archivo
     * @param filePath Ruta del archivo
     * @return Objeto deserializado
     * @throws IOException
     * @throws ClassNotFoundException
     */
    public static Object deserializar(String filePath) throws IOException, ClassNotFoundException{

        FileInputStream fileInputStream = null;
        ObjectInputStream entradaSerializada = null;
	Object iObject=null;
        try {
            fileInputStream=new FileInputStream(filePath);
            entradaSerializada=new ObjectInputStream(fileInputStream);

            iObject=entradaSerializada.readObject();
            entradaSerializada.close();
            fileInputStream.close();
            return iObject;
        } catch (FileNotFoundException e) {
            System.out.println(e.getMessage());
        } catch (IOException e) {
            System.out.println(e.getMessage());
        } finally {
            try {
                if(fileInputStream!=null) fileInputStream.close();
                if(entradaSerializada!=null) entradaSerializada.close();
            } catch (IOException e) {
                System.out.println(e.getMessage());
            }
	    return iObject;
        }
    }

Pues ya que tenemos nuestras herramientas,os muestro el método utilizado para crear el menú en el sistema planetario el cual crea el menú, entra en un bucle do while y dentro de este se encuentra una instrucción switch con las opciones que podría seleccionar el usario y dentro de cada caso, las acciones a realizar, las cuales utilizan muchos de los métodos incluidos en esta clase.

Saludos «der Waki»

    private static void menuPrincipal() throws IOException, ParseException{
        // DECLARACIÓN DE VARIABLES
        String opcionMenu = "";
        String[] menu={"Calcular distancia entre dos objetos", "Calcular atracción gravitacional",
            "Calcular tiempo (línea recta)", "Listar objetos", "Salir"};

        // MENU, INICIO Y FIN
        do {
            System.out.println(ConsoleUtilities.createMenu(menu,sl.nombre));
            opcionMenu = String.valueOf(ConsoleUtilities.readText("Introduzca la opción..."));

            switch (opcionMenu) {
                case "1":
                    // Calcular distancia entre objetos
                    String str1 = ConsoleUtilities.readText("Introduzca el nombre del primer objeto...");
                    String str2="";
                    ObjetoSistema obj1=sl.getObjeto(str1);
                    ObjetoSistema obj2=null;
                    if (obj1==null) {
                        System.out.println("El objeto no existe.");
                    }
                    else {
                        str2 = ConsoleUtilities.readText("Introduzca el nombre del segundo objeto...");
                        obj2=sl.getObjeto(str2);
                        if (obj2==null) {
                            System.out.println("El objeto no existe.");
                        }
                        else {
                            System.out.println(ConsoleUtilities.toNumber(obj1.calcularDistancia(obj2)/1000) + " Km");
                        }
                    }
                    ConsoleUtilities.keyDownToContinue();
                    break;
                case "2":
                    // Calcular atracción gravitacional entre dos objetos
                    String str3 = ConsoleUtilities.readText("Introduzca el nombre del primer objeto...");
                    String str4="";
                    ObjetoSistema obj3=sl.getObjeto(str3);
                    ObjetoSistema obj4=null;
                    if (obj3==null) {
                        System.out.println("El objeto no existe.");
                    }
                    else {
                        str4 = ConsoleUtilities.readText("Introduzca el nombre del segundo objeto...");
                        obj4=sl.getObjeto(str4);
                        if (obj4==null) {
                            System.out.println("El objeto no existe.");
                        }
                        else {
                            double calculo=obj3.getAtraccion(obj4);
                            System.out.println(ConsoleUtilities.toNumber(calculo) + " Newton");
                        }
                    }
                    ConsoleUtilities.keyDownToContinue();
                    break;

                case "3":
                    // Calcular velocidad
                    String str5 = ConsoleUtilities.readText("Introduzca el nombre del primer objeto...");
                    String str6="";
                    ObjetoSistema obj5=sl.getObjeto(str5);
                    ObjetoSistema obj6=null;
                    if (obj5==null) {
                        System.out.println("El objeto no existe.");
                    }
                    else {
                        str6 = ConsoleUtilities.readText("Introduzca el nombre del segundo objeto...");
                        obj6=sl.getObjeto(str6);
                        if (obj6==null) {
                            System.out.println("El objeto no existe.");
                        }
                        else {
                            double calculoKm=obj5.calcularDistancia(obj6)/1000;
                            double velocidad = ConsoleUtilities.readDouble("Introduzca la velocidad km/h (Si pulsa INTRO, se asignará 28000 km/h... ",28000);
                            if (velocidad>0){
                                double tiempo=(calculoKm / velocidad)/(24);

                                System.out.printf("Invertirá para recorrer %s Km a una velocidad de %s Km/h, %s días\n",
                                        ConsoleUtilities.toNumber(calculoKm),ConsoleUtilities.toNumber(velocidad), ConsoleUtilities.convertToTime(tiempo*60*60*24));

                            }
                            else {
                                System.out.println("Velocidad no válida");
                            }
                        }
                    }
                    ConsoleUtilities.keyDownToContinue();
                    break;
                case "4":
                    // Listar objetos
                    menuListado();
                    break;
            }

        } while (!opcionMenu.equals("5"));

        ConsoleUtilities.clearScreen();
        System.out.println("La aplicación ha finalizado");
    }

ConsoleTools (1). Java

ConsoleTools (1). Java

Como ya os comenté en un post anterior, es importante que vayamos creando nuestras clases de código reutilizable, como por ejemplo en este caso os voy a mostrar una clase de herramientas y utilidades que uso para aplicaciones de consola en Java. Como las aplicaciones de consola suelen ser eso, texto puro en consola, a veces es necesario obtener claridad en los textos o en otro caso simplificar acciones que comunmente realizamos, para ello, inlcuyo dentro de la clase una serie de métodos estáticos que me van a permitir crear más fácil y rápido mis aplicaciones.

Dentro de la clase he creado distintos grupos de código, lo primero para aclararme y no repetir métodos o saber que es lo que tengo y en segundo lugar cuando el número de métodos es grande, es conveniente haber ordenado y agrupado nuestro código. A continuación expongo los grupos y método a método.

  • Solicitudes desde teclado. En una aplicación de consola es normal solicitar datos desde teclado ya que no tenemos controles TextBox, ComboBox ni nada por el estilo, por tanto tenemos que ingeniarnos un método fácil y práctico para solicitar información desde el teclado y pasarla a la aplicación. En este caso, he creado varios métodos estáticos y sus sobrecargas para obtener texto, obtener números, textos o fechas.
    • Lectura de texto. En este caso, existen dos métodos de lectura de texto, uno sobrecargado para leer texto y otro para leer varias veces el texto y devolver un array.readText es un método al cual se le pasan dos parámetros, el primero es el texto que se muestra por pantalla al solicitar el texto y el segundo parámetro, un valor booleano que permite si el valor que se captura desde el teclado puede ser un elemento vacío. El método crea un objeto BufferedReader, muestra por pantalla el textoy entra en un bucle do... while hasta obtener el texto. Una vez capturado desde el teclado lo devuelve. readText tiene otro método sobrecargado con un solo parámetro enviando el parámetro allowEmpty con un false. Por último el método readArrayText solicita mediante el método readText tantas cadenas de texto como necesite el usuario devolviendo una array de cadena.( Lo he limitado a 8 cadenas como podía haberlo hecho a 100). Un ejemplo. String stringFromKeyboard = ConsoleUtilities.readText("Introduzca el nombre del usuario...");, solicitando por pantalla el texto del primer parámetro y asignando e la variable stringFromKeyboard el valor obtenido desde el teclado.
          /**
          * Devuelve un valor desde el teclado
          * @param textIn Texto que se visualiza en la consola
          * @return Texto procedente del teclado
          * @throws IOException
          */
          public static String readText(String textIn, boolean allowEmpty) throws IOException{
      
              // Declaración de variables
              BufferedReader keyboardIn=new BufferedReader(new InputStreamReader(System.in));
              String textOut=null;
      
              // Imprimir texto de consola
              System.out.println(textIn);
      
              // Solicitar datos desde teclado
      
              do {
                  textOut=keyboardIn.readLine();
              } while (ConsoleTools.isNullOrEmpty(textOut) &amp;&amp; !allowEmpty);
      
              // Retornar texto
              return textOut;
          }
      
          /**
          * Devuelve un valor desde el teclado
          * @param textIn Texto que se visualiza en la consola
          * @return Texto procedente del teclado
          * @throws IOException
          */
          public static String readText(String textIn) throws IOException{
              return ConsoleTools.readText(textIn, false);
          }
      
          /**
          * Devuelve un array de textos. Limitado a 8 textos
          * @param textIn Texto que se visualiza en la consola
          * @return Array de texto
          */
          public static String[] readArrayText(String textIn) throws IOException{
              int n=0;
              int indexMax=8;
      
              String[] texts=new  String[indexMax];
      
              do {
                  texts[n]=ConsoleTools.readText(textIn);
                  n++;
              } while (ConsoleTools.confirmationMessage("¿Desea introducir más textos?") || n==indexMax);
      
              return texts;
      
          }
      
    • Lectura de números. Para obtener números desde teclado y asegurarnos que estos son lo que son y del tipo que son, uso un método sobrecargado para leer números enteros, otro para enteros positivos y por último otro sobrecargado para leer números con precisión doble. En todos ellos, se validan los datos comprobando que son del tipo que se solicita y que realmente son números y no otro objeto.
      Existe un método sobrecargado para lectura de números enteros, uno que tiene dos parámetros readInteger(String textIn, int defaultValue)pasando una cadena que se visualizará por pantalla y otro que pasará un valor por defecto en caso de no introducir ningún dato y otro método readInteger(String textIn)que solo pasa la cadena. Otro método para enteros es readPositiveInteger(String textIn) el cual además ed validar que es numérico y entero, comprueba que es positivo.
      Para lectura de números con precisión doble, disponemos de dos métodos sobrecargados igualmente que para números enteros.

          /**
          * Devuelve un valor entero desde el teclado
          * @param textIn Texto que se visualiza en la consola
          * @return Número procedente desde el teclado
          */
          public static int readInteger(String textIn) throws IOException{
              // Declaración de variables
              BufferedReader keyboardIn=new BufferedReader(new InputStreamReader(System.in));
              int numberOut=0;
              String numberOutToString=null;
              boolean validate=false;
      
              // Imprimir texto de consola
              System.out.println(textIn );
      
              // Solicitar datos desde teclado
              do {
                  numberOutToString=keyboardIn.readLine();
                  validate=ConsoleTools.isInteger(numberOutToString);
                  // Mensaje de Validación errónea
                  if (!validate) {
                      System.out.println("No es un valor válido. Introduzca un número entero.");
                  }
              } while (!validate);
      
              numberOut=Integer.parseInt(numberOutToString);
              // Retornar número
              return numberOut;
          }
      
          public static int readInteger(String textIn, int defaultValue) throws IOException{
              // Declaración de variables
              BufferedReader keyboardIn=new BufferedReader(new InputStreamReader(System.in));
              int numberOut=0;
              String numberOutToString=null;
              boolean validate=false;
      
              // Imprimir texto de consola
              System.out.println(textIn );
      
              // Solicitar datos desde teclado
              do {
                  numberOutToString=keyboardIn.readLine();
                  validate=ConsoleTools.isInteger(numberOutToString);
      
                  if (!validate) {
                      numberOutToString=String.valueOf(defaultValue);
                      validate=true;
                  }
              } while (!validate);
      
              numberOut=Integer.parseInt(numberOutToString);
              // Retornar número
              return numberOut;
          }
      
          /**
          * Devuelve un valor entero y positivo desde el teclado
          * @param textIn Texto que se visualiza en la consola
          * @return Número procedente desde el teclado
          */
          public static int readPositiveInteger(String textIn) throws IOException{
              // Declaración de variables
              BufferedReader keyboardIn=new BufferedReader(new InputStreamReader(System.in));
              int numberOut=0;
              String numberOutToString=null;
              boolean validate=false;
      
              // Imprimir texto de consola
              System.out.println(textIn );
      
              // Solicitar datos desde teclado
              do {
                  numberOutToString=keyboardIn.readLine();
                  validate=ConsoleTools.isPositiveInteger(numberOutToString);
                  // Mensaje de Validación errónea
                  if (!validate) {
                      System.out.println("No es un valor válido. Introduzca un número entero.");
                  }
              } while (!validate);
      
              numberOut=Integer.parseInt(numberOutToString);
              // Retornar número
              return numberOut;
          }
      
          /**
           * Devuelve un valor entero desde el teclado
           * @param textIn Texto que se visualiza en la consola
           * @return Número procedente desde el teclado
           * @throws java.io.IOException
           */
          public static double readDouble(String textIn) throws IOException{
              // Declaración de variables
              BufferedReader keyboardIn=new BufferedReader(new InputStreamReader(System.in));
              double numberOut=0;
              String numberOutToString=null;
              boolean validate=false;
      
              // Imprimir texto de consola
              System.out.println(textIn );
      
              // Solicitar datos desde teclado
              do {
                  numberOutToString=keyboardIn.readLine();
                  validate=ConsoleTools.isNumeric(numberOutToString);
                  // Mensaje de Validación errónea
                  if (!validate) {
                      System.out.println("No es un valor válido. Introduzca un número con doble precisión.");
                  }
              } while (!validate);
      
              numberOut=Double.parseDouble(numberOutToString);
              // Retornar número
              return numberOut;
          }
      
          /**
           * Devuelve un valor entero desde el teclado
           * @param textIn Texto que se visualiza en la consola
           * @param defaultValue Valor por defecto que se asigna en caso de no validez de entrada
           * @return Número procedente desde el teclado o valor por defecto
           * @throws IOException
           */
          public static double readDouble(String textIn, double defaultValue) throws IOException{
              // Declaración de variables
              BufferedReader keyboardIn=new BufferedReader(new InputStreamReader(System.in));
              double numberOut=0;
              String numberOutToString=null;
              boolean validate=false;
      
              // Imprimir texto de consola
              System.out.println(textIn );
              // Solicitar datos desde teclado
              do {
                  numberOutToString=keyboardIn.readLine();
                  validate=ConsoleTools.isNumeric(numberOutToString);
                  // Mensaje de Validación errónea
                  if (!validate) {
                      numberOutToString=String.valueOf(defaultValue);
                      validate=true;
                  }
              } while (!validate);
      
              numberOut=Double.parseDouble(numberOutToString);
      
              // Retornar número
              return numberOut;
          }
      
    • Lectura de fechas. Para leer fechas, uso un solo método readDate(String textIn) que comprueba que es una fecha y que contiene el formato correcto (dd/MM/yyyy).
      public static Date readDate(String textIn) throws IOException, ParseException{
              // Declaración de variables
              BufferedReader keyboardIn=new BufferedReader(new InputStreamReader(System.in));
              SimpleDateFormat df = new SimpleDateFormat("dd/MM/yyyy");
              boolean exit=false;
              Date dateTemp=new Date();
              String date;
              // Imprimir texto de consola
              System.out.println(textIn);
      
              // Solicitar datos desde teclado
      
              do {
                  date = keyboardIn.readLine();
                  try{
                      dateTemp = df.parse(date);
      
                  } catch (Exception ex){
                  }
      
                  if (!df.format(dateTemp).equals(date)){
                      if (ConsoleTools.confirmationMessage("El formato es inválido, ¿desea introducir una nueva fecha?. " +
                              "En caso contrario, se insertará la fecha actual")) {
                      }
                      else{
                          dateTemp=new Date();
                          exit=true;
                      }
                  } else {
                      exit=true;
                  }
      
              } while (!exit);
      
              // Retornar fecha
              return dateTemp;
          }
      
  • Validación. En este grupo se incluyen métodos de validación que devuelven un valor booleano con true si se cumple la validación o false en caso contrario, como por ejemplo si el valor de una cadena es vacía o nulaboolean isNullOrEmpty(String textIn), si un número es positivo entero boolean isPositiveInteger(String numberIn), si es número entero boolean isInteger(String numberIn), si es un valor númerico doble boolean isInteger(String numberIn) o comprobando si es una fecha es válida boolean isDate(String date).
        /**
         * Comprueba si un texto es vacio o nulo
         * @param textIn Texto de entrada
         * @return True. Si es vació o nulo. False. Si contiene texto
         */
        public static boolean isNullOrEmpty(String textIn){
    
            return textIn=="" || textIn.isEmpty() || textIn==null?true:false;
    
        }
    
        /**
         * Comprueba si un número es entero y positivo
         * @param numberIn. Número de entrada
         * @return
         * True. Si es positivo y entero.
         * False. No es positivo o no es un número entero
         */
        public static boolean isPositiveInteger(String numberIn){
    
            int numberOut=0;
            // Primera validación convirtiendo el número a entero
            try {
                numberOut=Integer.parseInt(numberIn);
            } catch (Exception ex) {
                return false;
            }
            // Segunda validación comprobando que es positivo
            return numberOut&gt;0;
        }
    
        /**
         * Comprueba si un número es entero
         * @param numberIn. Número de entrada
         * @return
         * True. Si es positivo y entero.
         * False. No es positivo o no es un número entero
         */
        public static boolean isInteger(String numberIn){
    
            int numberOut=0;
            // Primera validación convirtiendo el número a entero
            try {
                numberOut=Integer.parseInt(numberIn);
            } catch (Exception ex) {
                return false;
            }
    
            return true;
        }
    
        /**
         * Comprueba si la cadena es un número
         * @param numberIn. Número de entrada
         * @return
         * True. Si es un número.
         * False. No es un número
         */
        public static boolean isNumeric(String numberIn){
    
            double numberOut=0;
            // Primera validación convirtiendo el número a double
            try {
                numberOut=Double.parseDouble(numberIn);
            } catch (Exception ex) {
                return false;
            }
    
            return true;
        }
    
        /**
         * Comprueba si la cadena es una fecha válida
         * @param date Fecha de compración
         * @return
         * True. Si es una fecha.
         * False. No es una fecha
         */
        public static boolean isDate(String date){
            try{
                SimpleDateFormat df = new SimpleDateFormat("dd/MM/yyyy");
                Date dateTemp=new Date();
                dateTemp = df.parse(date);
                return true;
                } catch (Exception ex){
                    return false;
                }
        }
    
  • Formatos y conversiones. En este grupo, se incluyen métodos que permiten mostrar los resultados de nuestras aplicaciones de un modo más legible por los que podemos disponer de un método que convierte un texto o número a un texto con formato de la moneda local String toCurrency(String number), un método que convierte a número con formato pudiendo especificar un número de dígitos String toNumber(double number) o String toNumber(double number, int digits), métodos para rellenar por la izquierda o por la derecha de un caracter String padRight(String text, int number) y String padLeft(String text, int number), un método que retorna una cadena un número de veces String dumpChar(String string, int lenght), un método que añade un borde sobre un texto String addBorder( String text, int longLine), método que convierte una fecha en formato corto String toShortDate(Date date), o convertir un tiempo en segundos en una cadena expresada en segundos, minutos, horas, días, meses y años String convertToTime(double tiempoSegundos).
    /**
         * Convierte un número a texto con formato de moneda.
         * Separador de miles, dos decimales y moneda
         * @param number Número a convertir
         * @return Cadena con formato de moneda
         */
        public static String toCurrency(String number){
            return ConsoleTools.toCurrency(Integer.parseInt(number));
        }
    
        /**
         * Convierte un número a texto con formato de moneda.
         * Separador de miles, dos decimales y moneda
         * @param number Número a convertir
         * @return Cadena con formato de moneda
         */
        public static String toCurrency(int number){
            NumberFormat fn=NumberFormat.getCurrencyInstance();
            return fn.format(number);
        }
    
        /**
         * Convierte un número a texto con formato de moneda.
         * Separador de miles, dos decimales y moneda
         * @param number Número a convertir
         * @return Cadena con formato de moneda
         */
        public static String toCurrency(double number){
            NumberFormat fn=NumberFormat.getCurrencyInstance();
            return fn.format(number);
        }
    
        /**
         * Convierte un número a texto con formato
         * @param number Número a convertir
         * @return Cadena con formato
         */
        public static String toNumber(double number){
            NumberFormat fn=NumberFormat.getNumberInstance();
            return fn.format(number);
        }
    
        /**
         * Convierte un número a texto con formato de número de decimales específico
         * @param number Número a convertir
         * @param digits Número de decimales
         * @return Cadena con formato
         */
        public static String toNumber(double number, int digits){
            NumberFormat fn=NumberFormat.getNumberInstance();
            fn.setMaximumFractionDigits(digits);
            return fn.format(number);
        }
        /**
         * Rellena por la derecha n espacios y
         * recorta el texto cuando es mayor añadiéndole
         * puntos suspensivos
         * @param text Texto formateado
         * @param number Número de espacios
         * @return Cadena de texto formateada
         */
        public static String padRight(String text, int number){
            if (text.length()&gt;number) {
                text=text.substring(0, number-3) + "...";
            }
            return String.format("%1$-" + number + "s", text);
        }
    
        /**
         * Rellena por la izquierda n espacios y
         * recorta el texto cuando es mayor añadiéndole
         * puntos suspensivos
         * @param text Texto formateado
         * @param number Número de espacios
         * @return Cadena de texto formateada
         */
        public static String padLeft(String text, int number){
            if (text.length()&gt;number) {
                text=text.substring(0, number-3) + "...";
            }
            return String.format("%1$" + number + "s", text);
        }
    
        /**
         * Retorna un caracter repetido un número de veces
         * @param string Cadena de texto
         * @param lenght Número de veces que se repite
         * @return Cadena de texto
         */
        public static String dumpChar(String string, int lenght){
            String textOut="";
    
            for (int i = 0; i &lt; lenght; i++) {
                textOut +=string;
            }
            return textOut;
        }
    
         /**
         * Devuelve un borde de texto superior e inferior
         * @param text Texto al que se le añade el borde
         * @param longLine Longitud del borde
         * @return Cadena de texto formateada con borde
         */
        public static String addBorder( String text, int longLine){
            String border = &quot;&quot;;
            border += &quot;+&quot;;
            for (int i = 0; i &lt; longLine; i++) {
                border += &quot;-&quot;;
            }
            border += &quot;+\n&quot;;
    
             return border + text + border;
        }
    
        /**
         * Convierte una fecha en formato corto
         * @param date Fecha a convertir
         * @return Cadena de texto con la fecha
         */
        public static String toShortDate(Date date){
            SimpleDateFormat df=new SimpleDateFormat(&quot;dd/MM/yyyy&quot;);
    
            return df.format(date);
        }
    
         /**
         * Convierte un tiempo en segundos en sg, min, horas, días, meses y años
         * @param tiempoSegundos Tiempo en segundos
         * @return Texto
         */
        public static String convertToTime(double tiempoSegundos){
    
            double segundos =  Math.floor(tiempoSegundos % 60);
            double aux1 =((tiempoSegundos-segundos) / 60);
            double minutos = Math.floor(aux1 % 60);
            aux1 = (aux1-minutos) / (60);
            double horas = Math.floor(aux1 % 24);
            aux1 = (aux1-horas) / 24;
            double dias = Math.floor(aux1 % 30);
            aux1 = (aux1-dias) / 30;
            double meses = Math.floor(aux1 % 12);
            aux1 = (aux1 - meses)/12;
            double años = Math.floor(aux1);
    
            String sAños = años &gt; 0? (años==1?&quot;1 año&quot;:(int)años + &quot; años &quot;):&quot;&quot;;
            String sMes = meses &gt; 0? (meses==1?&quot;1 mes&quot;:(int)meses + &quot; meses &quot;):&quot;&quot;;
            String sDias = dias &gt; 0? (dias==1?&quot;1 día&quot;:(int)dias + &quot; días &quot;):&quot;&quot;;
            String sHoras = horas &gt; 0? (horas==1?&quot;1 hora&quot;:(int)horas + &quot; horas &quot;):&quot;&quot;;
            String sMinutos = minutos &gt; 0? (minutos==1?&quot;1 minuto&quot;:(int)minutos + &quot; minutos &quot;):&quot;&quot;;
            String sSegundos = segundos &gt; 0? (segundos==1?&quot;1 segundo&quot;:(int)segundos + &quot; segundos &quot;):&quot;&quot;;
    
            return sAños + sMes + sDias + sHoras + sMinutos + sSegundos;
        }
    

Como podéis apreciar, pequeños métodos que nos permiten crear aplicaciones mucho más rápido.
En la próxima entrega, algunas utilidades de consola más como un método que crea menús automáticamente.

Un saludo

Calculadora de números complejos en Java

Calculadora de números complejos en Java

En este caso, voy a detallar una clase sencilla que implementa una calculadora de números complejos en Java.

Para los que no lo recuerden, un número complejo consta de dos partes, una parte real y una parte imaginaria; la parte imaginaria se compone del número √-1 o lo que es lo mismo i. Un número complejo lo podemos representar vectorialmente como z = (a,b), binomial z = a + bi, polar mediante un módulo y un ángulo z = r α, trigonométrica z = r (cos α + i sin α) y por último la forma exponencial y que no voy a incluir en la clase aunque a mi parecer es la más elegante e = cos α + i sin α.

Cada forma tiene su fin, por ejemplo la suma y las resta son más fáciles con la forma binomial mientras que la multiplicación, división y exponenciación lo son con la forma polar; en el caso de la clase todos los cálculos los he realizado con la forma binomial.

Si pasamos a la clase, como ya he comentado está implementada en Java y consta de cuatro propiedades, parte real mediante x, parte imaginaria mediante y, el módulo que es calculado en base de x e y, y por último el argumento o ángulo.A continuación paso el diagrama de la clase donde se exponen los métodos con la mayoría de las operaciones:

Pasando un ejemplo

// Pasamos dos números complejos
        // Efectuamos los cálculos
        Complex c1=new Complex(4,5);
        Complex c2=new Complex(7,6);

        System.out.println("(" + c1.toString() + ") + (" + c2.toString() + ") = " + c1.addComplex(c2));
        System.out.println("(" +c1.toString() + ") - (" + c2.toString() + ") = " + c1.substractComplex(c2));
        System.out.println("(" +c1.toString() + ") x (" + c2.toString() + ") = " + c1.multiplyComplex(c2));
        System.out.println("(" +c1.toString() + ") / (" + c2.toString() + ") = " + c1.divideComplex(c2));
        System.out.println("Opuesto de " + c1.toString() + " = " + c1.opposite().toString());
        System.out.println("Inverso  de " + c1.toString() + " = " + c1.reverse().toString());
        System.out.println(c1.toString() + " = " + c1.polarToString());
        System.out.println(c1.toString() + " = " + c1.polarToString(true));
        System.out.println(c2.toString() + " = " + c2.polarToString());
        System.out.println(c2.toString() + " = " + c2.polarToString(true));
        c2.convertToBinomial(5, 36.86*Math.PI/180);
        System.out.println("Convertir 5) 36.86º a binomial = " + c2.toString());<span id="mce_SELREST_start" style="overflow:hidden;line-height:0;"></span>

Resultado

(4 + 5i) + (7 + 6i) = 11 + 11i
(4 + 5i) - (7 + 6i) = -3 -1i
(4 + 5i) x (7 + 6i) = -2 + 59i
(4 + 5i) / (7 + 6i) = 0,68 + 0,13i
Opuesto de 4 + 5i = -4 -5i
Inverso  de 4 + 5i = 0,1 -0,12i
4 + 5i = 5,7)0,896 radians
4 + 5i = 5,7)51,3º
7 + 6i = 9,9)0,709 radians
7 + 6i = 9,9)40,6º
Convertir 5) 36.86º a binomial = 4 -3i

A continuación paso el código:

/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package complex;

/******************************************************
 *  @author:    Joaquín Martínez Rus (c) 2016
 *  @version:   1.0 
 *  File:       Estrella.java
 *  Created:    17/07/2016
 *  Project:    Calculadora de números complejos
 *  Comments:   Clase Complex.
 *******************************************************/
public class Complex {
    
    /**
     * Inicia una nueva instancia de la clase Complex con valor z = 0 + 0i;
     */
    public Complex(){
        this(0,0);
    }
    
    /**
     * Inicia una nueva instancia de la clase Complex
     * @param _x Parte real
     * @param _y Parte imaginaria
     */
    public Complex(double _x, double _y){
        this.x=_x;
        this.y=_y;
        this.setModule();
        this.setGrades();
    }
    
    double x;
    double y;
    double module;
    double grades;

    public double getX() {
        return x;
    }

    public void setX(double x) {
        this.x = x;
    }

    public double getY() {
        return y;
    }

    public void setY(double y) {
        this.y = y;
    }

    public double getModule() {
        
        return module;
    }

    /**
     * Asigna y calcula el valor del módulo
     */
    public void setModule(){
        this.module=Math.sqrt(Math.pow(x, 2)+ Math.pow(x, 2));
    }
    
    /**
     * Asigna el valor del módulo
     */
    public void setModule(double module) {
        this.module = module;
    }

    public double getGrades() {
        return grades;
    }

    public void setGrades() {
        this.grades = Math.atan2(y, x);
    }
    
    public void setGrades(double grades) {
        this.grades = grades;
    }
    
    /**
     * Suma al número complejo otro número complejo
     * @param _complex Número complejo de la suma
     * @return Objeto Complex
     */
    public Complex addComplex(Complex _complex){
        return new Complex(this.x + _complex.x, this.y + _complex.y);
    }
    
    /**
     * Resta al número complejo otro número complejo
     * @param _complex Número complejo de la resta
     * @return Objeto Complex
     */
    public Complex substractComplex(Complex _complex){
        return new Complex(this.x - _complex.x, this.y - _complex.y);
    }
    
    /**
     * Multiplica al número complejo otro número complejo
     * @param _complex Número complejo de la multiplicación
     * @return Objeto Complex
     */
    public Complex multiplyComplex(Complex _complex){
        return new Complex((this.x * _complex.x - this.y * _complex.y), 
                (this.x * _complex.y + this.y * _complex.x));
    }
    
    /**
     * Divide el número complejo actual entre el valor del parámetro
     * @param _complex Denominador
     * @return Objeto Complex
     */
    public Complex divideComplex(Complex _complex){
        return divideComplex(this,_complex);
    }
    
    /**
     * Divide dos números complejos
     * @param _complex1 Numerador
     * @param _complex2 Denominado
     * @return Objeto Complex
     */
    public Complex divideComplex(Complex _complex1, Complex _complex2){
        double xx = (_complex1.x * _complex2.x + _complex1.y * _complex2.y)/(Math.pow(_complex2.x,2)+Math.pow(_complex2.y,2));
        double yy = (_complex1.y * _complex2.x - _complex1.x * _complex2.y)/(Math.pow(_complex2.x,2)+Math.pow(_complex2.y,2));
        return new Complex(xx, yy);
    }
    
    /**
     * Obtiene un número complejo en forma vectorial
     * @return Cadena de texto
     */
    public String vectorialtoString(){
        return "(" + ConsoleTools.toNumber(this.x,2) + ", " + ConsoleTools.toNumber(this.y,2) + ")";
    }
    
    /**
     * Obtiene un número complejo en forma binomial
     * @return Cadena de texto
     */
    @Override
    public String toString(){
        return ConsoleTools.toNumber(this.x,2) + (this.y < 0? "": " + ") + ConsoleTools.toNumber(this.y,2) + "i";
    }
    
    /**
     * Obtiene un número complejo en forma polar
     * @return Cadena de texto
     */
    public String polarToString(){
        return  polarToString(false);
    }
    
    /**
     * Obtiene un número complejo en forma polar
     * @param isDegrees Mostrar como grados centigrados o radianes
     * @return Cadena de texto
     */
    public String polarToString(boolean isDegrees){
        double angle=isDegrees?this.getGrades()*180/Math.PI:this.getGrades();
        String angleToString = (isDegrees?ConsoleTools.toNumber(angle,1):ConsoleTools.toNumber(angle,3)) + (isDegrees? "º": " radians");
        return  ConsoleTools.toNumber(this.module,1) + ")" + angleToString;
    }
    
    /**
     * Obtiene el conjugado de un número complejo
     * @param complex Número complejo
     * @return Objeto Complex
     */
    public Complex conjugate(Complex complex){
        return new Complex(complex.x, - complex.y);
    }
    
    /**
     * Calcula el opuesto de un número complejo
     * @return Objeto Complex
     */
    public Complex opposite(){
        return opposite(this);
    }
    
    /**
     * Calcula el opuesto de un número complejo
     * @param _complex Número complejo
     * @return Objeto Complex
     */
    public Complex opposite(Complex _complex){
        return new Complex(-_complex.x, - _complex.y);
    }
    /**
     * Obtiene el conjugado de un número complejo
     * @return Objeto Complex
     */
    public Complex conjugate(){
        return conjugate(this);
    }
    
    /**
     * Obtiene el inverso de un número complejo
     * @return Objeto Complex
     */
    public Complex reverse(){
        return reverse(this);
    }
    
    /**
     * Obtiene el inverso de un número complejo
     * @param complex Número complejo del cálculo
     * @return Objeto Complex
     */
    public Complex reverse(Complex complex){
        double denominador=Math.pow(complex.x, 2) + Math.pow(complex.y, 2);  
        return new Complex( complex.x/denominador, - complex.y/denominador);
    }
    
    /**
     * Convierte un número complejo de forma polar a binomial
     * @param module Modulo del número complejo
     * @param argument Argumento en radianes del número complejo
     * @return Objeto Complex
     */
    public Complex convertPolarToBinomial(double module, double argument){
        double _x = Math.abs(module) * Math.cos(argument);
        double _y = Math.abs(module) * Math.sin(argument);
        return new Complex(_x,-_y);
    } 
    
    /**
     * Asigna los valores real e imaginario en base al módulo y el argumento
     * @param module Modulo del número complejo
     * @param argument Argumento en radianes del número complejo
     */
    public void convertToBinomial(double module, double argument){
        Complex _complex=convertPolarToBinomial(module, argument);
        this.x=_complex.x;
        this.y=_complex.y;
    }
    
    /**
     * Comprueba dos números complejos
     * @param _complex Número complejo a comparar
     * @return Objeto Complex
     */
    public boolean equals(Complex _complex){
        if (_complex==null) {
            return false;
        }
        if (this.getClass()!=_complex.getClass()) {
            return false;
        }
        
        return this.x ==_complex.x && this.y == _complex.y;
    }
}


Consejos.

  • La clase debe estar bien definida. Constructores, propiedades y métodos
  • Antes de escribir código, genera un diagrama de clases como mínimo (esto implica pensar que vas a hacer), además una buena estructura puede hacer nuestro código más robusto.
  • Piensa en las cuatro características de la Programación Orientada a Objetos, Abstracción, Encapsulamiento, Herencia y Polimorfismo (hay alguna más, pero estas son las principales y debes tenerlas muy claras)
  • Los métodos deben contener el código justo. Un método con mucho código no hace la clase legible y lo vuelve débil.
  • Las clases deben funcionar en cualquier medio. Si usara esta clase en modo gráfico con ventanas en vez de modo consola, debería de funcionar del igual modo, solo debo llamar al método de operación y obtener su resultado mediante los métodos apropiados.
  • Usa los comentarios, tanto dentro de los métodos como en su documentación. Por ejemplo, si llamo al método divideComplex de la clase Complex, cuando estoy escribiendo el método, aparecerá el método y el texto que nosotros escribimos, en este caso yo escribí «Divide el número complejo actual entre el valor del parámetro» junto con los parámetros y el valor retornado. Cuando las clases se hacen muy grandes y complejas, es necesario documentarlas todo lo que se pueda.
  • Además de los comentarios, a mi me gusta agrupar el código por regiones con Java uso
    // 

    /**
     * Devuelve un valor desde el teclado
     * @param textIn Texto que se visualiza en la consola
     * @return Texto procedente del teclado
     * @throws IOException
     */
    public static String readText(String textIn, boolean allowEmpty) throws IOException{

        // Declaración de variables
        BufferedReader keyboardIn=new BufferedReader(new InputStreamReader(System.in));
        String textOut=null;

        // Imprimir texto de consola
        System.out.println(textIn);

        // Solicitar datos desde teclado

        do {
            textOut=keyboardIn.readLine();
        } while (ConsoleUtilities.isNullOrEmpty(textOut) && !allowEmpty);

        // Retornar texto
        return textOut;
    }

    // 

Esto me va permitir expandir o contraer todo el código contenido entre las etiquetas o para el caso de C#

#region Private Methods
      // Aquí iría el código en C#
#endregion

o Visual Basic

#Región MiRegion
     // Aquí iría el código en Visual Basic
#End Region
  • Cíñete a la nomenclatura estándar con cada lenguaje de programación, por ejemplo Java o C# o Visual Basic.
  • Créate un clase con herramientas, por ejemplo, para aplicaciones del tipo consola en Java, tengo una clase con métodos estáticos donde implemento herramientas que puedo usar en este medio como un método que genera un menú automáticamente, un lector desde teclado de texto o números, un formateador de texto, un serializador, etc. Para C# tengo otro tipo de clases donde extiendo funcionalidades a las clases creadas, en fin, código que reutilizaré más a menudo de lo que me pienso.
  • Si hacemos todo esto, en un futuro nos será más fácil, modificar, entender que hicimos o ampliar nuestras clases.
Y esto es todo por hoy. Saludos!

Sistema Planetario en Java

Sistema Planetario en Java

Para practicar POO, se me ha ocurrido crear un sistema planetario, el cual contiene unos objetos que a su vez orbitan alrededor de otro objeto con mayor gravedad y así poder calcular datos entre ellos, como pueda ser la fuerza de gravitación, distancias, tiempo de llegada, etc.

Este sistema es muy precario, porque no se tienen en cuenta las órbitas, si no que se entiende que todos los objetos está alineados y la distancia que los separa, es la distancia media de esas órbitas.

Con esta aplicación, existe una superclase abstracta ObjetoSistema que contiene la mayoría de los atributos y métodos, luego hereda de esta otra clase llamada ObjetoSinLuz al cual se le pueden añadir datos de su atmósfera mediante un ArrayList y de esta clase.

Los objetos reales del sistema, Estrella, hereda de la superclase; los planetas y satélites de la clase heredan de la clase ObjetoSinLuz para poder en un momento dado añadirles datos de su atmósfera.

Cada objeto tiene como atributo un objeto tipo ObjetoSistema que es el objeto de referencia sobre el cual orbita, de modo que la distancia media del objeto será a la de su objeto de referencia.

Para calcular la distancia por ejemplo entre la Luna y Marte, se calcularía:

  • La distancia entre la Luna y la Tierra que es su objeto de referencia
  • Distancia desde la Tierra al Sol y la suma de estas dos, la distancia de la luna al sol
  • Por otra parte calcularía la distancia  de Marte al Sol y la diferencia de ambas distancias, nos daría el resultado esperado.

El cálculo de las distancias se calcula recursivamente para que funcione en cualquier nivel, por ejemplo si la Luna a su vez fuera un objeto referencia de otros satélites o asteroides.

Otra particularidad que he incluido, es la posibilidad de obtener listados por diferentes órdenes, como pueda ser el caso de ordenar por la masa, tamaño, días de traslación, etc., de modo que el método compareTo de cada clase, debe tener previsto con que atributo ordenar.

Su funcionamiento es muy sencillo mediante menú textual, (lo que se trata es aprender)

**********************************************
Sistema Solar
**********************************************
* (1) Calcular distancia entre dos objetos   *
* (2) Calcular atracción gravitacional       *
* (3) Calcular tiempo (línea recta)          *
* (4) Listar objetos                         *
* (5) Salir                                  *
**********************************************

Introduzca la opción…
3
Introduzca el nombre del primer objeto…
tierra
Introduzca el nombre del segundo objeto…
marte
Introduzca la velocidad km/h (Si pulsa INTRO, se asignará 28000 km/h…
50000
Invertirá para recorrer 78.338.770 Km a una velocidad de 50.000 Km/h, 2 meses 5 días 6 horas 46 minutos 31 segundos  días
Pulse INTRO para continuar…

Tiempo empleado por la luz en llegar desde el Sol a Júpiter.(300.000km/s=1.080.000.000 km/h)

Invertirá para recorrer 778.412.010 Km a una velocidad de 1.080.000.000 Km/h, 43 minutos 14 segundos  días

o el mismo caso para el Sol y la Tierra

Invertirá para recorrer 149.597.870 Km a una velocidad de 1.080.000.000 Km/h, 8 minutos 18 segundos  días

Como caso particular, el código para calcular la distancia, que mediante un método recursivo, hasta que no encuentra el objeto de referencia principal, va calculando la distancia entre cualquier objeto y este.

/**
 * Funcion recursiva para calcular la distancia hasta el objeto de referencia del sistema planetario
 * @param objetoSistemaInicial Objeto inicial desde donde se calcula la distancia
 * @param objetoSistema Objeto hasta donde se calculará la distancia
 * @return Distancia entre los dos objetos
 */
private double calcularDistanciaAObjetoReferencia(ObjetoSistema objetoSistemaInicial){
        double distancia = objetoSistemaInicial.distanciaMedia;
        if (!objetoSistemaInicial.objetoReferencia.objetoReferencia.equals(objetoSistemaInicial.objetoReferencia)) {
            distancia += calcularDistanciaAObjetoReferencia(objetoSistemaInicial.objetoReferencia);        }
        return distancia;
    }





Os dejo el diagrama de clases y el
código (proyecto de NetBeans) para practicar y toquetear.

Un saludo y a programar.

Pd: No está terminada, ya que le falta añadir otros elementos para añadir componentes, atmósferas, etc., pero creo que se entiende su objetivo que es el de aprender a programar.

Le he incluido el sol, los planetas y dos satélites, la Luna y Phobos, meted más!

Espiral de Ulam

Espiral de Ulam

 

Un día durante una conferencia aburrida, un matemático llamado Stalisnaw Ulam jugando como no con los números, comenzó a dibujar en un papel el número 1 y girando alrededor de él en el sentido contrario a las agujas del reloj, siguió escribiendo el resto de números naturales consecutivamente formando así la espiral que lleva su nombre creando un cuadrado de n x n. Esta espiral tiene una particularidad y es que los números primos tienden a alinearse en diagonales. He aquí un ejemplo de como se forma, una espiral de 7 x 7 y una muestra mayor con los números primos remarcados:

37 36 35 34 33 32 31
38 17 16 15 14 13 30
39 18 5 4 3 12 29
40 19 6 1 2 11 28
41 20 7 8 9 10 27
42 21 22 23 24 25 26
43 44 45 46 47 48 49

Espiral de Ulam de orden 7

Espiral de Ulam con 142000 iteraciones

Por otra parte desde el punto de vista algorítmico, formar una espiral mediante código es otro reto, se puede hacer de múltiples formas, pero en concreto se me ha ocurrido crear un algoritmo de creación de la espiral y poder así implementar una ordenación con java implementado la interfaz Comparator y Comparable.

Para crear la espiral, lo primero determino la posición inicial del 1 dentro del cuadrado antes de formarlo. Esto es fácil, obtenemos el cuadrado del mayor impar y el valor de la mitad de los lados correspondería a las coordenadas x,y.

El paso siguiente sería rotar desde el 1 en el sentido de las agujas del reloj, formando cuadrados de n x n almacenando su valor en una clase que guardará las coordenadas y el valor de cada posición; este objeto, lo salvaremos en un ArrayList.

Como las coordenadas se van agregando en este orden en el ArrayList , una vez creada la espiral tendremos que ordenar las coordenadas para visualizarla en pantalla.

¿Qué clases usaremos?

Una clase Coordinate que almacena tres atributos, los valores de x, y y el valor de la coordenada. Implementa interface Comparable, por tanto debe contener el método compareTo .

Una clase que implementa la interfaz Comparator, por tanto debe implementar obligatoriamente el método compare y dentro de este llamaría al método compareTo de la clase Coordinate.

Diagrama de clases

¿Cómo funciona el algoritmo?

Del siguiente modo:

  • Iteramos n veces como cuadrados alrededor del 1 tengamos que dibujar
  • Iteramos 4 veces por cada uno de estos cuadrados, ya que un cuadrado tiene 4 lados y lo que haremos es decirle con esta iteración es la siguiente coordenada de destino donde tiene que asignarle un valor
  • Iteramos n veces por cada lado para escribir n números

Y una vez creada la espiral, ¿como ordenamos el ArrayList?

Una vez creada la espiral, debemos leer el ArrayList y visualizarlo en pantalla, pero antes hay que ordenarlo de modo que la primera coordenada sea la (0,0), la segunda (0,1), las siguientes (0,2)….(0,6), (1,0), (1,1)….(1,6),(2,0),…. hasta la (6,6) y para ello dentro del método compareTode la clase Coordinate.

Una vez ordenada el ArrayList, pasamos a imprimirla en pantalla con el método toString() iterando por la colección con un for con la salvedad de pasar a una nueva línea cada vez que se alcance el valor de n para formar el cuadrado.

Para otro día, un generador de espiral de Ulam con los números primos resaltados.

Aquí os dejo el código fuente de Java.

Un saludo

«Una ecuación no tiene para mí ningún significado a menos que exprese un pensamiento de Dios»

Srinivasa Ramanujan

package espiralulamproject;

/******************************************************
 *  @author:    Joaquín Martínez Rus (c) 2016
 *  @version:   1.0
 *  File:       Coordinate.java
 *  Created:    19/05/2016
 *  Project:    Ulam Spiral.
 *  Comments:   Clase Coordinate. Clase con coordenadas y valor asignados
 *
 *******************************************************/
public class Coordinate implements Comparable{

    // 

    /**
     * Inicia una nueva instancia de la clase Coordenada
     */
    public Coordinate(){
      this.x=0;
      this.y=0;
      this.value=0;
    }

    /**
     * Inicia una nueva instancia de la clase Coordenada
     * @param _coor Coordenada
     */
    public Coordinate(Coordinate _coor){
      this.x=_coor.x;
      this.y=_coor.y;
      this.value=_coor.value;
    }

    /**
     * Inicia una nueva instancia de la clase Coordenada
     * @param _x
     * @param _y
     */
    public Coordinate(int _x, int _y){
        this.x=_x;
        this.y=_y;
        this.value=0;
    }

    //  

    // 

    int x;
    int y;
    int value;

    //  

    // 

    /**
     * Obtiene una nuea coordenada a partir del origen
     * @param coordinate Coordenada
     * @return Coordenada
     */
    public Coordinate clone(Coordinate coordinate){
        return new Coordinate(coordinate.x, coordinate.y);
    }

    /**
     * Obtiene la Coordenada textual
     * @return Cadena de texto con la coordenada
     */
    @Override
    public String toString(){
        return "(" + x + ", "  + y + ") = " + this.value;
    }

    /**
     * Ordena dos coordenadas por su valor x e y
     * @param _coor Coordenada
     * @return Entero
     */
    @Override
    public int compareTo(Coordinate _coor) {
        int result=0;
        if (this.x  _coor.x) {
            result= 1;
        }
        else {
            if (this.y <=_coor.y) {
                result= -1;
            }
            else {
                result= 1;
            }
        }
        return result;
    }

    //
}

package espiralulamproject;
import java.util.Comparator;

/******************************************************
 *  @author:    Joaquín Martínez Rus (c) 2016
 *  @version:   1.0
 *  File:       SpiralComparator.java
 *  Created:    19/05/2016
 *  Project:    Ulam Spiral.
 *  Comments:   Clase SpiralComparator.
 *              Clase que implementa Comparator
 *******************************************************/
public class SpiralComparator implements Comparator<Coordinate> {
    @Override
    public int compare(Coordinate o1, Coordinate o2) {
        return o1.compareTo(o2);
    }
}

package espiralulamproject;
import java.util.ArrayList;

/******************************************************
 *  @author:    Joaquín Martínez Rus (c) 2016
 *  @version:   1.0
 *  File:       EspiralUlam.java
 *  Created:    19/05/2016
 *  Project:    Ulam Spiral.
 *  Comments:   Clase EspiralUlam
 *
 *******************************************************/
public class EspiralUlam {

    // 

    /**
     * Inicia unanueva instancia de la clase Espiral de Ulam
     * @param num Número máximo
     */
     public EspiralUlam(int num){
         baseNumber=getBaseNumber(num);
         maxNumber=getMaxNumber(baseNumber);
         firstNumber=getFirstNumbers(maxNumber,baseNumber);
         getFirstCoordinate();
         firstCoordinate.value=1;
         coordinates.add(firstCoordinate);
         setSpiral( new Coordinate(firstCoordinate));
     }
    // 

    // 

     int firstNumber;
     int baseNumber;
     int maxNumber;
     Coordinate firstCoordinate;
     public ArrayList<Coordinate> coordinates=new ArrayList();

    // 

    // 

    /**
     * Obtiene el resultado de la espiral
     * @return Cadena de texto
     */ @Override
     public String toString(){
         this.sort();
         int count=1;
         String textTemp="";
         String textOut="";
         for (Coordinate coordinate : coordinates) {
              textTemp = count % baseNumber == 0?"\n":"\t";
              textOut += coordinate.value + textTemp;
              count ++;
         }
         return textOut;
     }

    // 

    // 

    /**
     * Ordena el arrayList de números
     */
     private void sort(){
         coordinates.sort(new SpiralComparator());
     }

    /**
     * Obtiene el número base de la Espiral
     * @param number Número solicitado
     * @return Valor entero
     */
     private int getBaseNumber(int number){
         int n= (int)Math.sqrt((double)(number))+1;
         if (n%2==0) {
             n++;
         }
         return n;
     }

    /**
     * Obtiene el máximo número de la Espiral
     * @param number Número solicitado
     * @return Valor entero
     */
     private int getMaxNumber(int number){
         return number * number;
     }

    /**
     * Obtiene el primer número de la Espiral
     * @param iMaxNumber Máximo número de la espiral
     * @param ibaseNumber Número base de la espiral
     * @return Valor entero
     */
     private int getFirstNumbers(int iMaxNumber, int ibaseNumber){
         return iMaxNumber - 2*(ibaseNumber-1);
     }  

    /**
     * Obtiene la oordenada central de la espiral
     */
     private void getFirstCoordinate(){
         firstCoordinate=new Coordinate(baseNumber /2, baseNumber/2);
     }

    /**
     * Calcula la espiral de Ulam
     * @param _coor Coordenada central
     */
     private void setSpiral(Coordinate _coor){
         Coordinate temp=_coor;

         int lastNumber=1;
         for (int impar = 1; impar < baseNumber; impar +=2) {
              temp.x++;
              temp.y++;
              for (int lado = 0; lado < 4; lado++) {
              // 4 veces. una por lado del cuadrado
                  for (int i = 0; i <= impar; i++) {
                       temp.value= ++lastNumber;
                       coordinates.add(nextCoordinate(lado, temp));
                  }
              }
         }
     }

    /**
     * Obtiene la siguiente coordenada de la espiral
     * @param n Lado del cuadrado
     * @param _coor Coordenada de cálculo
     * @return Coordenada
     */
     private Coordinate nextCoordinate(int n, Coordinate _coor){
         switch (n) {
             case 0:
                 // UP. substract 1 to x
                 _coor.x--;
                 break;
             case 1:
                 //LEFT. substract 1 to y
                 _coor.y--;
                 break;
             case 2:
                 //DOWN. add 1 to x
                 _coor.x++;
                 break;
             case 3:
                 //RIGHT. add 1 to y
                 _coor.y++;
                 break;
         }
         return new Coordinate(_coor);
     }

}