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");
    }

Anuncio publicitario

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 Números Complejos (C# y WPF)

Calculadora Números Complejos (C# y WPF)

Java y C# son lenguajes muy parecidos, de hecho C# deriva de C++, Java y otros lenguajes alimentándose de lo bueno de cada uno; particularmente para mi es el mejor por su potencia y sus increíbles mejoras que nos hacen la programación mucho más fácil.

En el anterior post, describí una calculadora de números complejos en Java y en este, lo implemento con C# y WPF. Con esto intento tres cosas, en primer lugar demostrar que la clase (muy pero que muy similar a la de Java) es invariable porque me daría igual usar una aplicación de consola o gráfica, otra es que podáis ver la diferencia entre lenguajes pero la similitud estructural y por último implementarla mediante WPF con el cual nos vamos a ahorrar mucho código quedando la aplicación muy limpia y robusta y de camino os enseño como se aplican estilos a los controles.

Lo primero vayamos a la clase Complex. Las principales diferencias con la de Java son las que voy a enumerar.

  1. Implemento la interfaz INotifyPropertyChanged de modo que la clase debe contener el evento public event PropertyChangedEventHandler PropertyChanged; el cual invocamos mediante el método OnPropertyChanged(string p_PropertyName) cada vez que cambia la propiedad de la parte real o imaginaria de un número complejo.
  2. En esta clase, he sobrecargado las operaciones básicas de suma, resta, multiplicación, división y negación (a este operador le he asignado el opuesto). Para ver como lo he hecho, os paso el código de la operación suma con el que con un método
            public static Complex operator + (Complex _complex1, Complex _complex2)
            {
                return new Complex(_complex1.Real + _complex2.Real, _complex1.Imaginary + _complex2.Imaginary);
            }
    

    permitiéndome efectuar sumas sobre dos complejos.

A continuación paso el código completo de la clase.

/*************************************************************************************************************************************************
* © JOAQUIN MARTINEZ RUS 2015
* PROYECTO:        ComplexCalc. Calculadora de números complejos
* Archivo:         Complex.cs
* Descripción:     Clase de números complejos
* Historial:
*                  1. Joaquin Martínez Rus - 18 jul 2016. Creación
*
* Comentarios:
*
*
**************************************************************************************************************************************************/

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ComplexCal
{
    public class Complex: INotifyPropertyChanged
    {
        #region Constructor

        public Complex(double _real=0, double _imaginary=0)
        {
            this.Real = _real;
            this.Imaginary = _imaginary;
        }

        public Complex(Complex _complex)
        {
            this.Real = _complex.Real;
            this.Imaginary = _complex.Imaginary;
        }

        #endregion

        #region Propiedades

        private double real;
        private double imaginary;

        public event PropertyChangedEventHandler PropertyChanged;

        public double Module { get; set; }
        public double DegreesAngle { get; set; }
        public double RadiansAngle { get; set; }

        public double Imaginary
        {
            get { return imaginary; }
            set
            {
                imaginary = value;
                SetPolarComplex();
                OnPropertyChanged("Imaginary");
                OnPropertyChanged("ToString");
            }
        }

        public double Real
        {
            get { return real; }
            set
            {
                real = value;
                SetPolarComplex();
                OnPropertyChanged("Real");
                OnPropertyChanged("ToString");
            }
        }

        public string ToBinomialString
        {
            get
            {
                return "z = " + this.Real.ToString("F2") + (this.Imaginary < 0 ? " - " : " + ") + Math.Abs(this.Imaginary).ToString("F2") + " i";
            }
        }

        public string ToPolarString
        {
            get
            {
                return "z = " + this.Module.ToString("F2") + ") " + this.DegreesAngle.ToString("F1") + "°";
            }
        }

        public string ToString
        {
            get { return this.ToBinomialString + new string(' ',10) + this.ToPolarString; }
        }

        #endregion

        #region Private Methods

        /// <summary>
        /// Asigna el módulo y el argumento de un número complejo
        /// </summary>
        void SetPolarComplex()
        {
            this.Module= Math.Sqrt(Math.Pow(this.Real, 2) + Math.Pow(this.Imaginary, 2));
            this.RadiansAngle= Math.Atan2(this.Imaginary, this.Real);
            this.DegreesAngle = this.RadiansAngle * 180 / Math.PI;
        }

        #endregion

        #region Static Public Methods

        // SOBRECARGA DE LOS OPERADORES BÁSICOS

        public static Complex operator + (Complex _complex1, Complex _complex2)
        {
            return new Complex(_complex1.Real + _complex2.Real, _complex1.Imaginary + _complex2.Imaginary);
        }

        public static Complex operator - (Complex _complex1, Complex _complex2)
        {
            return new Complex(_complex1.Real - _complex2.Real, _complex1.Imaginary - _complex2.Imaginary);
        }

        public static Complex operator * (Complex _complex1, Complex _complex2)
        {
            return new Complex((_complex1.Real * _complex2.Real - _complex1.Imaginary * _complex2.Imaginary),
                (_complex1.Real * _complex2.Imaginary + _complex1.Imaginary * _complex2.Real));
        }

        public static Complex operator / (Complex _complex1, Complex _complex2)
        {
            double xx = (_complex1.Real * _complex2.Real + _complex1.Imaginary * _complex2.Imaginary) / (Math.Pow(_complex2.Real, 2) + Math.Pow(_complex2.Imaginary, 2));
            double yy = (_complex1.Imaginary * _complex2.Real - _complex1.Real * _complex2.Imaginary) / (Math.Pow(_complex2.Real, 2) + Math.Pow(_complex2.Imaginary, 2));
            return new Complex(xx, yy);
        }

        public static Complex operator ! (Complex _complex)
        {
            return new Complex(-_complex.Real, -_complex.Imaginary);
        }

        #endregion

        #region Public Methods

        /// <summary>
        /// Inicia el evento de cambio de propiedad
        /// </summary>
        /// <param name="p_PropertyName">Nombre de la propiedad</param>
        public void OnPropertyChanged(string p_PropertyName)
        {
            if (this.PropertyChanged != null)
            {
                this.PropertyChanged(this, new PropertyChangedEventArgs(p_PropertyName));
            }
        }

        /// <summary>
        /// Efectúa una copia del objeto
        /// </summary>
        /// <param name="_complex">Objeto desde el cual se efectúa la copia</param>
        public void Clone(Complex _complex)
        {
            this.Real = _complex.Real;
            this.Imaginary = _complex.Imaginary;
        }

        /// <summary>
        /// Devuelve el conjugado de un número complejo
        /// </summary>
        /// <param name="complex">Número complejo al que se le calcula</param>
        /// <returns>Objeto Complex</returns>
        public Complex Conjugate(Complex complex)
        {
            return new Complex(complex.Real, -complex.Imaginary);
        }

        /// <summary>
        /// Devuelve el conjugado de un número complejo
        /// </summary>
        /// <returns>Objeto Complex</returns>
        public Complex Conjugate()
        {
            return Conjugate(this);
        }

        /// <summary>
        /// Determina cuando una instancia y un objeto tienen los mismos valores
        /// </summary>
        /// <param name="_complex">El objeto a comparar con la instancia</param>
        /// <returns>Valor booleano</returns>
        public bool Equals(Complex _complex)
        {
            if (_complex == null)
            {
                return false;
            }
            if (this.GetType() != _complex.GetType())
            {
                return false;
            }

            return this.Real == _complex.Real && this.Imaginary == _complex.Imaginary;
        }

        /// <summary>
        /// Obtiene el inverso del objeto Complex actual
        /// </summary>
        /// <returns>Objeto Complex</returns>
        public Complex Reverse()
        {
            return Reverse(this);
        }

        /// <summary>
        /// Obtiene el inverso de un objeto Complex
        /// </summary>
        /// <param name="_complex">Objeto Complex del que se obtiene el inverso</param>
        /// <returns>Objeto Complex</returns>
        public Complex Reverse(Complex _complex)
        {
            double denominador = Math.Pow(_complex.Real, 2) + Math.Pow(_complex.Imaginary, 2);
            return new Complex(_complex.Real / denominador, -_complex.Imaginary / denominador);
        }

        /// <summary>
        /// Retorna el objeto Complex en formato Polar
        /// </summary>
        /// <param name="isDegrees">La salida se muestra en grados sexagesimales</param>
        /// <returns>String</returns>
        public string PolarToString(bool isDegrees)
        {
            return this.Module.ToString("D1") + ")" + (isDegrees?this.DegreesAngle.ToString("D1"):this.RadiansAngle.ToString("D2"));
        }

        /// <summary>
        /// Retorna el objeto Complex en formato Polar
        /// </summary>
        /// <returns>String</returns>
        public string PolarToString()
        {
            return PolarToString(true);
        }

        #endregion
    }
}


Ahora damos paso al código XAML del formulario. Lo primero declaramos los recursos de la ventana con &lt;code&gt;Window.Resources&lt;/code&gt; y en este lugar, incluimos los estilos. He creado tres estilos, uno llamado &lt;span style='font-family: &quot;;&quot;;' data-mce-style='font-family: &quot;;'&gt;normal&lt;/span&gt; aplicado a los controles &lt;code&gt;TextBlock&lt;/code&gt;, otro llamado &lt;code&gt;header&lt;/code&gt; que hereda de normal y por tanto es aplicado a los mismos controles y un tercer estilo llamado &lt;code&gt;textBox&lt;/code&gt; aplicado a controles &lt;code&gt;TextBox&lt;/code&gt; (muy original, eh?), por tanto a cada control que le asigne este estilo se le aplicarán los valores de las propiedades incluidas en el estilo a no ser que se sobreescriban al crear el control.( En el control &lt;code&gt;TextBox&lt;/code&gt; he incluido un validador para que no se incluya texto que no sea numérico, pero eso lo veremos otro día)Una vez creados los estilos vamos a centrarnos en&amp;nbsp;el control &lt;code&gt;TextBlock&lt;/code&gt; del siguiente código: 
 
      <StackPanel x:Name="stackResult" Orientation="Horizontal" Grid.Column="2" Grid.Row="5" Grid.ColumnSpan="5">
            <TextBlock x:Name="result"  Style="{StaticResource header}" Text="Resultado" VerticalAlignment="Center"/>
            <TextBlock x:Name="labelComplexNumberResult" Style="{StaticResource normal}" Text="{Binding ToString}" Margin="123,0,0,0"/>
</StackPanel>  
  1. Le asignamos un nombre
  2. Le asignamos el estilo con Style="{StaticResource textBox}"
  3. Le asignamos el valor enlazado de la propiedad Text con  la propiedad Real de la clase Complex. Style="{StaticResource textBox}" Text="{Binding Real}"¿Y de donde extrae los datos la propiedad Text?

Para que los controles sepan de donde tienen que extraer los datos de la clase Complex, al crear la ventana principal, asigno a cada StackPanel donde se encuentran contenidos los controles de cada número, la clase desde donde se alimentará y cada vez que se alteren los datos de cada clase, se visualizarán automáticamente en los controles sin necesidad de código. Por tanto en el code-behind de la ventana principal solo y exclusivamente incluyo la declaración de los tres objetos Complex, los dos de cálculo y el resultado, la asignación del DataContext de los StackPanel, los métodos de los eventos de cambio de propiedad y cambio de operación y el cálculo  de la operación.

/* ************************************************************************************************************************************************
* © JOAQUIN MARTINEZ RUS 2015
* PROYECTO:        ComplexCalc. Calculadora de números complejos
* Archivo:         MainWindow.cs
* Descripción:     Clase de la ventana principal
* Historial:
*                  1. Joaquin Martínez Rus - 18 jul 2016. Creación
*
* Comentarios:
*
*
**************************************************************************************************************************************************/
using System.Windows;
using System.Windows.Controls;

namespace ComplexCal
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        Complex c1 = new Complex(2,4);
        Complex c2 = new Complex(3,5);
        Complex ComplexResult=new Complex();

        public MainWindow()
        {
            InitializeComponent();

            // asignar a los Stackpanel el Datacontext de cada objeto Complex

            stackC1.DataContext = c1;
            stackC2.DataContext = c2;
            stackResult.DataContext = ComplexResult;

            // Crear evento para que cada vez que se cambie una propiedad, se calcule la operación
            c1.PropertyChanged += Complex_PropertyChanged;
            c2.PropertyChanged += Complex_PropertyChanged;

        }

        private void Complex_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
        {
            Calculate(comboBox.SelectionBoxItem.ToString());
        }

        private void comboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            ComboBoxItem op = e.AddedItems[0] as ComboBoxItem;
            Calculate(op.Content.ToString());
        }

        /// <summary>
        /// Efectúa el cálculo entre dos complejos
        /// </summary>
        /// <param name="operatorToString">Operador</param>
        void Calculate(string operatorToString)
        {
            switch (operatorToString)
            {
                case "+":
                    ComplexResult.Clone(c1 + c2);
                    break;
                case "-":
                    ComplexResult.Clone(c1 - c2);
                    break;
                case "x":
                    ComplexResult.Clone(c1 * c2);
                    break;
                case "÷":
                    ComplexResult.Clone(c1 / c2);
                    break;
                case "Inverso":
                    ComplexResult.Clone(c1.Reverse());
                    break;
                case "Conjugado":
                    ComplexResult.Clone(c1.Conjugate());
                    break;
                case "Opuesto":
                    ComplexResult.Clone(!c1);
                    break;
            }

        }

    }
}

y el código XAML full:

<Window x:Class="ComplexCal.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:ComplexCal"
        mc:Ignorable="d"
        Title="Complex Calc" Height="480" Width="640">
    <Window.Resources>

        <Style x:Key="normal" TargetType="TextBlock">
            <Setter Property="VerticalAlignment" Value="Center"/>            
            <Setter Property="FontSize" Value="14"/>
            <Setter Property="FontFamily" Value="Segoe UI"/>
            <Setter Property="Height" Value="25"/>
            <Setter Property="Width" Value="Auto"/>
        </Style>
        <Style x:Key="header" TargetType="TextBlock" BasedOn="{StaticResource normal}">
            <Setter Property="FontWeight" Value="Bold"/>
            <Setter Property="VerticalAlignment" Value="Bottom"/>
            <Setter Property="HorizontalAlignment" Value="Center"/>            
        </Style>
        <Style x:Key="textBox" TargetType="TextBox">
            <Setter Property="VerticalAlignment" Value="Center"/>
            <Setter Property="Height" Value="25"/>
            <Setter Property="Width" Value="50"/>
            <Setter Property="HorizontalAlignment" Value="Center"/>
            <Setter Property="Validation.ErrorTemplate">
                <Setter.Value>
                    <ControlTemplate>
                        <DockPanel LastChildFill="true">
                            <Border Background="OrangeRed" DockPanel.Dock="right" Margin="5,0,0,0" 
                                Width="20" Height="20" CornerRadius="5"
                                ToolTip="{Binding ElementName=customAdorner, 
                                          Path=AdornedElement.(Validation.Errors)[0].ErrorContent}">
                                <TextBlock Text="!" VerticalAlignment="center" HorizontalAlignment="center" 
                                   FontWeight="Bold" Foreground="white" />
                            </Border>
                            <AdornedElementPlaceholder Name="customAdorner" VerticalAlignment="Center" >
                                <Border BorderBrush="red" BorderThickness="1" />
                            </AdornedElementPlaceholder>
                        </DockPanel>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
            <Setter Property="Margin" Value="20,0,0,0"/>
        </Style>
    </Window.Resources>
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="28*"/>
            <ColumnDefinition Width="27*"/>
            <ColumnDefinition Width="82*"/>
            <ColumnDefinition Width="132*"/>
            <ColumnDefinition Width="154*"/>
            <ColumnDefinition Width="60*"/>
            <ColumnDefinition Width="149*"/>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="49*"/>
            <RowDefinition Height="49*"/>
            <RowDefinition Height="49*"/>
            <RowDefinition Height="49*"/>
            <RowDefinition Height="49*"/>
            <RowDefinition Height="49*"/>
            <RowDefinition Height="155*"/>
        </Grid.RowDefinitions>
        <TextBlock x:Name="labelReal" Grid.Column="2" Grid.Row="1" Style="{StaticResource header}" Text="Real"/>
        <TextBlock x:Name="labelImaginaria" Grid.Column="3" Grid.Row="1" Style="{StaticResource header}" Text="Imaginaria"/>
        <TextBlock x:Name="labelNumberHeader" Grid.Column="4" Grid.Row="1" Style="{StaticResource header}" Text="Número complejo"/>
        <TextBlock x:Name="z1" Grid.Column="1" Grid.Row="2" Style="{StaticResource normal}" Text="z1" Margin="0,12"/>
        <TextBlock x:Name="z2" Grid.Column="1" Grid.Row="4" Style="{StaticResource normal}" Text="z2" Margin="0,12" />
        <TextBlock x:Name="op" Grid.Column="2" Grid.Row="3" Style="{StaticResource normal}" Text="Operación" Margin="0,12"/>
        <ComboBox x:Name="comboBox" Grid.Column="3" HorizontalAlignment="Left" Grid.Row="3" VerticalAlignment="Center" Width="80" Height="25" SelectionChanged="comboBox_SelectionChanged">
            <ComboBoxItem Content="+" IsSelected="True"/>
            <ComboBoxItem Content="-"/>
            <ComboBoxItem Content="x"/>
            <ComboBoxItem Content="÷"/>
            <ComboBoxItem Content="Inverso"/>
            <ComboBoxItem Content="Conjugado"/>
            <ComboBoxItem Content="Opuesto"/>
        </ComboBox>
        <StackPanel x:Name="stackC1" Orientation="Horizontal" Grid.Column="2" Grid.Row="2" Grid.ColumnSpan="5">
            <TextBox x:Name="textBoxReal1"  Style="{StaticResource textBox}" Text="{Binding Real}"/>
            <TextBox x:Name="textBoxImagin1" Style="{StaticResource textBox}" Text="{Binding Imaginary}"/>
            <TextBlock x:Name="labelComplexNumber1" Style="{StaticResource normal}" Text="{Binding ToString}" Margin="50,0,0,0" />
        </StackPanel>
        <StackPanel x:Name="stackC2" Orientation="Horizontal" Grid.Column="2" Grid.Row="4" Grid.ColumnSpan="5">
            <TextBox x:Name="textBoxReal2" Style="{StaticResource textBox}" Text="{Binding Real}"/>
            <TextBox x:Name="textBoxImagin2" Style="{StaticResource textBox}" Text="{Binding Imaginary}"/>
            <TextBlock x:Name="labelComplexNumber2" Style="{StaticResource normal}" Text="{Binding ToString}" Margin="50,0,0,0" />
        </StackPanel>
        <StackPanel x:Name="stackResult" Orientation="Horizontal" Grid.Column="2" Grid.Row="5" Grid.ColumnSpan="5">
            <TextBlock x:Name="result"  Style="{StaticResource header}" Text="Resultado" VerticalAlignment="Center"/>
            <TextBlock x:Name="labelComplexNumberResult" Style="{StaticResource normal}" Text="{Binding ToString}" Margin="123,0,0,0"/>
        </StackPanel>
    </Grid>
</Window>


Pues esto es todo, la clase Complex en C# y su ejecución bajo WPF con los controles enlazados.

Nos vemos. Saludos

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!