8. Reutilizar (1)

Resumen del capítulo

  • Gramática combinada
  • sintaxis de herencia
    • Inicializar clase base
    • Constructor con parámetros
  • confiar

La reutilización de código es una de las razones más fascinantes de la programación orientada a objetos (POO).

Para lenguajes orientados a procesos como C, "reutilizar" generalmente significa "duplicar código". Cualquier lenguaje puede lograr la reutilización del código simplemente copiándolo, pero el efecto no es bueno. Java resuelve problemas relacionados con las "Clases". En lugar de crear una nueva clase y empezar de nuevo, podemos simplemente usar código que otra persona haya creado o depurado.

Cómo utilizar el código existente sin contaminar el código fuente requiere habilidades. Aprenda dos formas de lograrlo:

  1. La primera forma es sencilla. Crea un objeto de una clase existente en una nueva clase. Este enfoque se llama "Composición" y reutiliza la funcionalidad del código, en lugar de su forma.
  2. La segunda forma es más sutil. Cree una nueva clase de un tipo de clase existente. Entendido literalmente: tomar la forma de una clase existente sin cambiar su código durante la codificación se llama "herencia", y el compilador hará la mayor parte del trabajo. La herencia es uno de los fundamentos importantes de la programación orientada a objetos (OOP). Se introducirán más funciones relacionadas en el capítulo Polimorfismo.

Hay muchas similitudes en la sintaxis y el comportamiento de la composición y la herencia (esto en realidad tiene sentido, después de todo, ambos construyen nuevos tipos basados ​​en tipos existentes). En este capítulo, aprenderá sobre estos dos métodos de reutilización de código.

Gramática combinada

En estudios anteriores, se ha utilizado "Composición" muchas veces. Sólo necesita colocar las referencias de objetos en una nueva clase, que usa composición. Por ejemplo, supongamos que necesita un objeto que tenga varios objetos String integrados , dos campos de atributos de primitivos y un objeto de otras clases. Para objetos de tipo no básico, la referencia se coloca directamente en la nueva clase, y para campos de atributos de tipo básico, solo se declaran.

class WaterSource {
    
    
    private String s;

    WaterSource() {
    
    
        System.out.println("WaterSource()");
        s = "Constructed";
    }

    @Override
    public String toString() {
    
    
        return s;
    }
}

public class SprinklerSystem {
    
    
    private String valve1, valve2, valve3, valve4;
    private WaterSource source = new WaterSource();
    private int i;
    private float f;

    @Override
    public String toString() {
    
    
        return "valve1 = " + valve1 + " " +
                        "valve2 = " + valve2 + " " +
                        "valve3 = " + valve3 + " " +
                        "valve4 = " + valve4 + "\n" +
                        "i = " + i + " " + "f = " + f + " " +
                        "source = " + source; // [1]
    }

    public static void main(String[] args) {
    
    
        SprinklerSystem sprinklers = new SprinklerSystem();
        System.out.println(sprinklers);
    }
}
WaterSource()
valve1 = null valve2 = null valve3 = null valve4 = null
i = 0 f = 0.0 source = Constructed

Un método definido en estas dos clases es especial: toString(). Cada objeto de tipo no básico tiene un toString()método que se llama en el caso especial en el que el compilador espera una cadena pero tiene un objeto. Entonces, en [1], el compilador ve que está intentando "agregar" un objeto de cadena de tipo WaterSource . Debido a que una cadena solo se puede concatenar con otra cadena, primero llama para toString()convertir la fuente en una cadena. Luego puede concatenar las dos cadenas y pasar la cadena resultante a System.out.println(). Para permitir este comportamiento para cualquier clase que cree, simplemente escriba un método toString() . Utilice la anotación @Override** para indicarle al compilador que garantice la anulación correctatoString() . @Override ** es opcional, pero ayuda a verificar que no cometas errores de ortografía (o, más sutilmente, escribir mal letras mayúsculas y minúsculas). Los campos de tipo primitivo en las clases se inicializan automáticamente a cero, como se describe en el capítulo Objetos en todas partes . Pero la referencia del objeto se inicializa en nulo y si intenta llamar a cualquiera de sus métodos, obtendrá una excepción (un error de tiempo de ejecución). Convenientemente, puede imprimir una referencia nula sin obtener una excepción.

Tiene sentido que el compilador no cree un objeto predeterminado para cada referencia, porque en muchos casos esto provoca una sobrecarga innecesaria. Hay cuatro formas de inicializar una referencia:

  1. cuando el objeto está definido. Esto significa que siempre se inicializan antes de llamar al constructor.
  2. en el constructor de esta clase.
  3. antes de utilizar realmente el objeto. A esto se le suele denominar inicialización diferida. Puede reducir los gastos generales en situaciones en las que la creación de objetos es costosa y no es necesario crear el objeto cada vez.
  4. Utilice la inicialización de instancia.

A continuación se muestran ejemplos de los cuatro métodos anteriores de creación de instancias:

class Soap {
    
    
    private String s;

    Soap() {
    
    
        System.out.println("Soap()");
        s = "Constructed";
    }

    @Override
    public String toString() {
    
    
        return s;
    }
}

public class Bath {
    
    
    private String // Initializing at point of definition:
            s1 = "Happy",
            s2 = "Happy",
            s3, s4;
    private Soap castille;
    private int i;
    private float toy;

    public Bath() {
    
    
        System.out.println("Inside Bath()");
        s3 = "Joy";
        toy = 3.14f;
        castille = new Soap();
    }

    // Instance initialization:
    {
    
    
        i = 47;
    }

    @Override
    public String toString() {
    
    
        if (s4 == null) // Delayed initialization:
        {
    
    
            s4 = "Joy";
        }
        return "s1 = " + s1 + "\n" +
                        "s2 = " + s2 + "\n" +
                        "s3 = " + s3 + "\n" +
                        "s4 = " + s4 + "\n" +
                        "i = " + i + "\n" +
                        "toy = " + toy + "\n" +
                        "castille = " + castille;
    }

    public static void main(String[] args) {
    
    
        Bath b = new Bath();
        System.out.println(b);
    }
}

Insertar descripción de la imagen aquí

En el constructor de Bath , hay un bloque de código que se ejecuta antes de que se realice cualquier inicialización. Cuando no inicializa en la definición, todavía no hay garantía de que se realice ninguna inicialización antes de enviar un mensaje a la referencia del objeto; si intenta llamar a un método en una referencia no inicializada, la referencia no inicializada producirá una excepción de tiempo de ejecución.

Cuando se llama toString(), asigna s4 para que todas las propiedades ya estén inicializadas cuando se usa el campo.

sintaxis de herencia

La herencia es una parte integral de todos los lenguajes orientados a objetos. Resulta que siempre tienes que heredar cuando creas una clase, porque a menos que heredes explícitamente de otras clases, heredas implícitamente la clase raíz estándar de Java Objeto (Objeto).

La sintaxis para la composición es obvia, pero la herencia usa una sintaxis especial. Cuando heredas, dices: "Esta nueva clase es similar a la clase anterior. Declaras esto en el código antes de la llave de apertura del cuerpo de la clase, usando la palabra clave extends seguida del nombre de la clase base. Cuando lo haces , Obtendrá automáticamente todos los campos y métodos en la clase base. Aquí hay un ejemplo:

class Cleanser {
    
    
    private String s = "Cleanser";

    public void append(String a) {
    
    
        s += a;
    }

    public void dilute() {
    
    
        append(" dilute()");
    }

    public void apply() {
    
    
        append(" apply()");
    }

    public void scrub() {
    
    
        append(" scrub()");
    }

    @Override
    public String toString() {
    
    
        return s;
    }

    public static void main(String[] args) {
    
    
        Cleanser x = new Cleanser();
        x.dilute();
        x.apply();
        x.scrub();
        System.out.println(x);
    }
}

public class Detergent extends Cleanser {
    
    
    // Change a method:
    @Override
    public void scrub() {
    
    
        append(" Detergent.scrub()");
        super.scrub(); // Call base-class version
    }

    // Add methods to the interface:
    public void foam() {
    
    
        append(" foam()");
    }

    // Test the new class:
    public static void main(String[] args) {
    
    
        Detergent x = new Detergent();
        x.dilute();
        x.apply();
        x.scrub();
        x.foam();
        System.out.println(x);
        System.out.println("Testing base class:");
        Cleanser.main(args);
    }
}
Cleanser dilute() apply() scrub()

Cleanser dilute() apply() Detergent.scrub() scrub() foam()
Testing base class:
Cleanser dilute() apply() scrub()

Esto demuestra algunas características. Primero, en el método de Cleanser append(), concatenas la cadena con s+= usando el operador , que es uno de los operadores que los diseñadores de Java "sobrecargaron" para manejar cadenas (junto con +).

En segundo lugar, tanto el limpiador como el detergente contienen un main()método. Puede crear uno para cada clase main(); esto permite realizar pruebas sencillas de cada clase. Cuando haya terminado de probar, no es necesario eliminarlo main(); puede dejarlo para pruebas futuras. Aunque hay muchas clases en el programa con main()métodos, las únicas que se ejecutan son las que se llaman en la línea de comando main(). Aquí, se llama cuando se usa java DetergentDetergent.main() . Pero también puedes usar java Cleanser para llamarlo Cleanser.main(), incluso si Cleanser no es una clase pública. El acceso está disponible incluso si la clase sólo tiene acceso al paquete public main().

Aquí, Detergent.main()se llama explícitamente Cleanser.main()y se pasan los mismos argumentos desde la línea de comando (por supuesto, puede pasar cualquier conjunto de cadenas).

Todos los métodos en Cleanser son públicos. Recuerde que si no utiliza ningún modificador de acceso, los miembros tendrán acceso al paquete de forma predeterminada, lo que solo permite el acceso a los miembros dentro del paquete. Por lo tanto, sin modificadores de acceso, estos métodos están disponibles para cualquier persona dentro del paquete. Por ejemplo, Detergente no tiene ningún problema. Sin embargo, si una clase en otro paquete hereda Cleanser , esa clase solo puede acceder a los miembros públicos de Cleanser . Entonces, para permitir la herencia, la regla general es que todos los campos sean privados y todos los métodos sean públicos. ( Las clases derivadas también pueden acceder a los miembros protegidos ; lo sabrás más adelante). En casos específicos, tendrás que hacer ajustes, pero esta es una guía útil.

Hay un conjunto de métodos en la interfaz de Cleanserappend() : , dilute(), apply()y . Debido a que Detergent se deriva de Cleanser (a través de la palabra clave extends ), automáticamente obtiene todos estos métodos en su interfaz, incluso si no ve una definición explícita de todos estos métodos en Detergent . Entonces, se puede considerar la herencia como la reutilización de clases. Como se ve en , puede utilizar métodos definidos en la clase base y modificarlos. Aquí puede llamar al método de la clase base en la nueva clase. Pero internamente, no se puede llamar simplemente, ya que eso crearía una llamada recursiva. Para resolver este problema, la palabra clave super de Java se refiere a la "superclase" (clase base) de la que hereda la clase actual. Por lo tanto, la expresión llama a la versión de clase base del método.scrub()toString()scrub()scrub()scrub()super.scrub()scrub()

Al heredar, no está limitado a utilizar métodos de la clase base. También puedes agregar un nuevo método a una clase derivada tal como lo harías con cualquier método de una clase: simplemente defínelo. El método foam()es un ejemplo. Detergent.main()Como puede ver en el objeto Detergente , puede llamar a todos los métodos disponibles en Limpiador y Detergentefoam() (por ejemplo , .

Inicializar clase base

Ahora hay dos clases involucradas: clase base y clase derivada. Puede resultar confuso imaginar el objeto resultante generado por una clase derivada. Desde fuera, la nueva clase tiene la misma interfaz que la clase base, posiblemente con algunos métodos y campos adicionales. Pero la herencia no se limita a copiar la interfaz de la clase base. Cuando crea un objeto de una clase derivada, contiene objetos secundarios de la clase base. Este subobjeto es el mismo que el objeto de clase base que creó usted mismo. Desde fuera, los subobjetos de la clase base están envueltos en objetos de la clase derivada.

Los subobjetos de la clase base deben inicializarse correctamente y solo hay una forma de garantizar esto: realizar la inicialización en un constructor llamando a un constructor de clase base que tenga toda la información y los privilegios adecuados necesarios para realizar la inicialización de la clase base. Java inserta automáticamente una llamada al constructor de la clase base en el constructor de la clase derivada. El siguiente ejemplo muestra tres niveles de herencia:

class Art {
    
    
    Art() {
    
    
        System.out.println("Art constructor");
    }
}

class Drawing extends Art {
    
    
    Drawing() {
    
    
        System.out.println("Drawing constructor");
    }
}

public class Cartoon extends Drawing {
    
    
    public Cartoon() {
    
    
        System.out.println("Cartoon constructor");
    }

    public static void main(String[] args) {
    
    
        Cartoon x = new Cartoon();
    }
}
Art constructor
Drawing constructor
Cartoon constructor

La construcción procede "fuera" de la clase base, por lo que la clase base se inicializa antes de que el constructor de la clase derivada pueda acceder a ella. Incluso si no crea un constructor para Cartoon , el compilador sintetizará un constructor sin parámetros y llamará al constructor de la clase base. Intente eliminar el constructor Cartoon para ver esto.

Constructor con parámetros

Los constructores en todos los ejemplos anteriores no tienen parámetros; es fácil para el compilador llamar a estos constructores porque no se requieren parámetros. Si no hay un constructor de clase base sin parámetros, o si se debe llamar a un constructor de clase base con parámetros, la llamada al constructor de clase base debe escribirse explícitamente usando la palabra clave super y una lista de parámetros apropiada:

// reuse/Chess.java
// (c)2017 MindView LLC: see Copyright.txt
// We make no guarantees that this code is fit for any purpose.
// Visit http://OnJava8.com for more book information.
// Inheritance, constructors and arguments

class Game {
    
    
  Game(int i) {
    
    
    System.out.println("Game constructor");
  }
}

class BoardGame extends Game {
    
    
  BoardGame(int i) {
    
    
    super(i);
    System.out.println("BoardGame constructor");
  }
}

public class Chess extends BoardGame {
    
    
  Chess() {
    
    
    super(11);
    System.out.println("Chess constructor");
  }
  public static void main(String[] args) {
    
    
    Chess x = new Chess();
  }
}
/* Output:
Game constructor
BoardGame constructor
Chess constructor
*/

Si no se llama al constructor de la clase base en el constructor BoardGame , el compilador informará un error que indica que Game()no se puede encontrar el constructor. Además, la llamada al constructor de la clase base debe ser la primera operación en el constructor de la clase derivada. (El compilador le recordará si comete un error).

confiar

Un tercer tipo de relación de reutilización que Java no admite directamente se llama delegación. Esto está en algún lugar entre herencia y composición, porque colocas un objeto miembro en la clase que estás creando (como composición), pero al mismo tiempo expones todos los métodos del objeto miembro en la nueva clase (como herencia). Por ejemplo, una nave espacial requiere un módulo de control:

// reuse/SpaceShipControls.java
// (c)2017 MindView LLC: see Copyright.txt
// We make no guarantees that this code is fit for any purpose.
// Visit http://OnJava8.com for more book information.

public class SpaceShipControls {
    
    
  void up(int velocity) {
    
    }
  void down(int velocity) {
    
    }
  void left(int velocity) {
    
    }
  void right(int velocity) {
    
    }
  void forward(int velocity) {
    
    }
  void back(int velocity) {
    
    }
  void turboBoost() {
    
    }
}

Una forma de construir una nave espacial es utilizar la herencia:

// reuse/DerivedSpaceShip.java
// (c)2017 MindView LLC: see Copyright.txt
// We make no guarantees that this code is fit for any purpose.
// Visit http://OnJava8.com for more book information.

public class DerivedSpaceShip extends SpaceShipControls {
    
    
  private String name;
  public DerivedSpaceShip(String name) {
    
    
    this.name = name;
  }
  @Override
  public String toString() {
    
     return name; }
  public static void main(String[] args) {
    
    
    DerivedSpaceShip protector = new DerivedSpaceShip("NSEA Protector");
    protector.forward(100);
  }
}

Sin embargo, DerivedSpaceShip no es realmente "una especie de" SpaceShipControls , incluso si le "dice" a DerivedSpaceShip que llame forward(). Más precisamente, una nave espacial contiene SpaceShipControls y todos los métodos de SpaceShipControls están expuestos en la nave espacial. La delegación resuelve este dilema:

// reuse/SpaceShipDelegation.java
// (c)2017 MindView LLC: see Copyright.txt
// We make no guarantees that this code is fit for any purpose.
// Visit http://OnJava8.com for more book information.

public class SpaceShipDelegation {
    
    
  private String name;
  private SpaceShipControls controls = new SpaceShipControls();
  public SpaceShipDelegation(String name) {
    
    
    this.name = name;
  }
  // Delegated methods:
  public void back(int velocity) {
    
    
    controls.back(velocity);
  }
  public void down(int velocity) {
    
    
    controls.down(velocity);
  }
  public void forward(int velocity) {
    
    
    controls.forward(velocity);
  }
  public void left(int velocity) {
    
    
    controls.left(velocity);
  }
  public void right(int velocity) {
    
    
    controls.right(velocity);
  }
  public void turboBoost() {
    
    
    controls.turboBoost();
  }
  public void up(int velocity) {
    
    
    controls.up(velocity);
  }
  public static void main(String[] args) {
    
    
    SpaceShipDelegation protector = new SpaceShipDelegation("NSEA Protector");
    protector.forward(100);
  }
}

Los métodos se reenvían al objeto de control subyacente , por lo que la interfaz es la misma que la heredada. Sin embargo, tiene más control sobre los delegados, porque puede elegir proporcionar solo un subconjunto de métodos en objetos miembro.

Aunque el lenguaje Java no admite la delegación, las herramientas de desarrollo sí lo hacen. Por ejemplo, el ejemplo anterior se generó automáticamente utilizando JetBrains Idea IDE.

Guess you like

Origin blog.csdn.net/GXL_1012/article/details/132195759