面向对象编程练习

Posted

技术标签:

【中文标题】面向对象编程练习【英文标题】:Object Oriented Programming exercise 【发布时间】:2015-04-16 12:34:51 【问题描述】:

我正在复习考试并参加了这个复习练习。我有答案,但我一生都无法解决这个问题。谁能解释发生了什么?

interface I1 
 public void f();

class A implements I1 
 public void f() 
  //implementation
 
 public static void g()
  //implementation
 

class B extends A 
 public void f() 
//implementation
 
 public static void g()
 //implementation
 

假设

I1 i1=new B();

I1 i2=new A();

A a1= 新 A();

A a2=新 B();

A b1= (A) 新 B();

不写任何代码,用文字解释每一个的效果 以下方法调用。只需说“这将调用 f 方法在 A" 等中实现。

b1.f();

b1.g();

i1.f();

a1.g();

i2.f();

a2.g();

【问题讨论】:

要自己找出答案,您可以简单地在每个实现中添加一个 print 语句,例如“方法 f 在 A 中实现”,您可以使用它来找出发生了什么。 【参考方案1】:

g方法解析

注意f 是一个实例方法,而g 是一个静态方法。静态方法与class关联。

因此,因为您将b1a1a2 声明为A,所以调用b1.g()a1.g()a2.g() 都会调用A.g()

这是关于 Java 静态方法的另一个很好的 SO 问题:Why doesn't the compiler complain when I try to override a static method?

f方法解析

Java 实例方法默认为virtual 方法,可以被子类覆盖。所以f()实际上做了什么取决于实例是什么(即取决于new关键字后面的类型)。

b1B的一个实例,所以b1.f()会调用B上定义的f方法,即B.f()

i1也是B的一个实例,所以i1.f()也会调用B上定义的f方法,即B.f()

i2A的一个实例,所以i2.f()会调用A上定义的f方法,即A.f()

那么a2.f()呢?如上所述,a2B 的一个实例(您使用new 关键字创建它),因此a2.f() 将调用B.f()

更多关于方法覆盖

您可能想知道为什么它们会这样工作?为什么a2.f() 不是设计为在Java 中调用A.f()?原因如下:

假设您有一个模块可以对记录列表进行复杂计算。这些记录可能是从数据库中检索到的,也可能是从第三方 rest api 提供的 web api 中检索到的。如何让你的模块尽可能的可扩展?

这是你的模块代码:

public class MyModule 
    public void doComplexWork() 
        
        // Where to get the records?
        List<Record> records = ???;
        
        // Do complex computation here
    

一种可扩展的方法是将接口(或抽象类)定义为“记录检索”的契约

public interface IRecordRetriever 
    List<Record> retrieveRecords();

然后你实现两个IRecordRetrievers,一个从数据库中检索记录,而另一个从rest api中检索记录。

public class DatabaseRecordRetriever implements IRecordRetriever 
    @Override
    public List<Record> retrieveRecords() 
        // Connect database and fetch database records here
    


public class RestApiRecordRetriever implements IRecordRetriever 
    @Override
    public List<Record> retrieveRecords() 
        // Access the rest api to fetch records here
     

现在让我们稍微修改一下我们的模块代码:

public class MyModule 
    
    private IRecordRetriever _recordRetriever;
    
    // Inject IRecordRetriever from contructor
    public MyModule(IRecordRetriever retriever) 
        _recordRetriever = retriever;
    
    
    public void doComplexWork() 
        
        // Where to get the records?
        // From the IRecordRetriever
        List<Record> records = _recordRetriever.retrieveRecords();
        
        // Do complex computation here
    

现在我们的MyModule 可以扩展了。因为无论从哪里检索记录,MyModule 中的代码都不需要更改。如果现在您从数据库中检索记录,但后来您决定更改为从rest api 检索记录,那么MyModule 中的任何内容都不需要更改!您只需要更改传递给MyModule 构造函数的运行时IRecordRetriever 实例。

为什么我们可以实现这种可扩展性?这是因为方法覆盖。我们将私有字段_recordRetriever 声明为IRecordRetriever,因此实际行为取决于实际运行时实例(由new 关键字创建)。因此,要更改记录检索行为,我们只需更改传递给MyModule 构造函数的运行时IRecordRetriever 实例。如果方法覆盖不可用,那就是:

IRecordRetriever 检索器 = new DatabaseRecordRetriever(); 检索器.retrieveRecords();

如果对retriever.retrieveRecords()的调用不是调用DatabaseRecordRetriever上定义的retrieveRecords()方法,而是调用IRecordRetriever上定义的retrieveRecords()方法,我们无法实现扩展性。 (当然,IRecordRetriever 是一个接口,所以你不能调用IRecordRetriever.retrieveRecords(),但这也适用于虚方法)。

我上面提到的内容也适用于其他面向对象的编程语言。

我通过构造函数将IRecordRetriever 传递给MyModule 的方式称为Constructor Dependency Injection。您可以阅读更多有关面向对象编程、方法覆盖和 GoF 设计模式的资料。

【讨论】:

以上是关于面向对象编程练习的主要内容,如果未能解决你的问题,请参考以下文章

面向对象练习题

python--面向对象编程之学生选课系统练习

Java 面向对象编程小练习(曾经)

python 练习 30

面向对象和网络编程练习题

面向对象编程(含java练习题)