面向对象编程练习
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
关联。
因此,因为您将b1
、a1
和a2
声明为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
关键字后面的类型)。
b1
是B
的一个实例,所以b1.f()
会调用B
上定义的f
方法,即B.f()
。
i1
也是B
的一个实例,所以i1.f()
也会调用B
上定义的f
方法,即B.f()
。
i2
是A
的一个实例,所以i2.f()
会调用A
上定义的f
方法,即A.f()
。
那么a2.f()
呢?如上所述,a2
是B
的一个实例(您使用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();
然后你实现两个IRecordRetriever
s,一个从数据库中检索记录,而另一个从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 设计模式的资料。
【讨论】:
以上是关于面向对象编程练习的主要内容,如果未能解决你的问题,请参考以下文章