我有java接口和类实现,在调用类似的行为时需要不同的参数。以下哪项最适合?
在第一个选项中,我有不同的类从基接口继承常见行为,所有差异只在类中直接实现,而不是在接口中实现。这个似乎最合适,但我必须在代码中进行手动类型转换。
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);
}
}
这里我使用泛型以便在接口中使用通用方法,并且在每个类实现中决定参数类型。这里的问题是我有未经检查的绘画调用。这与选项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方法,只有在构造了对象后才可以调用它。
到目前为止,这看起来是最好的选择,因为它在下面的帖子中提出:
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);
}
}
答案 0 :(得分:8)
更好的选择是删除drive
方法的重载版本,并传递构造函数中子类所需的任何信息:
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
的类,其中包含vehcileColor
,{{1}等属性并更改trailerColor
方法以使参数类型为paint
。
PaintSpecification
上述所有方法的优势在于,所有interface Vehicle{
void drive();
void paint(PaintSpecification spec);
}
实施都遵循单一合同,允许您执行操作,例如将所有Vehicle
个实例添加到Vehicle
并调用无论其类型如何,都会逐一List
方法。
答案 1 :(得分:1)
但我必须在代码中进行手动类型转换。
这是因为你丢失了你显然需要的类型信息。
您的客户端代码取决于具体的类型信息,因为您的绘制方法取决于具体类型。
如果您的客户端代码不应该知道具体的Vehicle
类型,那么Vehicle
接口应该以不需要具体类型信息的方式设计。 E.g。
public void paint();
这也意味着每个Vehicle
实例必须拥有绘制自身所需的所有信息。因此,您应该实现 color
属性。
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;
}
}
你还能做什么?
如果您希望保持代码不变,则必须以某种方式重新创建类型信息。
我看到以下解决方案:
<强>适配器点模式强>
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);
}
适配器模式的优点
您将instanceof
检查从客户端代码移动到适配器中。因此,客户端代码将更加重构安全。例如。想象一下以下客户端代码:
if(vehicle instanceof Car){
// ...
} else if(vehicle instanceof TruckWithTrailer){
// ...
}
想一想如果将代码重构为TruckWithTrailer extends Car
适配器不得自行返回。具体的Vehicle
可以实例化另一个让它看起来像适配器类型的对象。
public <T extends Vehicle> T getAdapter(Class<T> adapterClass) {
if(Car.class.isAssignableFrom(adapterClass)){
return new CarAdapter(this)
}
return null;
}
适配器模式的缺点
Vehicle
(很多if-else语句)实现时,cyclomatic complexity客户端代码会增加。<强>观众点模式强>
interface Vehicle {
public void accept(VehicleVisitor vehicleVisitor);
}
interface VehicleVisitor {
public void visit(Car car);
public void visit(TruckWithTrailer truckWithTrailer);
}
然后汽车的实现将决定应该调用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);
}
});
访客模式的优点
访客模式的缺点
PS:有关代码上下文的更多信息,可能还有其他解决方案。