Làm thế nào để triển khai thành ngữ Tham số được đặt tên trong Java? (đặc biệt đối với các nhà xây dựng)
Tôi đang tìm một cú pháp giống Objective-C và không giống cú pháp được sử dụng trong JavaBeans.
Một ví dụ mã nhỏ sẽ ổn.
Cảm ơn.
Làm thế nào để triển khai thành ngữ Tham số được đặt tên trong Java? (đặc biệt đối với các nhà xây dựng)
Tôi đang tìm một cú pháp giống Objective-C và không giống cú pháp được sử dụng trong JavaBeans.
Một ví dụ mã nhỏ sẽ ổn.
Cảm ơn.
Câu trả lời:
Thành ngữ Java tốt nhất mà tôi có vẻ để mô phỏng các đối số từ khóa trong các hàm tạo là mẫu Builder, được mô tả trong Phiên bản Java thứ 2 hiệu quả .
Ý tưởng cơ bản là có một lớp Builder có các bộ định tuyến (nhưng thường không phải bộ chuyển đổi) cho các tham số phương thức khởi tạo khác nhau. Ngoài ra còn có một build()
phương pháp. Lớp Builder thường là một lớp lồng nhau (tĩnh) của lớp mà nó được sử dụng để xây dựng. Phương thức khởi tạo của lớp bên ngoài thường là private.
Kết quả cuối cùng trông giống như sau:
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...
}
Để tạo một phiên bản Foo, sau đó bạn viết một cái gì đó như:
Foo foo = Foo.builder()
.setColor(red)
.setName("Fred")
.setSize(42)
.build();
Những lưu ý chính là:
Bạn cũng có thể muốn xem bài đăng trên blog này (không phải của tôi).
.withFoo
, chứ không phải là .setFoo
: newBuilder().withSize(1).withName(1).build()
hơnnewBuilder().setSize(1).setName(1).build()
There's no compile-time checking that all of the parameters have been specified exactly once.
Vấn đề này có thể được khắc phục bằng cách trả lại giao diện Builder1
để BuilderN
nơi mỗi bìa hoặc một trong những setters hoặc build()
. Nó dài dòng hơn nhiều để viết mã, nhưng nó đi kèm với hỗ trợ trình biên dịch cho DSL của bạn và làm cho tự động hoàn thành rất tốt để làm việc.
Đây là điều đáng nói:
Foo foo = new Foo() {{
color = red;
name = "Fred";
size = 42;
}};
cái gọi là bộ khởi tạo dấu ngoặc kép . Nó thực sự là một lớp ẩn danh với bộ khởi tạo cá thể.
Bạn cũng có thể thử làm theo lời khuyên từ đây: http://www.artima.com/weblogs/viewpost.jsp?thread=118828
int value; int location; boolean overwrite;
doIt(value=13, location=47, overwrite=true);
Nó dài dòng trên trang web cuộc gọi, nhưng nhìn chung cho chi phí thấp nhất.
doIt( /*value*/ 13, /*location*/ 47, /*overwrite*/ true )
Kiểu 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);
}
}
create()
đã ngăn tôi theo dõi. Tôi chưa bao giờ thấy kiểu chuỗi lambda đó trong Java. Lần đầu tiên bạn phát hiện ra ý tưởng này bằng một ngôn ngữ khác với lambdas?
Java không hỗ trợ các tham số có tên giống Objective-C cho các hàm tạo hoặc đối số phương thức. Hơn nữa, đây thực sự không phải là cách hoạt động của Java. Trong java, mẫu điển hình là các lớp và thành viên được đặt tên chi tiết. Các lớp và biến phải là danh từ và phương thức được đặt tên là động từ. Tôi cho rằng bạn có thể sáng tạo và đi ngược lại các quy ước đặt tên của Java và mô phỏng theo mô hình Objective-C theo cách khó hiểu nhưng điều này sẽ không được các nhà phát triển Java trung bình chịu trách nhiệm duy trì mã của bạn đánh giá cao. Khi làm việc bằng bất kỳ ngôn ngữ nào, bạn cần tuân thủ các quy ước của ngôn ngữ và cộng đồng, đặc biệt là khi làm việc theo nhóm.
Nếu bạn đang sử dụng Java 6, bạn có thể sử dụng các tham số biến và nhập tĩnh để tạo ra kết quả tốt hơn nhiều. Chi tiết về điều này được tìm thấy trong:
http://zinzel.blogspot.com/2010/07/creating-methods-with-name-parameters.html
Trong ngắn hạn, bạn có thể có một cái gì đó như:
go();
go(min(0));
go(min(0), max(100));
go(max(100), min(0));
go(prompt("Enter a value"), min(0), max(100));
Tôi muốn chỉ ra rằng kiểu này giải quyết cả tham số được đặt tên và các tính năng thuộc tính mà không có tiền tố get và set mà ngôn ngữ khác có. Nó không thông thường trong lĩnh vực Java nhưng đơn giản hơn, không khó hiểu, đặc biệt nếu bạn đã xử lý các ngôn ngữ khác.
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());
}
}
Thế còn
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");
Bạn có thể sử dụng một hàm tạo thông thường và các phương thức tĩnh để đặt tên cho các đối số:
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;
}
}
Sử dụng:
import static Something.*;
Something s = new Something(name("pen"), size(20), weight(8.2));
Hạn chế so với các tham số được đặt tên thực:
/*name*/ "pen", /*size*/ 20, /*weight*/ 8.2)
)Nếu bạn có sự lựa chọn, hãy xem Scala 2.8. http://www.scala-lang.org/node/2075
not really better than a comment
... mặt khác ...;)
Sử dụng lambdas của Java 8, bạn có thể tiến gần hơn đến các tham số được đặt tên thực .
foo($ -> {$.foo = -10; $.bar = "hello"; $.array = new int[]{1, 2, 3, 4};});
Xin lưu ý rằng điều này có thể vi phạm một vài "phương pháp hay nhất của java" (như bất kỳ thứ gì sử dụng $
biểu tượng).
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));
}
}
Ưu điểm:
Nhược điểm:
$foo
không bao giờ thoát cho người gọi (trừ khi ai đó gán nó cho một biến bên trong lệnh gọi lại), vậy tại sao chúng không thể công khai?
Bạn có thể sử dụng chú thích @Builder của dự án Lombok để mô phỏng các tham số được đặt tên trong Java. Điều này sẽ tạo ra một trình tạo cho bạn mà bạn có thể sử dụng để tạo các phiên bản mới của bất kỳ lớp nào (cả các lớp bạn đã viết và những lớp đến từ các thư viện bên ngoài).
Đây là cách kích hoạt nó trên một lớp:
@Getter
@Builder
public class User {
private final Long id;
private final String name;
}
Sau đó, bạn có thể sử dụng nó bằng cách:
User userInstance = User.builder()
.id(1L)
.name("joe")
.build();
Nếu bạn muốn tạo một Bộ dựng như vậy cho một lớp đến từ thư viện, hãy tạo một phương thức tĩnh có chú thích như sau:
class UserBuilder {
@Builder(builderMethodName = "builder")
public static LibraryUser newLibraryUser(Long id, String name) {
return new LibraryUser(id, name);
}
}
Điều này sẽ tạo ra một phương thức có tên "người xây dựng" có thể được gọi bằng:
LibraryUser user = UserBuilder.builder()
.id(1L)
.name("joe")
.build();
Tôi cảm thấy như "comment-workaround" xứng đáng là câu trả lời của riêng nó (ẩn trong các câu trả lời hiện có và được đề cập trong phần nhận xét ở đây).
someMethod(/* width */ 1024, /* height */ 768);
Đây là một biến thể của Builder
Mẫu như đã được Lawrence mô tả ở trên.
Tôi thấy mình sử dụng cái này rất nhiều (ở những nơi thích hợp).
Sự khác biệt chính là, trong trường hợp này, Builder có thể miễn dịch . Điều này có ưu điểm là nó có thể được tái sử dụng và an toàn cho các chủ đề.
Vì vậy, bạn có thể sử dụng nó để tạo một Builder mặc định và sau đó ở những nơi khác nhau mà bạn cần, bạn có thể cấu hình nó và xây dựng đối tượng của mình.
Điều này hoàn toàn hợp lý, nếu bạn đang xây dựng lặp đi lặp lại cùng một đối tượng, vì khi đó bạn có thể làm cho trình tạo tĩnh và không phải lo lắng về việc thay đổi cài đặt của nó.
Mặt khác, nếu bạn phải xây dựng các đối tượng với paramaters thay đổi, điều này có một số chi phí yên tĩnh. (nhưng này, bạn có thể kết hợp tạo tĩnh / động với các build
phương thức tùy chỉnh )
Đây là mã ví dụ:
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();
}
}
Bất kỳ giải pháp nào trong Java có thể sẽ khá dài dòng, nhưng điều đáng nói là các công cụ như Google AutoValues và Immutable sẽ tự động tạo các lớp trình tạo cho bạn bằng cách sử dụng xử lý chú thích thời gian biên dịch JDK.
Đối với trường hợp của tôi, tôi muốn các tham số được đặt tên để sử dụng trong một enum Java, vì vậy một mẫu trình tạo sẽ không hoạt động vì các trường hợp enum không thể được khởi tạo bởi các lớp khác. Tôi đã nghĩ ra một cách tiếp cận tương tự câu trả lời của @ deamon nhưng thêm kiểm tra thời gian biên dịch của thứ tự tham số (với chi phí nhiều mã hơn)
Đây là mã khách hàng:
Person p = new Person( age(16), weight(100), heightInches(65) );
Và việc thực hiệ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; }
}
Thành ngữ được hỗ trợ bởi thư viện karg có thể đáng xem xét:
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");
}
}
R Casha
answer nhưng không có mã để giải thích nó.
@irreputable đã đưa ra một giải pháp hay. Tuy nhiên - nó có thể khiến cá thể Lớp của bạn ở trạng thái không hợp lệ, vì sẽ không có việc xác thực và kiểm tra tính nhất quán. Do đó, tôi muốn kết hợp điều này với giải pháp Builder, tránh việc tạo thêm lớp con, mặc dù nó vẫn sẽ là lớp con của lớp người xây dựng. Ngoài ra, vì lớp trình tạo thêm làm cho nó dài dòng hơn, tôi đã thêm một phương thức nữa bằng cách sử dụng lambda. Tôi đã thêm một số phương pháp tiếp cận trình xây dựng khác để hoàn thiện.
Bắt đầu với một lớp như sau:
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; }
}
Sau đó, sử dụng điều này áp dụng các phương pháp khác nhau:
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";
}
);
Có vẻ như một phần nào đó là sự tách rời hoàn toàn từ những gì @LaurenceGonsalves đã đăng, nhưng bạn sẽ thấy sự khác biệt nhỏ trong quy ước được chọn.
Tôi tự hỏi, nếu JLS sẽ triển khai các tham số được đặt tên, thì họ sẽ làm như thế nào? Liệu họ có đang mở rộng một trong những thành ngữ hiện có bằng cách cung cấp hỗ trợ dạng ngắn cho nó không? Ngoài ra Scala hỗ trợ các tham số được đặt tên như thế nào?
Hmmm - đủ để nghiên cứu, và có thể là một câu hỏi mới.