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. InstallShield集成安装MSDE2000最小版本(一) fishout特许授权发布

    原文:InstallShield集成安装MSDE2000最小版本(一) fishout特许授权发布 原帖地址:http://blog.csdn.net/fishout/archive/2009/10/ ...

  2. TextArea中定位光标位置

    原文:TextArea中定位光标位置 在项目中,遇到一个场景:希望能在TextArea中输入某条记录中的明细(明细较简单,没有附属信息,只用记录顺序和值即可,譬如用"+"号来作为明 ...

  3. CSS背景图片定位

    原文:CSS背景图片定位 在网页开发中我们经常需要对图片进行分割(如下图)来使用,而不是分别提供单独的图片来调用,常见的如页面背景,按钮图标等,这样做的好处就是减少请求次数,节省时间和带宽. 对背景图 ...

  4. tornado settings想到的

    今天有足够多的时间来看看Tornado中RequestHandler和Application这两个类的关系. 昨天想要调用settings中的内容,找了好半天不知道怎么在handler中使用setti ...

  5. VBOX安装Centos设置分辨率为1366x768[已解决]

    最近想了解下GTK+,但是对于直接在系统上搭建环境有点心里阴影,怕又把桌面玩挂,所以打算在虚拟机中先试试 本来打算使用Fedora的,怕gnome太吃资源所以下了个xfce的,不过貌似有BUG,无法安 ...

  6. GNU中宏定义对可变参数的支持(引自百度)

    http://zhidao.baidu.com/question/125413478.html 问:#define PDEBUG(fmt,args...) fprintf(stderr,fmt, ## ...

  7. android音乐播放器开发 SweetMusicPlayer 智能负载直插式歌词

    在一份书面的使用MediaPlayer播放音乐, http://blog.csdn.net/huweigoodboy/article/details/39862773.假设没有本地歌词怎么办?如今来将 ...

  8. 通过改变viewport 实现网站自适应

    var phoneWidth = parseInt(window.screen.width); var phoneScale = phoneWidth/640; var userAgent = nav ...

  9. css Cursor:url()自定义鼠标指针样式为图片

    css自定义鼠标指针样式为图片Cursor:url()的使用,今天在项目中,要用到自定义鼠标样式,格式: css:{cursor:url('绝对路径的图片(格式:cur,ico)'),-moz-zoo ...

  10. Windows 7上使用HP QC的问题

    C(Quantity Center)是一款不错的测试管理工具,最近把公司的操作系统从Windows XP升级到Windows 7之后,发现登录到QC Server的Addin页面,很多客户端组件不能正 ...