具有可变参数类型的接口方法
Interface methods with variable argument types
我有 java 接口和 class 实现,它们在调用类似行为时需要不同的参数。以下哪项最合适?
在第一个选项中,我让不同的 classes 从基本接口继承了共同的行为,所有差异仅直接在 classes 中实现,而不是在接口中实现。这个似乎最合适,但我必须在代码中进行手动类型转换。
public class VaryParam1 {
static Map<VehicleType, Vehicle> list = new HashMap<>();
static List<Car> carsList = new ArrayList<>();
static List<TruckWithTrailer> trucksList = new ArrayList<>();
public static void main(String[] args) {
list.put(VehicleType.WITHOUT_TRAILER, new Car());
list.put(VehicleType.WITH_TRAILER, new TruckWithTrailer());
//violates LSP?
((Car)list.get(VehicleType.WITHOUT_TRAILER)).paint(1); //ok - but needed manual cast
((TruckWithTrailer)list.get(VehicleType.WITH_TRAILER)).paint(1, "1"); //ok - but needed manual cast
carsList.add(new Car());
trucksList.add(new TruckWithTrailer());
//Does not violate LSP
carsList.get(0).paint(1);
trucksList.get(0).paint(1, "1");
}
}
enum VehicleType {
WITHOUT_TRAILER,
WITH_TRAILER;
}
interface Vehicle{
//definition of all common methods
void drive();
void stop();
}
class Car implements Vehicle {
public void paint(int vehicleColor) {
System.out.println(vehicleColor);
}
@Override
public void drive() {}
@Override
public void stop() {}
}
class TruckWithTrailer implements Vehicle {
public void paint(int vehicleColor, String trailerColor) {
System.out.println(vehicleColor + trailerColor);
}
@Override
public void drive() {}
@Override
public void stop() {}
}
在第二个选项中,我将方法向上移动了一层到接口,但现在我需要使用 UnsupportedOpException 来实现行为。这看起来像代码味道。在代码中,我不必进行手动转换,但我也有可能调用将在 运行 时间内产生异常的方法 - 无需编译时检查。这不是什么大问题 - 只有这个方法看起来像代码味道。这种实施方式是最佳实践吗?
public class VaryParam2 {
static Map<VehicleType, Vehicle> list = new HashMap<>();
public static void main(String[] args) {
list.put(VehicleType.WITHOUT_TRAILER, new Car());
list.put(VehicleType.WITH_TRAILER, new TruckWithTrailer());
list.get(VehicleType.WITHOUT_TRAILER).paint(1); //works
list.get(VehicleType.WITH_TRAILER).paint(1, "1"); //works
list.get(VehicleType.WITHOUT_TRAILER).paint(1, "1"); //ok - exception - passing trailer when no trailer - no compile time check!
list.get(VehicleType.WITH_TRAILER).paint(1); //ok - exception - calling trailer without trailer args - no compile time check!
}
}
enum VehicleType {
WITHOUT_TRAILER,
WITH_TRAILER;
}
interface Vehicle{
void paint(int vehicleColor);
void paint(int vehicleColor, String trailerColor); //code smell - not valid for all vehicles??
}
class Car implements Vehicle {
@Override
public void paint(int vehicleColor) {
System.out.println(vehicleColor);
}
@Override
public void paint(int vehicleColor, String trailerColor) { //code smell ??
throw new UnsupportedOperationException("Car has no trailer");
}
}
class TruckWithTrailer implements Vehicle {
@Override
public void paint(int vehicleColor) { //code smell ??
throw new UnsupportedOperationException("What to do with the trailer?");
}
@Override
public void paint(int vehicleColor, String trailerColor) {
System.out.println(vehicleColor + trailerColor);
}
}
这里我使用泛型是为了在接口中有通用的方法,参数类型在每个class实现中决定。这里的问题是我有未经检查的绘画调用。这与选项 1 中直接转换的问题差不多。但是在这里我也有可能调用我不应该调用的方法!
public class VaryParam3 {
static Map<VehicleType, Vehicle> list = new HashMap<>();
public static void main(String[] args) {
list.put(VehicleType.WITHOUT_TRAILER, new Car());
list.put(VehicleType.WITH_TRAILER, new TruckWithTrailer());
list.get(VehicleType.WITHOUT_TRAILER).paint(new VehicleParam()); //works but unchecked call
list.get(VehicleType.WITH_TRAILER).paint(new TruckWithTrailerParam()); //works but unchecked call
list.get(VehicleType.WITHOUT_TRAILER).paint(new TruckWithTrailerParam()); //works but should not!
list.get(VehicleType.WITH_TRAILER).paint(new VehicleParam()); //ClassCastException in runtime - ok but no compile time check
}
}
enum VehicleType {
WITHOUT_TRAILER,
WITH_TRAILER;
}
class VehicleParam {
int vehicleColor;
}
class TruckWithTrailerParam extends VehicleParam {
String trailerColor;
}
interface Vehicle<T extends VehicleParam>{
void paint(T param);
}
class Car implements Vehicle<VehicleParam> {
@Override
public void paint(VehicleParam param) {
System.out.println(param.vehicleColor);
}
}
class TruckWithTrailer implements Vehicle<TruckWithTrailerParam> {
@Override
public void paint(TruckWithTrailerParam param) {
System.out.println(param.vehicleColor + param.trailerColor);
}
}
所以问题是 - 这 3 个选项中哪一个是最好的(或者如果还有其他我没有找到的选项)?在进一步维护、更换等方面
更新
我更新了问题,现在我有了只能在构造对象后才能调用的 paint 方法。
到目前为止,这看起来像是下面 post 中建议的最佳选择:
public class VaryParam4 {
static Map<VehicleType, Vehicle> list = new HashMap<>();
public static void main(String[] args) {
list.put(VehicleType.WITHOUT_TRAILER, new Car());
list.put(VehicleType.WITH_TRAILER, new TruckWithTrailer());
list.get(VehicleType.WITHOUT_TRAILER).paint(new PaintConfigObject()); //works but can pass trailerColor (even if null) that is not needed
list.get(VehicleType.WITH_TRAILER).paint(new PaintConfigObject()); //works
}
}
enum VehicleType {
WITHOUT_TRAILER,
WITH_TRAILER;
}
class PaintConfigObject {
int vehicleColor;
String trailerColor;
}
interface Vehicle{
void paint(PaintConfigObject param);
}
class Car implements Vehicle {
@Override
public void paint(PaintConfigObject param) {
//param.trailerColor will never be used here but it's passed in param
System.out.println(param.vehicleColor);
}
}
class TruckWithTrailer implements Vehicle {
@Override
public void paint(PaintConfigObject param) {
System.out.println(param.vehicleColor + param.trailerColor);
}
}
更好的选择是摆脱 重载 版本的 drive
方法,并传递子 classes 所需的任何信息在构造函数中改为:
interface Vehicle{
void drive();
}
class Car implements Vehicle {
private int numberOfDoors;
public Car(int numberOfDoors) {
this.numberOfDoors = numberOfDoors;
}
public void drive() {
System.out.println(numberOfDoors);
}
}
class TruckWithTrailer implements Vehicle {
private int numberOfDoors;
private int numberOfTrailers;
public TruckWithTrailer(int numberOfDoors,numberOfTrailers) {
this.numberOfDoors = numberOfDoors;
this.numberOfTrailers = numberOfTrailers;
}
@Override
public void drive() {
System.out.println(numberOfDoors + numberOfTrailers);
}
}
针对您对运行时决定的 paint
的评论,您可以向采用可变参数的车辆添加一个 paint
方法:
interface Vehicle{
void drive();
void paint(String ...colors);
}
正如评论中所讨论的,如果在 paint 方法中使用的参数数量因车辆类型而异,请定义一个名为 PaintSpecification
的 class,其中包含 [=17] 等属性=], trailerColor
并将 paint
方法更改为具有类型 PaintSpecification
的参数。
interface Vehicle{
void drive();
void paint(PaintSpecification spec);
}
上述所有方法的优点是所有 Vehicle
实现都遵循单一契约,允许您执行诸如将所有 Vehicle
实例添加到 List
和无论它们的类型如何,都对它们一一调用 paint
方法。
but I have to do manual type-cast in the code.
这是因为您丢失了显然需要的类型信息。
您的客户端代码取决于具体类型信息,因为您的绘制方法取决于具体类型。
如果您的客户端代码不应该知道具体的 Vehicle
类型,则 Vehicle
接口应该设计成不需要具体类型信息的方式。例如
public void paint();
这也意味着每个 Vehicle
实例必须具有绘制自身所需的所有信息。因此你应该给实现 color
properties.
public class Car implements Vehicle {
private int color = 0; // Default color
public void paint() {
System.out.println(color);
}
public void setColor(int color){
// maybe some validation first
this.color = color;
}
}
你还能做什么?
如果您想保持代码不变,您必须以某种方式重新创建类型信息。
我看到以下解决方案:
- instanceof 检查 downcast(你已经试过了)
- Adapter-Pattern
- Visitor-Pattern
Adapter-Pattern
interface Vehicle {
public <T extends Vehicle> T getAdapter(Class<T> adapterClass);
}
class Car implements Vehicle {
@Override
public <T extends Vehicle> T getAdapter(Class<T> adapterClass) {
if(adapterClass.isInstance(this)){
return adapterClass.cast(this);
}
return null;
}
}
您的客户端代码将如下所示:
Vehicle vehicle = ...;
Car car = vehicle.getAdapter(Car.class);
if(car != null){
// the vehicle can be adapted to a car
car.paint(1);
}
Adapter-Pattern
的优点
您将 instanceof
检查从客户端代码移到适配器中。因此客户端代码将更多refactoring-safe。例如。想象以下客户端代码:
if(vehicle instanceof Car){
// ...
} else if(vehicle instanceof TruckWithTrailer){
// ...
}
想想如果将代码重构为 TruckWithTrailer extends Car
会发生什么
适配器不能 return 本身。具体 Vehicle
可能会实例化另一个对象,让它看起来像适配器类型。
public <T extends Vehicle> T getAdapter(Class<T> adapterClass) {
if(Car.class.isAssignableFrom(adapterClass)){
return new CarAdapter(this)
}
return null;
}
Adapter-Pattern
的缺点
- 当您添加越来越多的
Vehicle
实现(很多 if-else 语句)时,客户端代码的 cyclomatic complexity 会增加。
Visitor-Pattern
interface Vehicle {
public void accept(VehicleVisitor vehicleVisitor);
}
interface VehicleVisitor {
public void visit(Car car);
public void visit(TruckWithTrailer truckWithTrailer);
}
car 的实现将决定应该调用 VihicleVisitor
的哪个方法。
class Car implements Vehicle {
public void paint(int vehicleColor) {
System.out.println(vehicleColor);
}
@Override
public void accept(VehicleVisitor vehicleVisitor) {
vehicleVisitor.visit(this);
}
}
然后您的客户端代码必须提供 VehicleVisitor
Vehicle vehicle = ...;
vehicle.accept(new VehicleVisitor() {
public void visit(TruckWithTrailer truckWithTrailer) {
truckWithTrailer.paint(1, "1");
}
public void visit(Car car) {
car.paint(1);
}
});
Visitor-Pattern
的优点
- 在不同的方法中分离类型特定的逻辑
Visitor-Pattern
的缺点
- 新类型需要更改访问者界面,并且访问者的所有实现也必须更改。
PS:有了有关代码上下文的更多信息,可能会有其他解决方案。
我有 java 接口和 class 实现,它们在调用类似行为时需要不同的参数。以下哪项最合适?
在第一个选项中,我让不同的 classes 从基本接口继承了共同的行为,所有差异仅直接在 classes 中实现,而不是在接口中实现。这个似乎最合适,但我必须在代码中进行手动类型转换。
public class VaryParam1 {
static Map<VehicleType, Vehicle> list = new HashMap<>();
static List<Car> carsList = new ArrayList<>();
static List<TruckWithTrailer> trucksList = new ArrayList<>();
public static void main(String[] args) {
list.put(VehicleType.WITHOUT_TRAILER, new Car());
list.put(VehicleType.WITH_TRAILER, new TruckWithTrailer());
//violates LSP?
((Car)list.get(VehicleType.WITHOUT_TRAILER)).paint(1); //ok - but needed manual cast
((TruckWithTrailer)list.get(VehicleType.WITH_TRAILER)).paint(1, "1"); //ok - but needed manual cast
carsList.add(new Car());
trucksList.add(new TruckWithTrailer());
//Does not violate LSP
carsList.get(0).paint(1);
trucksList.get(0).paint(1, "1");
}
}
enum VehicleType {
WITHOUT_TRAILER,
WITH_TRAILER;
}
interface Vehicle{
//definition of all common methods
void drive();
void stop();
}
class Car implements Vehicle {
public void paint(int vehicleColor) {
System.out.println(vehicleColor);
}
@Override
public void drive() {}
@Override
public void stop() {}
}
class TruckWithTrailer implements Vehicle {
public void paint(int vehicleColor, String trailerColor) {
System.out.println(vehicleColor + trailerColor);
}
@Override
public void drive() {}
@Override
public void stop() {}
}
在第二个选项中,我将方法向上移动了一层到接口,但现在我需要使用 UnsupportedOpException 来实现行为。这看起来像代码味道。在代码中,我不必进行手动转换,但我也有可能调用将在 运行 时间内产生异常的方法 - 无需编译时检查。这不是什么大问题 - 只有这个方法看起来像代码味道。这种实施方式是最佳实践吗?
public class VaryParam2 {
static Map<VehicleType, Vehicle> list = new HashMap<>();
public static void main(String[] args) {
list.put(VehicleType.WITHOUT_TRAILER, new Car());
list.put(VehicleType.WITH_TRAILER, new TruckWithTrailer());
list.get(VehicleType.WITHOUT_TRAILER).paint(1); //works
list.get(VehicleType.WITH_TRAILER).paint(1, "1"); //works
list.get(VehicleType.WITHOUT_TRAILER).paint(1, "1"); //ok - exception - passing trailer when no trailer - no compile time check!
list.get(VehicleType.WITH_TRAILER).paint(1); //ok - exception - calling trailer without trailer args - no compile time check!
}
}
enum VehicleType {
WITHOUT_TRAILER,
WITH_TRAILER;
}
interface Vehicle{
void paint(int vehicleColor);
void paint(int vehicleColor, String trailerColor); //code smell - not valid for all vehicles??
}
class Car implements Vehicle {
@Override
public void paint(int vehicleColor) {
System.out.println(vehicleColor);
}
@Override
public void paint(int vehicleColor, String trailerColor) { //code smell ??
throw new UnsupportedOperationException("Car has no trailer");
}
}
class TruckWithTrailer implements Vehicle {
@Override
public void paint(int vehicleColor) { //code smell ??
throw new UnsupportedOperationException("What to do with the trailer?");
}
@Override
public void paint(int vehicleColor, String trailerColor) {
System.out.println(vehicleColor + trailerColor);
}
}
这里我使用泛型是为了在接口中有通用的方法,参数类型在每个class实现中决定。这里的问题是我有未经检查的绘画调用。这与选项 1 中直接转换的问题差不多。但是在这里我也有可能调用我不应该调用的方法!
public class VaryParam3 {
static Map<VehicleType, Vehicle> list = new HashMap<>();
public static void main(String[] args) {
list.put(VehicleType.WITHOUT_TRAILER, new Car());
list.put(VehicleType.WITH_TRAILER, new TruckWithTrailer());
list.get(VehicleType.WITHOUT_TRAILER).paint(new VehicleParam()); //works but unchecked call
list.get(VehicleType.WITH_TRAILER).paint(new TruckWithTrailerParam()); //works but unchecked call
list.get(VehicleType.WITHOUT_TRAILER).paint(new TruckWithTrailerParam()); //works but should not!
list.get(VehicleType.WITH_TRAILER).paint(new VehicleParam()); //ClassCastException in runtime - ok but no compile time check
}
}
enum VehicleType {
WITHOUT_TRAILER,
WITH_TRAILER;
}
class VehicleParam {
int vehicleColor;
}
class TruckWithTrailerParam extends VehicleParam {
String trailerColor;
}
interface Vehicle<T extends VehicleParam>{
void paint(T param);
}
class Car implements Vehicle<VehicleParam> {
@Override
public void paint(VehicleParam param) {
System.out.println(param.vehicleColor);
}
}
class TruckWithTrailer implements Vehicle<TruckWithTrailerParam> {
@Override
public void paint(TruckWithTrailerParam param) {
System.out.println(param.vehicleColor + param.trailerColor);
}
}
所以问题是 - 这 3 个选项中哪一个是最好的(或者如果还有其他我没有找到的选项)?在进一步维护、更换等方面
更新
我更新了问题,现在我有了只能在构造对象后才能调用的 paint 方法。
到目前为止,这看起来像是下面 post 中建议的最佳选择:
public class VaryParam4 {
static Map<VehicleType, Vehicle> list = new HashMap<>();
public static void main(String[] args) {
list.put(VehicleType.WITHOUT_TRAILER, new Car());
list.put(VehicleType.WITH_TRAILER, new TruckWithTrailer());
list.get(VehicleType.WITHOUT_TRAILER).paint(new PaintConfigObject()); //works but can pass trailerColor (even if null) that is not needed
list.get(VehicleType.WITH_TRAILER).paint(new PaintConfigObject()); //works
}
}
enum VehicleType {
WITHOUT_TRAILER,
WITH_TRAILER;
}
class PaintConfigObject {
int vehicleColor;
String trailerColor;
}
interface Vehicle{
void paint(PaintConfigObject param);
}
class Car implements Vehicle {
@Override
public void paint(PaintConfigObject param) {
//param.trailerColor will never be used here but it's passed in param
System.out.println(param.vehicleColor);
}
}
class TruckWithTrailer implements Vehicle {
@Override
public void paint(PaintConfigObject param) {
System.out.println(param.vehicleColor + param.trailerColor);
}
}
更好的选择是摆脱 重载 版本的 drive
方法,并传递子 classes 所需的任何信息在构造函数中改为:
interface Vehicle{
void drive();
}
class Car implements Vehicle {
private int numberOfDoors;
public Car(int numberOfDoors) {
this.numberOfDoors = numberOfDoors;
}
public void drive() {
System.out.println(numberOfDoors);
}
}
class TruckWithTrailer implements Vehicle {
private int numberOfDoors;
private int numberOfTrailers;
public TruckWithTrailer(int numberOfDoors,numberOfTrailers) {
this.numberOfDoors = numberOfDoors;
this.numberOfTrailers = numberOfTrailers;
}
@Override
public void drive() {
System.out.println(numberOfDoors + numberOfTrailers);
}
}
针对您对运行时决定的 paint
的评论,您可以向采用可变参数的车辆添加一个 paint
方法:
interface Vehicle{
void drive();
void paint(String ...colors);
}
正如评论中所讨论的,如果在 paint 方法中使用的参数数量因车辆类型而异,请定义一个名为 PaintSpecification
的 class,其中包含 [=17] 等属性=], trailerColor
并将 paint
方法更改为具有类型 PaintSpecification
的参数。
interface Vehicle{
void drive();
void paint(PaintSpecification spec);
}
上述所有方法的优点是所有 Vehicle
实现都遵循单一契约,允许您执行诸如将所有 Vehicle
实例添加到 List
和无论它们的类型如何,都对它们一一调用 paint
方法。
but I have to do manual type-cast in the code.
这是因为您丢失了显然需要的类型信息。
您的客户端代码取决于具体类型信息,因为您的绘制方法取决于具体类型。
如果您的客户端代码不应该知道具体的 Vehicle
类型,则 Vehicle
接口应该设计成不需要具体类型信息的方式。例如
public void paint();
这也意味着每个 Vehicle
实例必须具有绘制自身所需的所有信息。因此你应该给实现 color
properties.
public class Car implements Vehicle {
private int color = 0; // Default color
public void paint() {
System.out.println(color);
}
public void setColor(int color){
// maybe some validation first
this.color = color;
}
}
你还能做什么?
如果您想保持代码不变,您必须以某种方式重新创建类型信息。
我看到以下解决方案:
- instanceof 检查 downcast(你已经试过了)
- Adapter-Pattern
- Visitor-Pattern
Adapter-Pattern
interface Vehicle {
public <T extends Vehicle> T getAdapter(Class<T> adapterClass);
}
class Car implements Vehicle {
@Override
public <T extends Vehicle> T getAdapter(Class<T> adapterClass) {
if(adapterClass.isInstance(this)){
return adapterClass.cast(this);
}
return null;
}
}
您的客户端代码将如下所示:
Vehicle vehicle = ...;
Car car = vehicle.getAdapter(Car.class);
if(car != null){
// the vehicle can be adapted to a car
car.paint(1);
}
Adapter-Pattern
的优点您将
instanceof
检查从客户端代码移到适配器中。因此客户端代码将更多refactoring-safe。例如。想象以下客户端代码:if(vehicle instanceof Car){ // ... } else if(vehicle instanceof TruckWithTrailer){ // ... }
想想如果将代码重构为
TruckWithTrailer extends Car
会发生什么
适配器不能 return 本身。具体
Vehicle
可能会实例化另一个对象,让它看起来像适配器类型。public <T extends Vehicle> T getAdapter(Class<T> adapterClass) { if(Car.class.isAssignableFrom(adapterClass)){ return new CarAdapter(this) } return null; }
Adapter-Pattern
的缺点- 当您添加越来越多的
Vehicle
实现(很多 if-else 语句)时,客户端代码的 cyclomatic complexity 会增加。
Visitor-Pattern
interface Vehicle {
public void accept(VehicleVisitor vehicleVisitor);
}
interface VehicleVisitor {
public void visit(Car car);
public void visit(TruckWithTrailer truckWithTrailer);
}
car 的实现将决定应该调用 VihicleVisitor
的哪个方法。
class Car implements Vehicle {
public void paint(int vehicleColor) {
System.out.println(vehicleColor);
}
@Override
public void accept(VehicleVisitor vehicleVisitor) {
vehicleVisitor.visit(this);
}
}
然后您的客户端代码必须提供 VehicleVisitor
Vehicle vehicle = ...;
vehicle.accept(new VehicleVisitor() {
public void visit(TruckWithTrailer truckWithTrailer) {
truckWithTrailer.paint(1, "1");
}
public void visit(Car car) {
car.paint(1);
}
});
Visitor-Pattern
的优点- 在不同的方法中分离类型特定的逻辑
Visitor-Pattern
的缺点- 新类型需要更改访问者界面,并且访问者的所有实现也必须更改。
PS:有了有关代码上下文的更多信息,可能会有其他解决方案。