Modismo de parámetro con nombre en Java


81

¿Cómo implementar el idioma de parámetro con nombre en Java? (especialmente para constructores)

Estoy buscando una sintaxis similar a Objective-C y no como la que se usa en JavaBeans.

Un pequeño ejemplo de código estaría bien.

Gracias.

Respuestas:


103

El mejor lenguaje de Java que he encontrado para simular argumentos de palabras clave en constructores es el patrón Builder, descrito en Effective Java 2nd Edition .

La idea básica es tener una clase Builder que tenga setters (pero generalmente no getters) para los diferentes parámetros del constructor. También hay un build()método. La clase Builder es a menudo una clase anidada (estática) de la clase que se usa para construir. El constructor de la clase externa suele ser privado.

El resultado final se parece a lo siguiente:

public class Foo {
  public static class Builder {
    public Foo build() {
      return new Foo(this);
    }

    public Builder setSize(int size) {
      this.size = size;
      return this;
    }

    public Builder setColor(Color color) {
      this.color = color;
      return this;
    }

    public Builder setName(String name) {
      this.name = name;
      return this;
    }

    // you can set defaults for these here
    private int size;
    private Color color;
    private String name;
  }

  public static Builder builder() {
      return new Builder();
  }

  private Foo(Builder builder) {
    size = builder.size;
    color = builder.color;
    name = builder.name;
  }

  private final int size;
  private final Color color;
  private final String name;

  // The rest of Foo goes here...
}

Para crear una instancia de Foo, escribe algo como:

Foo foo = Foo.builder()
    .setColor(red)
    .setName("Fred")
    .setSize(42)
    .build();

Las principales advertencias son:

  1. Configurar el patrón es bastante detallado (como puede ver). Probablemente no valga la pena, excepto para las clases que planea instanciar en muchos lugares.
  2. No se comprueba en tiempo de compilación que todos los parámetros se hayan especificado exactamente una vez. Puede agregar verificaciones en tiempo de ejecución, o puede usar esto solo para parámetros opcionales y hacer que los parámetros requeridos sean parámetros normales para Foo o el constructor del Constructor. (Las personas generalmente no se preocupan por el caso en el que el mismo parámetro se configura varias veces).

También puede consultar esta publicación de blog (no de mí).


12
Eso realmente no es parámetros con nombre de la forma en que Objective-C los hace. Eso se parece más a una interfaz fluida. Realmente no es lo mismo.
Asaph

29
Me gusta usar .withFoo, en lugar de .setFoo: en newBuilder().withSize(1).withName(1).build()lugar denewBuilder().setSize(1).setName(1).build()
notnoop

16
Asaph: sí, lo sé. Java no tiene parámetros con nombre. Por eso dije que este es "el mejor idioma de Java que he visto para simular argumentos de palabras clave". Los "parámetros con nombre" de Objective-C también son menos que ideales, ya que fuerzan un orden particular. No son verdaderos argumentos de palabras clave como en Lisp o Python. Al menos con el patrón Java Builder, solo necesita recordar los nombres, no el orden, al igual que los argumentos de palabras clave reales.
Laurence Gonsalves

14
notnoop: Prefiero "set" porque estos son setters que mutan el estado del Builder. Sí, "con" se ve bien en el caso simple en el que está encadenando todo, pero en casos más complicados en los que tiene el Constructor en su propia variable (tal vez porque está configurando propiedades condicionalmente) Me gusta que el conjunto El prefijo deja completamente claro que el Constructor está siendo mutado cuando se llaman estos métodos. El prefijo "con" me suena funcional, y estos métodos definitivamente no son funcionales.
Laurence Gonsalves

4
There's no compile-time checking that all of the parameters have been specified exactly once.Este problema puede superarse devolviendo las interfaces Builder1a BuilderNdonde cada una cubre uno de los establecedores o el build(). Es mucho más detallado de codificar, pero viene con soporte de compilador para su DSL y hace que el autocompletado sea muy agradable para trabajar.
rsp

72

Vale la pena mencionar esto:

Foo foo = new Foo() {{
    color = red;
    name = "Fred";
    size = 42;
}};

el llamado inicializador de doble abrazadera . En realidad, es una clase anónima con un inicializador de instancia.


26
Es una técnica interesante pero parece un poco cara, ya que creará una nueva clase cada vez que la use en mi código.
Red Hyena

6
Dejando a un lado las advertencias de auto-formato, subclasificación y serialización, esto en realidad se acerca bastante a la sintaxis de C # para la inicialización basada en propiedades. Sin embargo, C # a partir de 4.0 también tiene parámetros con nombre, por lo que los programadores tienen muchas opciones para elegir, a diferencia de los programadores de Java que tienen que simular modismos que les impiden dispararse en el pie más adelante.
Distortum

3
Me alegra ver que esto es posible, pero tuve que votar en contra ya que esta solución es costosa, como señaló Red Hyena. No puedo esperar hasta que Java realmente admita parámetros con nombre como lo hace Python.
Gattster

12
Voto a favor. Esto responde a la pregunta de la manera más legible y concisa. Multa. No es "eficaz". ¿Cuántos milisegundos y bits extra estamos hablando aquí? ¿Uno de ellos? Diez? No me malinterpretes, no usaré esto, ya que odiaría ser ejecutado por un fanático de Java detallado (juego de palabras;)
steve

2
¡Perfecto! Solo requiere campos públicos / protegidos. Definitivamente es la mejor solución, ya que causa muchos menos gastos generales que los del constructor. Hyena / Gattster: por favor (1) lea JLS y (2) verifique el bytecode generado antes de escribir sus comentarios.
Jonatan Kaźmierczak

21

También puede intentar seguir los consejos desde aquí: http://www.artima.com/weblogs/viewpost.jsp?thread=118828

int value; int location; boolean overwrite;
doIt(value=13, location=47, overwrite=true);

Es detallado en el sitio de la llamada, pero en general ofrece la sobrecarga más baja.


3
Bien por la baja sobrecarga, pero se siente tan hack. Probablemente voy a usar el método Builder () para los casos en los que hay muchos argumentos.
Gattster

23
Creo que esto pierde completamente el sentido de los parámetros con nombre. (que es tener algo asociado nombres con valores ). No hay indicación alguna si inviertes el orden. En lugar de hacer esto, le aconsejaría que simplemente agregue un comentario:doIt( /*value*/ 13, /*location*/ 47, /*overwrite*/ true )
Scheintod

20

Estilo Java 8:

public class Person {
    String name;
    int age;

    private Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    static PersonWaitingForName create() {
        return name -> age -> new Person(name, age);
    }

    static interface PersonWaitingForName {
        PersonWaitingForAge name(String name);
    }

    static interface PersonWaitingForAge {
        Person age(int age);
    }

    public static void main(String[] args) {

        Person charlotte = Person.create()
            .name("Charlotte")
            .age(25);

    }
}
  • parámetros con nombre
  • arreglar el orden de los argumentos
  • comprobación estática -> no es posible una persona sin nombre
  • Es difícil cambiar argumentos del mismo tipo por accidente (como es posible en los constructores de telescop)

3
bonito. Qué lástima que no tenga un orden de argumentos variable. (Pero no quiero decir que usaría esto ...)
Scheintod

1
Esta es una idea brillante. La definición de create()me detuvo en seco. Nunca he visto ese estilo de encadenamiento lambda en Java. ¿Descubrió esta idea por primera vez en otro idioma con lambdas?
kevinarpe

2
Se llama currying: en.wikipedia.org/wiki/Currying . Por cierto: Tal vez sea una idea inteligente, pero no recomendaría este estilo de argumentos con nombre. Lo probé en un proyecto real con muchos argumentos y conduce a un código difícil de leer y de navegar.
Alex

Eventualmente, Java recibirá parámetros con nombre de estilo Visual Basic. Java no lo hizo antes porque C ++ no lo hace. Pero llegaremos allí eventualmente. Yo diría que el 90% del polimorfismo de Java es simplemente piratear parámetros opcionales.
Tuntable

7

Java no admite parámetros con nombre tipo Objective-C para constructores o argumentos de método. Además, esta no es realmente la forma en que Java hace las cosas. En java, el patrón típico es clases y miembros con nombres prolijos. Las clases y variables deben ser sustantivos y los métodos nombrados deben ser verbos. Supongo que podría ser creativo y desviarse de las convenciones de nomenclatura de Java y emular el paradigma Objective-C de una manera hacky, pero esto no sería particularmente apreciado por el desarrollador promedio de Java encargado de mantener su código. Cuando trabajes en cualquier idioma, te conviene ceñirte a las convenciones del idioma y la comunidad, especialmente cuando trabajas en equipo.


4
+1: por el consejo sobre cómo ceñirse a los modismos del idioma que está utilizando actualmente. ¡Piense en las otras personas que necesitarán leer su código!
Stephen C

3
Elegí tu respuesta porque creo que tienes un buen punto. Si tuviera que adivinar por qué obtuviste el voto negativo, probablemente sea porque esto no responde a la pregunta. P: "¿Cómo hago parámetros con nombre en Java?" R: "No es así"
Gattster

12
He votado en contra porque creo que su respuesta no tiene nada que ver con la pregunta. Los nombres detallados no resuelven realmente el problema del orden de los parámetros. Sí, podría codificarlos en el nombre, pero eso claramente no es factible. Sacar a relucir un paradigma no relacionado no explica por qué no se admite un paradigma.
Andreas Mueller

7

Si está utilizando Java 6, puede utilizar los parámetros variables e importar estática para producir un resultado mucho mejor. Los detalles de esto se encuentran en:

http://zinzel.blogspot.com/2010/07/creating-methods-with-named-parameters.html

En resumen, podrías tener algo como:

go();
go(min(0));
go(min(0), max(100));
go(max(100), min(0));
go(prompt("Enter a value"), min(0), max(100));

2
Me gusta, pero solo resuelve la mitad del problema. En Java, no puede evitar la transposición accidental de parámetros sin perder una verificación en tiempo de compilación de los valores requeridos.
cdunn2001

Sin seguridad de tipos, esto es peor que los simples comentarios //.
Peter Davis

7

Me gustaría señalar que este estilo aborda tanto el parámetro nombrado como las características de propiedades sin el prefijo get y set que tienen otros idiomas. No es convencional en el ámbito de Java pero es más simple, no es difícil de entender, especialmente si ha manejado otros lenguajes.

public class Person {
   String name;
   int age;

   // name property
   // getter
   public String name() { return name; }

   // setter
   public Person name(String val)  { 
    name = val;
    return this;
   }

   // age property
   // getter
   public int age() { return age; }

   // setter
   public Person age(int val) {
     age = val;
     return this;
   }

   public static void main(String[] args) {

      // Addresses named parameter

      Person jacobi = new Person().name("Jacobi").age(3);

      // Addresses property style

      println(jacobi.name());
      println(jacobi.age());

      //...

      jacobi.name("Lemuel Jacobi");
      jacobi.age(4);

      println(jacobi.name());
      println(jacobi.age());
   }
}

6

Qué pasa

public class Tiger {
String myColor;
int    myLegs;

public Tiger color(String s)
{
    myColor = s;
    return this;
}

public Tiger legs(int i)
{
    myLegs = i;
    return this;
}
}

Tiger t = new Tiger().legs(4).color("striped");

5
Builder es mucho mejor, porque puede verificar algunas restricciones en build (). Pero también prefiero argumentos más cortos sin set / con prefijo.
rkj

4
Además, el patrón del constructor es mejor porque le permite hacer que la clase construida (Tiger en este caso) sea inmutable.
Jeff Olson

2

Puede utilizar un constructor habitual y métodos estáticos que den un nombre a los argumentos:

public class Something {

    String name;
    int size; 
    float weight;

    public Something(String name, int size, float weight) {
        this.name = name;
        this.size = size;
        this.weight = weight;
    }

    public static String name(String name) { 
        return name; 
    }

    public static int size(int size) {
        return size;
    }

    public float weight(float weight) {
        return weight;
    }

}

Uso:

import static Something.*;

Something s = new Something(name("pen"), size(20), weight(8.2));

Limitaciones en comparación con los parámetros con nombre reales:

  • el orden de los argumentos es relevante
  • las listas de argumentos de variables no son posibles con un solo constructor
  • necesitas un método para cada argumento
  • no es realmente mejor que un comentario (algo nuevo ( /*name*/ "pen", /*size*/ 20, /*weight*/ 8.2))

Si tiene la opción, mire Scala 2.8. http://www.scala-lang.org/node/2075


2
Una desventaja de este enfoque es que debe obtener los argumentos en el orden correcto. El código anterior le permitiría escribir: Algo s = nuevo Algo (nombre ("bolígrafo"), tamaño (20), tamaño (21)); Además, este enfoque no le ayuda a evitar escribir argumentos opcionales.
Matt Quail

1
Votaría esto para el análisis: not really better than a comment... por otro lado ...;)
Scheintod

2

Usando las lambdas de Java 8, puede acercarse aún más a los parámetros con nombre reales .

foo($ -> {$.foo = -10; $.bar = "hello"; $.array = new int[]{1, 2, 3, 4};});

Tenga en cuenta que esto probablemente viola un par de docenas de "mejores prácticas de Java" (como cualquier cosa que haga uso del $símbolo).

public class Main {
  public static void main(String[] args) {
    // Usage
    foo($ -> {$.foo = -10; $.bar = "hello"; $.array = new int[]{1, 2, 3, 4};});
    // Compare to roughly "equivalent" python call
    // foo(foo = -10, bar = "hello", array = [1, 2, 3, 4])
  }

  // Your parameter holder
  public static class $foo {
    private $foo() {}

    public int foo = 2;
    public String bar = "test";
    public int[] array = new int[]{};
  }

  // Some boilerplate logic
  public static void foo(Consumer<$foo> c) {
    $foo foo = new $foo();
    c.accept(foo);
    foo_impl(foo);
  }

  // Method with named parameters
  private static void foo_impl($foo par) {
    // Do something with your parameters
    System.out.println("foo: " + par.foo + ", bar: " + par.bar + ", array: " + Arrays.toString(par.array));
  }
}

Pros:

  • Considerablemente más corto que cualquier patrón de construcción que haya visto hasta ahora
  • Funciona tanto para métodos como para constructores
  • Completamente seguro
  • Se parece mucho a los parámetros con nombre reales en otros lenguajes de programación
  • Es tan seguro como su patrón de constructor típico (puede establecer parámetros varias veces)

Contras:

  • Tu jefe probablemente te linchará por esto
  • Es más difícil saber qué está pasando

1
Contras: los campos son públicos y no definitivos. Si está de acuerdo con esto, ¿por qué no usar setters? ¿Cómo funciona con los métodos?
Alex

Podría usar setters, pero ¿cuál es el punto? Simplemente alarga el código y eso eliminaría el beneficio de hacerlo así. La asignación no tiene efectos secundarios y los setters son cajas negras. $foonunca se escapa a la persona que llama (a menos que alguien lo asigne a una variable dentro de la devolución de llamada) entonces, ¿por qué no pueden ser públicos?
Vic

2

Puede usar la anotación @Builder del proyecto Lombok para simular parámetros con nombre en Java. Esto generará un constructor para usted que puede usar para crear nuevas instancias de cualquier clase (tanto las clases que ha escrito como las que provienen de bibliotecas externas).

Así es como habilitarlo en una clase:

@Getter
@Builder
public class User {
    private final Long id;
    private final String name;
}

Luego puede usar esto por:

User userInstance = User.builder()
    .id(1L)
    .name("joe")
    .build();

Si desea crear un constructor de este tipo para una clase que proviene de una biblioteca, cree un método estático anotado como este:

class UserBuilder {
    @Builder(builderMethodName = "builder")
    public static LibraryUser newLibraryUser(Long id, String name) {
        return new LibraryUser(id, name);
    }
  }

Esto generará un método llamado "constructor" que puede ser llamado por:

LibraryUser user = UserBuilder.builder()
    .id(1L)
    .name("joe")
    .build();

Google auto / value tiene un propósito similar pero usa el marco de procesamiento de anotaciones, que es mucho más seguro (las cosas seguirán funcionando después de una actualización de la JVM) que la manipulación del código de bytes del proyecto Lombocks.
René

1
Creo que el valor / auto de Google requiere un poco más de una milla adicional en comparación con el uso de Lombok. El enfoque de Lombok es más o menos compatible con la escritura tradicional de JavaBean (por ejemplo, puede crear una instancia a través de nuevos campos, que se muestran correctamente en un depurador, etc.). Tampoco soy un gran admirador del código de bytes manpiluation + solución de complemento IDE que usa Lombok, pero tengo que admitir que en la práctica funciona bien. Hasta ahora no hay problemas con los cambios de versión de JDK, la reflexión, etc.
Istvan Devai

Si eso es verdad. Para auto / value, debe proporcionar una clase abstracta que luego se implementará. Lombok necesita mucho menos código. Por tanto, es necesario comparar los pros y los contras.
René

2

Siento que la "solución de comentario" merece su propia respuesta (oculta en las respuestas existentes y mencionada en los comentarios aquí).

someMethod(/* width */ 1024, /* height */ 768);

1

Esta es una variante del BuilderPatrón descrito por Lawrence anteriormente.

Me encuentro usando esto mucho (en los lugares apropiados).

La principal diferencia es que en este caso el Constructor es inmutable . Esto tiene la ventaja de que se puede reutilizar y es seguro para subprocesos.

Entonces puedes usar esto para hacer un constructor predeterminado y luego, en los distintos lugares donde lo necesite, puede configurarlo y construir su objeto.

Esto tiene más sentido si está construyendo el mismo objeto una y otra vez, porque entonces puede hacer que el constructor sea estático y no tiene que preocuparse por cambiar su configuración.

Por otro lado, si tiene que construir objetos con parámetros cambiantes, esto tiene un poco de sobrecarga. (pero bueno, puede combinar la generación estática / dinámica con buildmétodos personalizados )

Aquí está el código de ejemplo:

public class Car {

    public enum Color { white, red, green, blue, black };

    private final String brand;
    private final String name;
    private final Color color;
    private final int speed;

    private Car( CarBuilder builder ){
        this.brand = builder.brand;
        this.color = builder.color;
        this.speed = builder.speed;
        this.name = builder.name;
    }

    public static CarBuilder with() {
        return DEFAULT;
    }

    private static final CarBuilder DEFAULT = new CarBuilder(
            null, null, Color.white, 130
    );

    public static class CarBuilder {

        final String brand;
        final String name;
        final Color color;
        final int speed;

        private CarBuilder( String brand, String name, Color color, int speed ) {
            this.brand = brand;
            this.name = name;
            this.color = color;
            this.speed = speed;
        }
        public CarBuilder brand( String newBrand ) {
            return new CarBuilder( newBrand, name, color, speed );
        }
        public CarBuilder name( String newName ) {
            return new CarBuilder( brand, newName, color, speed );
        }
        public CarBuilder color( Color newColor ) {
            return new CarBuilder( brand, name, newColor, speed );
        }
        public CarBuilder speed( int newSpeed ) {
            return new CarBuilder( brand, name, color, newSpeed );
        }
        public Car build() {
            return new Car( this );
        }
    }

    public static void main( String [] args ) {

        Car porsche = Car.with()
                .brand( "Porsche" )
                .name( "Carrera" )
                .color( Color.red )
                .speed( 270 )
                .build()
                ;

        // -- or with one default builder

        CarBuilder ASSEMBLY_LINE = Car.with()
                .brand( "Jeep" )
                .name( "Cherokee" )
                .color( Color.green )
                .speed( 180 )
                ;

        for( ;; ) ASSEMBLY_LINE.build();

        // -- or with custom default builder:

        CarBuilder MERCEDES = Car.with()
                .brand( "Mercedes" )
                .color( Color.black )
                ;

        Car c230 = MERCEDES.name( "C230" ).speed( 180 ).build(),
            clk = MERCEDES.name( "CLK" ).speed( 240 ).build();

    }
}

1

Cualquier solución en Java es probable que va a ser bastante detallado, pero vale la pena mencionar que las herramientas como Google autovalores y inmutables generarán clases constructoras de forma automática utilizando JDK procesamiento de anotación de tiempo de compilación.

Para mi caso, quería que los parámetros con nombre se usaran en una enumeración de Java, por lo que un patrón de constructor no funcionaría porque otras clases no pueden crear instancias de enumeración. Se me ocurrió un enfoque similar a la respuesta de @ deamon, pero agrega la verificación en tiempo de compilación del orden de los parámetros (a expensas de más código)

Aquí está el código de cliente:

Person p = new Person( age(16), weight(100), heightInches(65) );

Y la implementación:

class Person {
  static class TypedContainer<T> {
    T val;
    TypedContainer(T val) { this.val = val; }
  }
  static Age age(int age) { return new Age(age); }
  static class Age extends TypedContainer<Integer> {
    Age(Integer age) { super(age); }
  }
  static Weight weight(int weight) { return new Weight(weight); }
  static class Weight extends TypedContainer<Integer> {
    Weight(Integer weight) { super(weight); }
  }
  static Height heightInches(int height) { return new Height(height); }
  static class Height extends TypedContainer<Integer> {
    Height(Integer height) { super(height); }
  }

  private final int age;
  private final int weight;
  private final int height;

  Person(Age age, Weight weight, Height height) {
    this.age = age.val;
    this.weight = weight.val;
    this.height = height.val;
  }
  public int getAge() { return age; }
  public int getWeight() { return weight; }
  public int getHeight() { return height; }
}

0

Vale la pena considerar el idioma admitido por la biblioteca karg :

class Example {

    private static final Keyword<String> GREETING = Keyword.newKeyword();
    private static final Keyword<String> NAME = Keyword.newKeyword();

    public void greet(KeywordArgument...argArray) {
        KeywordArguments args = KeywordArguments.of(argArray);
        String greeting = GREETING.from(args, "Hello");
        String name = NAME.from(args, "World");
        System.out.println(String.format("%s, %s!", greeting, name));
    }

    public void sayHello() {
        greet();
    }

    public void sayGoodbye() {
        greet(GREETING.of("Goodbye");
    }

    public void campItUp() {
        greet(NAME.of("Sailor");
    }
}

Esto parece ser básicamente lo mismo que la R Casharespuesta pero sin el código para explicarlo.
Scheintod

-1

@irreputable se le ocurrió una buena solución. Sin embargo, podría dejar su instancia de Class en un estado no válido, ya que no se realizará ninguna validación ni verificación de coherencia. Por lo tanto, prefiero combinar esto con la solución Builder, evitando que se cree la subclase adicional, aunque todavía sería una subclase de la clase Builder. Además, debido a que la clase de constructor adicional lo hace más detallado, agregué un método más usando un lambda. Agregué algunos de los otros enfoques del constructor para completar.

Comenzando con una clase de la siguiente manera:

public class Foo {
  static public class Builder {
    public int size;
    public Color color;
    public String name;
    public Builder() { size = 0; color = Color.RED; name = null; }
    private Builder self() { return this; }

    public Builder size(int size) {this.size = size; return self();}
    public Builder color(Color color) {this.color = color; return self();}
    public Builder name(String name) {this.name = name; return self();}

    public Foo build() {return new Foo(this);}
  }

  private final int size;
  private final Color color;
  private final String name;

  public Foo(Builder b) {
    this.size = b.size;
    this.color = b.color;
    this.name = b.name;
  }

  public Foo(java.util.function.Consumer<Builder> bc) {
    Builder b = new Builder();
    bc.accept(b);
    this.size = b.size;
    this.color = b.color;
    this.name = b.name;
  }

  static public Builder with() {
    return new Builder();
  }

  public int getSize() { return this.size; }
  public Color getColor() { return this.color; }  
  public String getName() { return this.name; }  

}

Luego, usando esto aplicando los diferentes métodos:

Foo m1 = new Foo(
  new Foo.Builder ()
  .size(1)
  .color(BLUE)
  .name("Fred")
);

Foo m2 = new Foo.Builder()
  .size(1)
  .color(BLUE)
  .name("Fred")
  .build();

Foo m3 = Foo.with()
  .size(1)
  .color(BLUE)
  .name("Fred")
  .build();

Foo m4 = new Foo(
  new Foo.Builder() {{
    size = 1;
    color = BLUE;
    name = "Fred";
  }}
);

Foo m5 = new Foo(
  (b)->{
    b.size = 1;
    b.color = BLUE;
    b.name = "Fred";
  }
);

Parece, en parte, una estafa total de lo que @LaurenceGonsalves ya publicó, pero verá la pequeña diferencia en la convención elegida.

Me pregunto, si JLS alguna vez implementara parámetros con nombre, ¿cómo lo harían? ¿Se extenderían a uno de los modismos existentes al proporcionar un soporte de forma abreviada para él? Además, ¿cómo admite Scala los parámetros con nombre?

Hmmm, suficiente para investigar y tal vez una nueva pregunta.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.