Q9:如何让表达“是一个”和“有一个”关系?或者请解释下“继承”和“组合”。组合和聚合之间有什么区别?

A9:“是一个”的关系表示继承而“有一个”的关系是表示组合。继承和组合都允许你将子对象放入新类中。代码重用的两个主要技术便是类继承和对象组合。

继承是单向的。例如房子是一栋建筑,但建筑不是一个房子。继承使用extends关键字。

组合:用于表达房子有一个浴室。说房子是一个浴室就不准确了。组合简单地使用实例变量引用其他对象,如House类拥有一个实例变量,引用一个Bathroom对象。

Q:哪一个更好?组合还是继承?

A:指南是,仅当子类“是一个”父类时,才使用继承。

  • 不要仅仅为了代码重用而使用继承。如果没有“是一个”的关系,就应该使用组合语法。过度使用实现继承(使用“扩展”关键字)会破坏所有的子类,特别在超类需要被修改的时候。
  • 不要仅仅为了获得多态性而使用继承。如果没有“是一个”的关系,而你又想要获得多态性,那么,使用接口继承和组合语法。

Q:组合和聚合有什么区别?

A:它们都表达整体和局部的关系。聚合关系,局部可以独立于整体而存在。例如,一个行项目和产品是整体和局部的关系。如果一个行项目被删除,对应的产品不需要被删除。所以聚合是一种较弱的关系。组合关系,局部不可独立于整体而存在。如果一个整体被删除,那么所有零件也会被删除。例如,订单和项目是整体和局部的关系。如果一个订单被删除,那么相应的行项目也应该被删除。所以组合具有更强的关系。

Q10:你怎么理解继承、封装、多态和动态绑定?

A10:Polymorphism多态——描述了这样一种能力,一个给定类型的变量可以被用来引用多个类型不同的对象(当然,需要这些类型是给定类型的子类),而调用的却是这个变量引用的对象的具体类型上的方法。简而言之,多态是一种自下而上的方法调用。多态的好处是,很容易添加新的扩展类而不破坏原有的调用代码。当给一个对象发送一条消息(调用方法)时,你甚至不知道这个对象的具体类型,但是正确的行为会发生,这就是多态。

面向对象编程语言实现多态的过程称为动态绑定。(运行期类型推断。)

Inheritance继承——是将基类的行为(即方法)和状态(即变量)包含到派生类中,这样它们就可在派生类中被访问了。关键的好处是,它提供了代码重用的正式机制。

任何业务逻辑的公共部分都可以从派生类移至基类,重构时这样做,可以避免代码重复而提高代码的可维护性。

现有的类被称为基类而派生类被称为子类。继承也可以被定义为一个过程,即对象获得一个或多个其他对象的特征的过程,就像孩子从父母那里获得特征一样。

有两种类型的继承:

1、实作继承:可以通过继承部分或全部父类中已经实现的功能来扩展程序。在Java中,您只可以从一个超类继承。实作继承提升了重用性,但是不正确的继承使用可能导致编程噩梦,因为它会破坏封装性并且为将来的变化带来问题。使用实作继承,子类变得和父类紧密耦合起来。这将使得设计变得脆弱,如果你想改变父类,就不得不了解子类的细节以免破坏他们。所以使用实作继承,确保子类只依赖父类的行为,而不是实际的实现。

2、接口继承:接口提供了一种机制,将无关的类联系起来——通过指定系列普通方法,这些实现类都必须包含。(实现类之间可以是互不相关的。)接口继承提升了“面向接口编程而不是面向实现编程”的原则。这样降低了系统之间的耦合。在Java中,你可以实现任意数量的接口。这比“实作继承”更灵活,因为它不会把你锁定在具体实现中,具体实现会使子类变得难以维护。也要小心,修改接口会破坏实现类。

Which one to use?优先选择接口,因为它符合“面向接口编程”的理念并且可以降低耦合。接口继承可以在对象组合的帮助下实现代码的重用。如果你看GOF设计模式,你会发现他们更偏爱接口继承而不是实作继承。

实作继承案例:

package ch08_extends3;

/**
* 假设活期存款和定期存款在存取行为上有类型的行为,我们把这两个行为的实现定义在父类中。
* <p>但是活期存款和定期存款在计算利息这个行为上表现是不同的。
* @author zhengwei 2013-7-13
*/
public abstract class Account {
public void deposit (double amount) {
System.out.println("depositing " + amount);
} public void withdraw (double amount) {
System.out.println ("withdrawing " + amount);
} public abstract double calculateInterest (double amount);
} class SavingsAccount extends Account { public double calculateInterest (double amount) {
// calculate interest for SavingsAccount
return amount * 0.03;
} public void deposit (double amount) {
super.deposit (amount); // get code reuse
// do something else
} public void withdraw (double amount) {
super.withdraw (amount); // get code reuse
// do something else
}
} class TermDepositAccount extends Account { public double calculateInterest (double amount) {
// calculate interest for SavingsAccount
return amount * 0.05;
} public void deposit(double amount) {
super.deposit (amount); // get code reuse
// do something else
} public void withdraw(double amount) {
super.withdraw (amount); // get code reuse
// do something else
}
}

接口继承案例:

package ch08_extends3;

/**
* 接口继承示例代码,使用组合来重用代码。
* <p>在下例中,deposite和withdraw方法共享了AccountHelper中的代码片段。
* <p>而calculateInterest方法在各自实现中有独特的实现
* @author zhengwei 2013-7-13
*/
public interface Account {
public abstract double calculateInterest(double amount); public abstract void deposit(double amount); public abstract void withdraw(double amount);
} interface AccountHelper {
public abstract void deposit(double amount); public abstract void withdraw(double amount);
} /**
* class AccountHelperImpl has reusable code as methods deposit (double amount)
* and withdraw (double amount).
* <p>AccountHelperImpl含有可重用代码:deposit方法和withdraw方法
*/
class AccountHelperImpl implements AccountHelper {
public void deposit(double amount) {
System.out.println("depositing " + amount);
} public void withdraw(double amount) {
System.out.println("withdrawing " + amount);
} } class SavingsAccountImpl implements Account {
// composed helper class (i.e. composition ).
AccountHelper helper = new AccountHelperImpl(); public double calculateInterest(double amount) {
// calculate interest for SavingsAccount
return amount * 0.03;
} public void deposit(double amount) {
helper.deposit(amount); // code reuse via composition
} public void withdraw(double amount) {
helper.withdraw(amount); // code reuse via composition
}
} class TermDepositAccountImpl implements Account { // composed helper class (i.e. composition ).
AccountHelper helper = new AccountHelperImpl(); public double calculateInterest(double amount) {
// calculate interest for SavingsAccount
return amount * 0.05;
} public void deposit(double amount) {
helper.deposit(amount); // code reuse via composition
} public void withdraw(double amount) {
helper.withdraw(amount); // code reuse via composition
} }

两种方式可以使用如下的测试代码:

package ch08_extends3;

/**
*
* @author zhengwei 2013-7-13
*/
public class Test {
public static void main(String[] args) {
Account acc1 = new SavingsAccountImpl();
acc1.deposit(50.0); Account acc2 = new TermDepositAccountImpl();
acc2.deposit(25.0); acc1.withdraw(25);
acc2.withdraw(10); double cal1 = acc1.calculateInterest(100.0);
double cal2 = acc2.calculateInterest(100.0); System.out.println("Savings --> " + cal1);
System.out.println("TermDeposit --> " + cal2);
}
}

输出结果:

depositing 50.0
depositing 25.0
withdrawing 25.0
withdrawing 10.0
Savings --> 3.0
TermDeposit -->  5.0

问:为什么优先通过组合来重用代码而不是继承?

答:可以看到两种方式都可利用多态,并重用了代码,结果也是一致的,但是:

  • 类继承的优点是,它的重用是在编译时静态地完成的,是易于使用的。类继承的缺点也是因为它是静态的,从父类继承而来的实现在运行期不能被改变。而在对象组合中,(组合进来的)功能是在运行期动态获得的,通过对象收集其他对象的引用来达成。这种方法的优点是,组合进来的“实作对象”在运行时是可以更换的。这是因为我们依赖的是对象的接口类型,调用对象也只有通过他们的接口,所以一个对象可以被替换为另一个,只要他们有相同的类型(接口)。例如:组合进来的类型AccountHelperImpl可以在有需要时被替换为一个更有效率的实现:
public class  EfficientAccountHelperImpl  implements AccountHelper  {
public void deposit(double amount) {
System.out.println(" efficient depositing " + amount);
} public void withdraw(double amount) {
System.out.println(" efficient withdrawing " + amount);
}
}

译注:感觉这里没说透。我来说下这个问题,“父母是不可以动态替换的,但是朋友可以是动态替换的”。一旦继承了某类,你不可能替换这种继承关系,但是组合,我们可以通过向构造器传参或者setter方法临时改变组装进来的对象的类型,当然前提是这些对象的类型否和依赖的接口类型。

再进一步,重用代码要么用super.someMethod(),这个super指向父类对象,这个super你是没法换的。要么是通过brother.someMethod()重用代码,这个brother是组合语法中的域成员,它指向和我们协作的,或者说依赖的对象,这种对象只需一个set方法就可替换了。

  • 另一个问题是,实作继承中,子类依赖父类实现。这使得子类难以被重用,特别是继承而来的实作不再令人满意并因此而破坏封装性(译注:没看懂!)另外,对父类的修改不仅会沿着继承层次影响子类,还会影响到单纯使用子类的其他代码,这种子类严重耦合父类的设计是非常脆弱的。但是改变组合对象的接口/实现是容易的。

还是我上面说的那点。

因为对象组合的灵活性和强大,大部分设计模式只要有可能,就优先强调对象组合而非继承。很多时候,一个设计模式使用组合就展示了一个聪明的办法来解决一类常见问题,而不是用标准的、不那么灵活的基于继承的解决方案。

Encapsulation封装——指的是保持所有相关成员(变量和方法)在一起,在一个对象中。指定成员变量为私有可以隐藏变量和方法。对象应该向外界隐藏他们的内部运作。好的封装提高代码模块化,通过防止对象以一种意想不到的方式相互作用,从而使未来的开发和重构工作更容易。

示例代码:

class MyMarks {
private int vmarks = 0;
private String name; public void setMarks(int mark) throws MarkException {
if (mark > 0)
this.vmarks = mark;
else {
throw new MarkException("No negative Values");
}
} public int getMarks() {
return vmarks;
}
// getters and setters for attribute name goes here.
}

能够封装类的成员对于安全性和完整性来说是极其重要的。我们可以保护变量接收不合法的值。上面的示例代码描述了如何通过封装来保护MyMarks不拥有负值。任何修改成员变量”vmarks”的行为必须通过setter方法setMarks(int)。这可以防止对象”MyMarks”拥有负值,调用者传入负值将得到一个异常。

j2ee面试宝典翻译(3) j2ee job interview companion的更多相关文章

  1. j2ee面试宝典翻译(1)

    q1:给出一些使用Java的理由? a1:java是一个有趣的编程语言,让我找出一些理由来: 内建的多线程机制.套接字.内存管理(自动垃圾回收) 面向对象 跨平台 通过对标准API的扩展来支持基于we ...

  2. Java面试宝典2013版(超长版)

    一. Java基础部分......................................................................................... ...

  3. Java面试宝典2014版

    一. Java基础部分......................................................................................... ...

  4. Java 面试宝典-2017

    http://www.cnblogs.com/nelson-hu/p/7190163.html Java面试宝典-2017   Java面试宝典2017版 一. Java基础部分........... ...

  5. Java面试宝典-2017

    Java面试宝典2017版 一. Java基础部分........................................................................... ...

  6. Java面试宝典2018

    转 Java面试宝典2018 一. Java基础部分…………………………………………………………………………………….. 7 1.一个“.java”源文件中是否可以包括多个类(不是内部类)?有什么限制 ...

  7. java 软件开发面试宝典

    一. Java 基础部分........................................................................................ ...

  8. 最全的Java面试宝典

    一. 前言部分 从享受生活的角度上来说:“程序员并不是一种最好的职业,我认为两种人可以做程序员,第一,你不做程序员,你就没有什么工作可做,或者说是即使有可以做的工作但是你非常不愿意去做:第二,你非常痴 ...

  9. java面试宝典2019(好东西先留着)

    java面试宝典2019 1.meta标签的作用是什么 2.ReenTrantLock可重入锁(和synchronized的区别)总结 3.Spring中的自动装配有哪些限制? 4.什么是可变参数? ...

随机推荐

  1. 安装SQL Server 2008 - 初学者系列 - 学习者系列文章

    本文介绍SQL Server 2008数据库的安装 1.从下列地址获取SQL Server 2008的副本 thunder://QUFlZDJrOi8vfGZpbGV8Y25fc3FsX3NlcnZl ...

  2. 使用Clean() 去掉由函数自动生成的字符串中的双引号

    有时候由Excel单元格函数軿凑出来的字符串会自带双引号 效果如下: 想这种这个情况,刚好我们軿凑出来的是SQL语句, 执行的时候是去掉双引号, 这时候可以使用Excel自带的函数来去掉双引号 Cle ...

  3. cocos2d的-X- luaproject的LUA脚本加密

    2014/1/26 更新 近期又发现了一个非常easy的方法,事实上coco2dx已经给我们提供设置loader的方法. 注意:有个局限性,在非android平台下调用pEngine->exec ...

  4. ReviewBoard安装和配置说明

    眼下部门还没有採用Pair Programming那种时时刻刻都在review代码的工作方式,代码Review多採用走查方式.即代码写完后召开一个Code Review的Meeting,集中时间和经验 ...

  5. leetco Path Sum II

    和上一题类似,这里是要记录每条路径并返回结果. Given the below binary tree and sum = 22, 5 / \ 4 8 / / \ 11 13 4 / \ / \ 7 ...

  6. 修改servu数据库密码 servu加密方式

    项目要求可以有用户自行修改servu密码.servu可以通过odbc访问access\mysql\sqlserver数据库.我们直接通过创建web来修改就可以了. 不过问题来了,密码是加密的...通过 ...

  7. c语言可变参函数探究

    一.什么是可变长参数 可变长参数:顾名思义,就是函数的参数长度(数量)是可变的.比如 C 语言的 printf 系列的(格式化输入输出等)函数,都是参数可变的.下面是 printf 函数的声明: in ...

  8. 高级NAT-DMZ配置 -虚拟主机配置

    我家里另有一个网络摄像头,我想将公网IP映射到摄像头的IP,这样可以远程监控.以前没有光猫,用TP-Link做ADSL路由器,一点问题都没有. 现在破解了F420,在“高级NAT”-“DMZ配置”,或 ...

  9. 大数据时代,我们为什么使用hadoop

    大数据时代,我们为什么使用hadoop 我们先来看看大数据时代, 什么叫大数据,“大”,说的并不仅是数据的“多”!不能用数据到了多少TB ,多少PB 来说. 对于大数据,可以用四个词来表示:大量,多样 ...

  10. Class Model of Quick Time Plugin

    Quick Time Plugin 的类图. pdf version: http://pan.baidu.com/s/1o6oFV8Q