阿里面经之解答by cmershen(1)——Java的基本特性,面向对象的六大特征等
2016-06-27 16:49
627 查看
在牛客网上看到一份阿里面经,真假先不谈,就其中总结概括的知识点和常见问题还是不错的,在面试中也是经常会被问到的。
美中不足的是,里面的解答有的太过冗杂,有的则太过简略。本学妹按自己的理解,将这些问题重新整理,给出一份自己的理解,与读者共同学习共同进步。
1.自我介绍(略)
2.做过的项目(就说dts好啦~~)
(以下是重点的开始)
3.Java的四个基本特性:抽象,封装,继承,多态,对多态的理解以及项目中哪些地方用到多态
原po的解释太过繁杂,根本记不住
抽象:将一类对象的共同特征抽象成类。包括数据抽象(即成员变量)和行为抽象(即方法)。
继承:将已知类和继承信息合并起来创建新类的过程。
封装:隐藏内部的实现逻辑,只向外界提供最简单的接口的过程。
多态:允许不同子类型的对象,对同一消息做出不同的响应。
3.1 多态的理解
方法重载和重写:重载是同一个类中,同名方法有不同的参数列表 例如:
方法重写:
是在子类中重写父类已有的方法或抽象方法,例如:
要实现多态需要做的两件事:方法重写,对象造型(
项目中多态的应用:
在dts中的抽象语法树部分使用过多态,类SimpleNode代表抽象语法树的节点,但抽象语法树有许多种类型的节点,所以SimpleNode类中有一些抽象方法,在不同类型的节点中分别使用不同的实现类实现不同的处理逻辑。
(其实根本就不是这样的,编一个吧,DTS的设计模式无力吐槽。。。)
4.面向对象和面向过程的区别?用面向过程可以实现面向对象吗?
面向过程以事件为中心,分析出解决问题的步骤,然后用函数把这些步骤实现。
面向对象以类和对象为中心。
举例:”汽车发动“和”汽车停止“
对于面向对象来说是这样的:
对于面向过程来说是这样的:
5.重载和重写如何确定调用哪个函数?
重载:发生在同一个类中,同名方法具有不同参数,根据调用函数时的入口参数,选择与之对应的重载函数。
重写:发生在父类与子类之间,根据不同的子类对象确定调用哪个方法。
6.面向对象开发的六个基本原则:
(1)单一职责(SRP):There should never be more than one reason for a class to change.
即一个类只有一个引起变化的原因。
举例:一个调制解调器有四个功能,分别为拨号,挂断,接受消息,发送消息。
实际上Dial(拨通电话)和Hangup(挂电话)是属于连接的范畴,而Receive(收到信息)和Send(发送信息)是属于数据传送的范畴。这里类包括两个职责,显然违反了SRP。
因此要重构Modem类,从中抽象出两个接口,一个专门负责连接,另一个专门负责数据传送。依赖Modem类的元素要做相应的细化,根据职责的不同分别依赖不同的接口。如下:
这样无论单独修改连接部分还是单独修改数据传送部分,都彼此互不影响。
(2)里氏替换原则:“Subtypes must be substitutable for their base types”
也就是说子类必须能够替换成他们的基类。
举例:一个士兵用枪(手枪、步枪、机枪)射击杀人可描述为:
如果
(3)依赖倒置原则(Dependence Inversion Principle DIP )
所谓依赖倒置原则就是要依赖于抽象,不要依赖于具体。简单的说就是对抽象进行编程,不要对实现进行编程。面向对象的开发很好的解决了这个问题,一般的情况下抽象的变化概率很小,让用户程序依赖于抽象,实现的细节也依赖于抽象。即使实现细节不断变化,只要抽象不变,客户程序就不需要变化。
例如一个自动驾驶系统可以在福特车和本田车上使用,提供了三个方法:开车,停车,转弯。
如果不考虑依赖倒置原则来实现,应该这样写:
若该自动驾驶系统增加了对宝马车的支持,则需要增加BMWCar类,并在AutoSystem里按规律增加支持BMWCar的语句。如果又增加了10种,20种车的支持呢?如果不只是Run,Stop,Turn三种功能,而是100种,10000种功能呢?改动量将会变得极大。
这样我们需要抽象出来,使得Run,Stop,Turn三个函数只与车接口耦合,而不直接依赖具体车型,应该重构以上功能如下:
这样AutoSystem只与ICar耦合,无论增加多少种车,也不需改变AutoSystem类,只需新增加ICar接口的实现类即可。
4合成聚合复用:子类是超类的一个特殊种类,而不是超类的一个角色。区分“Has-A”和“Is-A”。只有“Is-A”关系才符合继承关系,“Has-A”关系应当用聚合来描述。
尽量使用聚合而不是继承,只有严格满足“Is-A”关系才使用继承。
5.开放封闭原则:Software entities(classes,modules,functions etc) should open for extension ,but close for modification.
软件实体应该对扩展开放,而对修改封闭。
对扩展开放,意味着有新的需求或变化时,可以对现有代码进行扩展,以适应新的情况。
对修改封闭,意味着类一旦设计完成,就可以独立其工作,而不要对类做任何修改。
举例:
银行业务系统有3种功能,存款、取款、转账。若不考虑OCP,则这样设计:
如果该系统升级,增加了第四种功能,那么BankProcess和BankStaff类都要修改。正确的做法是把三种业务抽象成接口,当业务员依赖于固定的抽象时,对修改就是封闭的,而通过继承和多态继承,从抽象体中扩展出新的实现,就是对扩展的开放。
6接口隔离原则(ISP)
接口隔离原则 认为:”使用多个专门的接口比使用单一的总接口要好”。因为接口如果能够保持粒度够小,就能保证它足够稳定,正如单一职责原则所标榜的那样。多个专门的接口就好比采用活字制版,可以随时拼版拆版,既利于修改,又利于文字的重用。而单一的总接口就是雕版印刷,显得笨重,实现殊为不易;一旦发现错字别字,就很难修改,往往需要整块雕版重新雕刻。
迪米特法则:一个类应该对其他类有尽可能少的了解。简称为“不要和陌生人说话”。
(DTS中的应用:遍历控制流图时使用观察者模式。)
美中不足的是,里面的解答有的太过冗杂,有的则太过简略。本学妹按自己的理解,将这些问题重新整理,给出一份自己的理解,与读者共同学习共同进步。
1.自我介绍(略)
2.做过的项目(就说dts好啦~~)
(以下是重点的开始)
3.Java的四个基本特性:抽象,封装,继承,多态,对多态的理解以及项目中哪些地方用到多态
原po的解释太过繁杂,根本记不住
抽象:将一类对象的共同特征抽象成类。包括数据抽象(即成员变量)和行为抽象(即方法)。
继承:将已知类和继承信息合并起来创建新类的过程。
封装:隐藏内部的实现逻辑,只向外界提供最简单的接口的过程。
多态:允许不同子类型的对象,对同一消息做出不同的响应。
3.1 多态的理解
方法重载和重写:重载是同一个类中,同名方法有不同的参数列表 例如:
f(arg1,arg2) { ... } f(arg1,arg2,arg3) { ... }
方法重写:
是在子类中重写父类已有的方法或抽象方法,例如:
class ClassA { public void f() { System.out.println("ClassA f()"); } } class ClassB extends ClassA { public void f() { System.out.println("ClassB f()"); } }
要实现多态需要做的两件事:方法重写,对象造型(
ClassA b = new ClassB();)
项目中多态的应用:
在dts中的抽象语法树部分使用过多态,类SimpleNode代表抽象语法树的节点,但抽象语法树有许多种类型的节点,所以SimpleNode类中有一些抽象方法,在不同类型的节点中分别使用不同的实现类实现不同的处理逻辑。
(其实根本就不是这样的,编一个吧,DTS的设计模式无力吐槽。。。)
4.面向对象和面向过程的区别?用面向过程可以实现面向对象吗?
面向过程以事件为中心,分析出解决问题的步骤,然后用函数把这些步骤实现。
面向对象以类和对象为中心。
举例:”汽车发动“和”汽车停止“
对于面向对象来说是这样的:
Class Car { ... void start(); void stop(); } Car c=new Car(); c.start(); c.stop();
对于面向过程来说是这样的:
void start(Car* c) void stop(Car* c)
5.重载和重写如何确定调用哪个函数?
重载:发生在同一个类中,同名方法具有不同参数,根据调用函数时的入口参数,选择与之对应的重载函数。
重写:发生在父类与子类之间,根据不同的子类对象确定调用哪个方法。
6.面向对象开发的六个基本原则:
(1)单一职责(SRP):There should never be more than one reason for a class to change.
即一个类只有一个引起变化的原因。
举例:一个调制解调器有四个功能,分别为拨号,挂断,接受消息,发送消息。
public class Modem { public void dial(); public void hangup(); public void receive(); public void send(); }
实际上Dial(拨通电话)和Hangup(挂电话)是属于连接的范畴,而Receive(收到信息)和Send(发送信息)是属于数据传送的范畴。这里类包括两个职责,显然违反了SRP。
因此要重构Modem类,从中抽象出两个接口,一个专门负责连接,另一个专门负责数据传送。依赖Modem类的元素要做相应的细化,根据职责的不同分别依赖不同的接口。如下:
public interface IConnection { public void dial(); public void hangup(); } public interface IDataTransfer { public void receive(); public void send(); } public class Modem implements IConnection,IDataTransfer { }
这样无论单独修改连接部分还是单独修改数据传送部分,都彼此互不影响。
(2)里氏替换原则:“Subtypes must be substitutable for their base types”
也就是说子类必须能够替换成他们的基类。
举例:一个士兵用枪(手枪、步枪、机枪)射击杀人可描述为:
public class Soldier { public void killEnemy(AbstractGun gun) { gun.shoot(); } } public abstract class Gun { public void shoot(); } public class HandGun extends Gun{ public void shoot() { System.out.println("HandGun shoot!"); } } public class Rifle extends Gun{ public void shoot() { System.out.println("Rifle shoot!"); } } public class MachineGun extends Gun{ public void shoot() { System.out.println("MachineGun shoot!"); } }
如果
public void killEnemy(AbstractGun gun)中不能调用父类AbstractGun,则违反了里氏代换原则。
(3)依赖倒置原则(Dependence Inversion Principle DIP )
所谓依赖倒置原则就是要依赖于抽象,不要依赖于具体。简单的说就是对抽象进行编程,不要对实现进行编程。面向对象的开发很好的解决了这个问题,一般的情况下抽象的变化概率很小,让用户程序依赖于抽象,实现的细节也依赖于抽象。即使实现细节不断变化,只要抽象不变,客户程序就不需要变化。
例如一个自动驾驶系统可以在福特车和本田车上使用,提供了三个方法:开车,停车,转弯。
如果不考虑依赖倒置原则来实现,应该这样写:
public class HondaCar { public void Run() { System.out.println("本田车启动了!"); } public void Turn() { System.out.println("本田车拐弯了!"); } public void Stop() { System.out.println("本田车停止了!"); } } public class FordCar { public void Run() { System.out.println("福特车启动了!"); } public void Turn() { System.out.println("福特车拐弯了!"); } public void Stop() { System.out.println("福特车停止了!"); } } public class AutoSystem { public enum CarType{ Ford,Honda} private HondaCar hondacar=new HondaCar(); private FordCar fordcar=new FordCar(); private CarType type; public AutoSystem(CarType carType) { this.type = carType; } public void RunCar() { if (this.type == CarType.Honda) hondacar.Run(); else if (this.type == CarType.Ford) fordcar.Run(); } public void StopCar() { if (this.type == CarType.Honda) hondacar.Stop(); else if (this.type == CarType.Ford) fordcar.Stop(); } public void TurnCar() { if (this.type == CarType.Honda) hondacar.Turn(); else if (this.type == CarType.Ford) fordcar.Turn(); } }
若该自动驾驶系统增加了对宝马车的支持,则需要增加BMWCar类,并在AutoSystem里按规律增加支持BMWCar的语句。如果又增加了10种,20种车的支持呢?如果不只是Run,Stop,Turn三种功能,而是100种,10000种功能呢?改动量将会变得极大。
这样我们需要抽象出来,使得Run,Stop,Turn三个函数只与车接口耦合,而不直接依赖具体车型,应该重构以上功能如下:
public interface ICar { public void Run(); public void Turn(); public void Stop(); } public class HondaCar implements ICar{ public void Run() { System.out.println("本田车启动了!"); } public void Turn() { System.out.println("本田车拐弯了!"); } public void Stop() { System.out.println("本田车停止了!"); } } public class FordCar implements ICar{ public void Run() { System.out.println("福特车启动了!"); } public void Turn() { System.out.println("福特车拐弯了!"); } public void Stop() { System.out.println("福特车停止了!"); } } public class AutoSystem { public ICar car; public AutoSystem(ICar car) { this.car=car; } public void runCar() { this.car.run(); } public void stopCar() { this.car.stop(); } public void turnCar() { this.car.turn(); } }
这样AutoSystem只与ICar耦合,无论增加多少种车,也不需改变AutoSystem类,只需新增加ICar接口的实现类即可。
4合成聚合复用:子类是超类的一个特殊种类,而不是超类的一个角色。区分“Has-A”和“Is-A”。只有“Is-A”关系才符合继承关系,“Has-A”关系应当用聚合来描述。
尽量使用聚合而不是继承,只有严格满足“Is-A”关系才使用继承。
5.开放封闭原则:Software entities(classes,modules,functions etc) should open for extension ,but close for modification.
软件实体应该对扩展开放,而对修改封闭。
对扩展开放,意味着有新的需求或变化时,可以对现有代码进行扩展,以适应新的情况。
对修改封闭,意味着类一旦设计完成,就可以独立其工作,而不要对类做任何修改。
举例:
银行业务系统有3种功能,存款、取款、转账。若不考虑OCP,则这样设计:
public class BankProcess { public void Deposite(); public void Withdraw(); public void Transfer(); } public class BankStaff { private BankProcess bankpro = new BankProcess(); public void BankHandle(Client client) { switch (client.Type) { case "deposite": bankpro.Deposite(); break; case "withdraw": bankpro.Withdraw(); break; case "transfer": bankpro.Transfer(); break; } } }
如果该系统升级,增加了第四种功能,那么BankProcess和BankStaff类都要修改。正确的做法是把三种业务抽象成接口,当业务员依赖于固定的抽象时,对修改就是封闭的,而通过继承和多态继承,从抽象体中扩展出新的实现,就是对扩展的开放。
6接口隔离原则(ISP)
接口隔离原则 认为:”使用多个专门的接口比使用单一的总接口要好”。因为接口如果能够保持粒度够小,就能保证它足够稳定,正如单一职责原则所标榜的那样。多个专门的接口就好比采用活字制版,可以随时拼版拆版,既利于修改,又利于文字的重用。而单一的总接口就是雕版印刷,显得笨重,实现殊为不易;一旦发现错字别字,就很难修改,往往需要整块雕版重新雕刻。
迪米特法则:一个类应该对其他类有尽可能少的了解。简称为“不要和陌生人说话”。
(DTS中的应用:遍历控制流图时使用观察者模式。)
相关文章推荐