【问题标题】:Returning an interface object from a method without instantiating an (unknown) concrete type从方法返回接口对象而不实例化(未知)具体类型
【发布时间】:2016-07-18 22:29:10
【问题描述】:

我确信对此有一些解决方案或模式,但我无法清楚地表达问题以找到正确的答案。

我有一个接口,ICar:

public interface ICar {
void SetMake(String make);
String GetMake();
}

我有课,汽车:

public class Car implements ICar {
private String make;

public void SetMake(String make) { this.make = make; }

public String GetMake() { return make; }

我在另一个类中有一个方法,它不知道 Car。本着多态性的精神,我希望这个方法返回一个 ICar,以便有人可以创建自己的 ICar 实现并将该方法用于他们的汽车:

...//class preamble
public ICar myMethod() {
    ICar myCar = new ICar();
    myCar.SetMake("Ferrari");
    return myCar;
}

我不确定为什么这样的事情不起作用。由于 ICar 包含方法 SetMake,因此每个实现也必须包含此方法。当然,jvm 可以以某种方式将方法调用排队,然后在方法的具体化可用时运行它们?

另一个想法是将新创建的 ICar 实现作为参数传递给 myMethod 并调用它的方法,但我觉得这可能不是一个干净的实现,因为我每次都需要创建 Car 的实例.此外,如果没有无参数构造函数,我将不得不用虚拟数据(也不干净)来实例化它。

我觉得我对如何实现这样的事情缺乏一些了解,并且想知道是否有人可以提供帮助?

谢谢。

【问题讨论】:

    标签: java design-patterns interface polymorphism


    【解决方案1】:

    一种方法是提供类类型作为参数,并使其成为泛型:

    public <T extends ICar> T myMethod(Class<T> type) {
        try {
            T myCar = type.newInstance(); // T needs a visible no-args constructor
            myCar.SetMake("Ferrari");
            return myCar;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
    

    (另请注意,我通过将任何异常包装在 RuntimeException 中来处理异常)

    那么你需要这样称呼它:

    Car car = myMethod(Car.class);
    

    更灵活的方法是引入工厂接口,将工厂实例传递给myMethod,后者使用它来创建具体的ICar。工厂仍然可以是通用的:

    public interface ICarFactory<T extends ICar> {
        T createCar();
    }
    
    ...
    
    public <T extends ICar> T myMethod(ICarFactory<T> factory) {
        T myCar = factory.createCar();
        myCar.SetMake("Ferrari");
        return myCar;
    }
    
    ...
    
    Car car = myMethod(carFactory);
    

    【讨论】:

    • 谢谢,我喜欢这个答案,因为它不那么明显并展示了实现。我想我会走工厂路线。
    【解决方案2】:

    你需要做的:

    public ICar myMethod() {
        ICar myCar = new Car();
        myCar.SetMake("Ferrari");
        return myCar;
    }
    

    而不是

    public ICar myMethod() {
        ICar myCar = new ICar();
        myCar.SetMake("Ferrari");
        myCar.return;
    }
    

    这个声明:myCar.return; 在这里几乎没有意义......因为 ICar 是一个接口,所以你不能这样做 ICar myCar = new ICar()

    【讨论】:

    • new ICar(); 不会编译,因为它是一个接口。
    • 抱歉,myCar.return 出错了。但是,这并不能解决问题,因为 myMethod 无法知道 Car 类型。
    【解决方案3】:

    您无法创建新界面,但可以返回一个。

    public ICar myMethod() {
        ICar myCar = new Car();
        myCar.SetMake("Ferrari");
        return myCar;
    }
    

    接口意味着一个合同,它基本上是在说“这是一个ICar,它可以是CarRedCar 和一千种不同的实现,但你不知道是哪个”。 这就是多态性的意思,用myMethod的人根本不知道自己开的是哪辆车,只能知道contract,不知道具体是怎么实现的。

    【讨论】:

    • 在这种情况下 myMethod() 不知道 Car 类。我想做类似Car myCar = myMethod() 的事情,其中​​在 myCar 上调用 SetMake(),而不使用依赖注入。这甚至可能是不可能的,这就是我问的原因。
    • 您缺少接口和多态性这一点。如果你想使用Car myCar = myMethod(),你可以通过将签名更改为返回Car而不是ICar', BUT lets say that tomorrow you decide to change myMethod()`来创建新的RedCar,所以现在你需要更改签名以及使用myMethod的人可以在很多地方。通过使用ICar 的返回值,如果您更改myMethod 以创建RedCar,则您正在解决此问题(称为封装),所有代码都保持不变,因为RedCar implements ICar 这是使用接口的方式。跨度>
    【解决方案4】:

    不太确定我是否完全理解您的需求,但我认为您真正想做的是从抽象基类继承您的 Car,以便其他人可以实现其他类型的车辆。

    类似这样的:

    public abstract class Vehicle {
        public abstract void SetMake(String make);
        public abstract String GetMake();
    }
    
    public class Car extends Vehicle {
        private String make;
        public void SetMake(String make) { this.make = make; }
        public String GetMake() { return make; }
    }
    
    public class Truck extends Vehicle {
        private String make;
        public void SetMake(String make) { this.make = make; }
        public String GetMake() { return make; }
    }
    
    
    
    public Vehicle myMethod() {
        Vehicle myCar = new Truck();
        myCar.SetMake("Ferrari");
        return myCar;
    }    
    

    【讨论】:

    • 没错,可以采用这种层次结构,但我仍然会创建接口 IVehicle,并让 myMethod() 返回它。这个例子还在两个子类中重复了String make,它可以在基类中处理。这里的方法仍然将一个编程留给特定的实现。
    【解决方案5】:

    您不能实例化接口,因此new ICar() 不是一个选项。比如 setMake 应该设置一个实例变量,但是一个 Interface 没有实例变量。

    但是,您可以执行以下操作:

            return new ICar() {
            String m;
            @Override
            public void setMake(String make)
            {
                m = make;
            }
    
    
    
            @Override
            public String getMake()
            {
                return m;
            }
        };
    

    通过这种方式,您将创建一个实现接口的对象。

    虽然这个答案给出了一个特定的例子来回答这个问题(就像简单地实例化一个 Car 对象一样),但关于接口的更大问题是人们可以返回在接口中实现的任何对象。除了允许委托创建对象之外,还有许多设计模式。例如,可以考虑Factory Pattern

    【讨论】:

    • 返回后是否可以“转换”为 Car 类型?所以我可以这样称呼 myMethod:Car newCar = myMethod();?
    • @RicketyRick 不,该方法返回interface,而不是特定的类。所以,电话是ICar newCar = myMethod()。一般用Program to Interfaces, Not Implementations比较好,所以最好用接口方法,而不是具体的类实现。
    • 在 myMethod 不知道具体的 Car 类型的情况下,有什么方法可以做 Car newCar = myMethod(); 吗? (除了传递一辆新车作为参数)
    • @RicketyRick,不,因为Car 是一个特定的类。更好的做法是使用interface ICar 而不是特定的类Car。但是,我对您使用“类型”一词的方式做出了一些可能不正确的假设。我相信最好的方法是对 myMethod 使用工厂模式,返回 ICar,并允许工厂返回 ICar 的具体/具体实例化。
    【解决方案6】:

    对于你所说的myMethod()方法

    “本着多态性的精神,我希望这个方法返回一个 ICar,以便有人可以创建自己的 ICar 实现并将该方法用于他们的汽车:”

    据我了解,您希望 myMethod() 与不同具体类型的 ICar 一起工作。

    使用new operator 时会创建对象。您现有的代码将无法编译,因为您无法实例化接口的对象。要修复编译错误,我不希望您创建具体的汽车对象:

    ICar car = new Car(); // this will solve compile error but avoid doing this in your code
    

    避免使用new 运算符,让您的代码用户使用 new 并注入具体实现(您甚至可能不知道具体实现,因为它们取决于用户,您的代码的用户)。

    改变方法如下

    ...//class preamble
    public ICar myMethod(ICar myCar, String make) {
        myCar.SetMake(make);
        return myCar;
    }
    

    我建议你学习:DIP principledependency injectioncode to interface

    【讨论】:

    • 感谢您的回答。这是我能想到的唯一方法,但很好奇是否有一种方法可以在没有依赖注入的情况下做到这一点。
    • @RicketyRick,我建议你保持代码简单。 【参考KISS原理】。记住简单是很难实现的,如果你拥有它,那么不要试图破坏它,否则你会破坏其他东西。还有语言,OOP 的观点:接口是抽象的,你怎么能从抽象中创建任何对象,甚至期望 JVM 来创建它。实现取决于我们的需要和我们在实现过程中放入抽象方法体的代码。要么您创建新的具体类型对象,要么让用户创建它们并注入您的代码。
    • JVM 无法理解我们的业务需求、用例、需求,我们有责任为它们提供实现。我们不知道未来的需求,抽象方法让我们有机会稍后通过向它们提供实现来根据我们的特定业务需求添加实现。
    • 谢谢。在看到我写下的问题后,我想我的意思是:有没有办法延迟具体类的实例化/操作,所以如果你做了Car car = myMethod(),它会在汽车上执行SetMake()。但我喜欢你保持简单的建议。我将如何通知用户注入依赖项? Java 中的 C# 中是否有与 out 等价的东西?
    • 汽车 car = myMethod();这意味着 myMethod() 有责任创建 Car 的对象。在创建之前,您无法操作任何对象,这就像吃蛋糕一样,一旦拥有就可以吃。如果您尝试在创建之前进行操作,则会抛出明显的 NullPointerException。您的第二个问题,是的,您以后可以很好地修改对象。只需使用 new 创建对象,然后根据您的要求设置其属性,就像在您的情况下,您可以根据需要随时设置 Car 的品牌,
    猜你喜欢
    • 2020-06-09
    • 2014-01-11
    • 2016-03-15
    • 1970-01-01
    • 1970-01-01
    • 2020-02-24
    • 2018-12-15
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多