最近听朋友A去面试遇到一些问题,问题并不难都是非常基础的,但是确是我们最容易忽视的地方,这里总结一下只为做记录,各位观众老爷不喜勿喷;接下来我要开始装逼了,哈哈
场景:面试基础问题
这个问题就是Java的核心思想:面向对象,这里不展开面向对象的概念,面试题是回答一下面向对象的几大原则,当时朋友A就一脸懵比(内心一万个草泥马),平时都是写代码,况且面向对象这么抽象的概念如何描述呢?(成妾做不到啊)。当时我也是懵比,就马上去看了一下面向对象的原则,然后简单的记录一下。
面向对象的六大原则:
1、单一职责
2、开闭原则
3、里氏替换
4、依赖倒置
5、接口隔离
6、迪米特原则
是不是觉得记住了这6大原则就可以出去装逼了?(不存在的,哈哈),正当你得意的说出这六大原则的时候面试官说解释一下什么是单一职责?我估计你会。。。。
废话说太多了,接下来我们来一一解释下概念性的问题:
1、单一职责
定义:就一个类而言,应该仅有一个引起它变化的原因。这个比较好理解就是自己做自己的事情,不要做超过自己职责范围之外的事情(我想起了葫芦娃七兄弟);比如在Android源码设计模式中提到小明在写缓存模块的时候把所有的缓存方式都是写在一个类中;现实我们coding的时候也经常这样,当我们完成后那业务逻辑的代码开起来是那么酸爽(笑哭,埋坑中。。),比如发布后产品说想改一下业务实现规则,这下你回去看你的代码发现自己都看不懂了(请不要给自己或则别人挖坑,),其实简单说在coding中就是把一组相关性很高的函数、数据封装在一个类中(这里是没毛病),如果有多个功能区分的函数我们可以拆开多个类,这就是简单的单一职责。
2、开闭原则
官方定义:在软件中的对象(类,模块、函数等)应该对于扩展是开放的,但是对于修改是封闭的。这句话通俗的理解为程序在完成以后,程序中一个类的实现只应该因错误而被修改,新的或者改变的特性应该通过新建不同的实现类,新建的类可以通过继承的方式来重用原类的代码,让程序更稳定灵活。这就是简单的开闭原则。
3、里氏替换原则
定义:所有引用基类的地方必须能透明的使用其子类的对象。我们知道面向对象的语言有三大特点是继承、封装、多态,里氏替换原则就是依赖继承、多态两大特性。通俗的说只要是父类能出现的地方子类就可以出现,而且替换为子类后不会出现任何错误或者异常,使用者不需要知道是父类还是子类,但是反过来就不行,有子类出现的地方,父类就未必适应。总结一下就是两个字:抽象;我们可以结合Android里面View、Button、TextView的关系就很好理解了。该原则的核心原理就是抽象,而抽象又是依赖继承特性,在面向对象中继承的优点非常明显,代码重用,子类拥有父类的方法属性,代码扩展性高。但是缺点也是有的,继承具有很强的侵入性,只要继承了就必须拥有父类的属性跟方法,降低了灵活性,会造成子类代码冗余。里氏替换原则就是通过抽象建立规范,具体的实现在运行时替换掉抽象,保证系统的扩展性,灵活性与开闭原则往往是生死相依,不离不弃的。
4、依赖倒置原则
定义:模块间的依赖通过抽象发生,实现类之间不发生直接的依赖关系,其依赖关系是通过接口或抽象类产生的。简单的说就是面向接口编程,这句话相信大家听的最多了。比如小明之前只是实现了sd卡的缓存,现在主管要求实现内存缓存,小明这个时候应该把缓存这个功能函数抽象为一个公共接口,然后去各自实现sd卡缓存策略跟内存缓存策略。如果下次主管再加一个别的缓存方式,这个时候小明就可以不修改之前的缓存策略而扩展一个新的缓存策略就可以了。
5、接口隔离原则
定义:类间的依赖关系应该建立在最小接口上。接口隔离原则将非常庞大、臃肿的接口拆分成更小的和更具体的接口,这样客户端只需要关系他们感兴趣的方法。接口隔离原则目的就是系统解耦,提高代码质量。比如在java6之前有个OutputStream的关闭问题,每次使用完后都要关闭,各种try...catch嵌套代码可读性非常差。我们通过看源码知道有一个Closeable接口,该接口定义了一个可关闭对象,恰好OutputStream又实现了这个接口,所以我们可以封装一个工具类来关闭对象,这里直接贴代码:
public final class CloseUtils{
private CloseUtils(){}
/**
**关闭Closeable对象
** @param closeable
**/
public static void closeQuietly(Closeable closeable){
if(closeable!=null){
try{
closeable.close();
}catch(IOExecption e){
e.printStackTrace();
}
}
}
}
我们直接在用到的地方调用就可以。CloseUtils的closeQuietly方法的基本原理就是依赖于Closeable抽象而不是具体实现,并建立在最小的依赖原则基础上,只需要知道这个对象可以关闭,其他不需要关心。
6、迪米特原则
大家肯定会问这个是什么鬼?根本没听过。。(原谅我的乱入)
定义:一个对象应该对其他对象有最少的了解(还有一个解释是:只与直接的朋友通信)。通俗的讲,一个类应该对自己需要耦合或者调用的类知道最少,类的内部如何实现与调用者或者依赖者没关系,调用者或者依赖者只需要知道它需要的方法即可,其他一律不关心。是不是觉得还是很难理解?我们举个栗子,我们程序员去租房子,在一线城市租房大部分都是通过中介找房,假如我们只关心房间的面积跟租金,其他一概不管,中介只需要将符合我们要求的房子提供给我们就好了。接下来我们从code层面上会比较好理解一点。我们抽象出一个房间类(Room),中介类(Mediator),租户类(Tenant)
/**
* 房间
*/
public class Room{
public float area;//面积
public float price;//价格
public Room(float area, float price) {
this.area = area;
this.price = price;
}
@Override
public String toString() {
return "Room{" +
"area=" + area +
", price=" + price +
'}';
}
}
/**
* 中介
*/
public class Mediator{
List<Room> rooms = new ArrayList<>();//中介手上的房间数
public Mediator(){
for(int i=0;i<5;i++){
rooms.add(new Room(14+i,(14+i)*150));
}
}
public List<Room> getRooms() {
return rooms;
}
}
/**
* 租户
*/
public class Tenant{
public float roomArea;
public float roomPrice;
public static final float diffPrice = 100.00001f;
public static final float diffArea = 0.00001f;
/**
* 租赁房子
* @param mediator
*/
public void rentRoom(Mediator mediator){
List<Room> rooms = mediator.getRooms();
for(Room room:rooms){
if(isSuitable(room)){
System.out.println("租到房子啦!"+room);
break;
}
}
}
/**
* 是否符合租户要求
* @param room
* @return
*/
private boolean isSuitable(Room room){
return Math.abs(room.price-roomPrice)<diffPrice&&Math.abs(room.area-roomArea)<diffArea;
}
}
从上面代码可以看到,租户(Tenant)不仅依赖了中介(Mediator),还需要频繁与房间(Room)打交道。租户的要求只是要求通过中介找房子,如果把这些检测条件都放在租户(Tenant)中,那么中介的功能就被弱化了,导致租户与房子的耦合较高,还要关心房子的细节。当Room变化时候Tenant也必须跟着变化;而Tenant又与Mediator耦合,这样就出现了多重关系。结构关系图(UML)如下:
既然耦合严重,那我们就见招拆招。首要要明确的是租户只跟中介通信,从代码中提现就是将Room相关的操作从Tenant中移除,这些操作是属于中介(Mediator)。重构代码后:
/**
* 中介
*/
public class Mediator{
List<Room> rooms = new ArrayList<>();//中介手上的房间数
public Mediator(){
for(int i=0;i<5;i++){
rooms.add(new Room(14+i,(14+i)*150));
}
}
/**
* 中介租赁房子
* @param area
* @param price
* @return
*/
public Room rentOut(float area,float price){
for (Room room:rooms){
if(isSuitable(area,price,room)){
return room;
}
}
return null;
}
/**
* 是否符合租户要求
* @param room
* @return
*/
private boolean isSuitable(float area,float price,Room room){
return Math.abs(room.price-price)<Tenant.diffPrice&&Math.abs(room.area-area)<Tenant.diffArea;
}
}
/**
* 租户
*/
public class Tenant{
public float roomArea;
public float roomPrice;
public static final float diffPrice = 100.00001f;
public static final float diffArea = 0.00001f;
/**
* 租赁房子
* @param mediator
*/
public void rentRoom(Mediator mediator){
System.out.println("租到房子啦!"+mediator.rentOut(roomArea,roomPrice));
}
}
重构后结构图:
我们只是将租户对于房间的判断操作放到了中介中,这本来就是属于中介的职责,根据租户设定的要求找到符合条件的房子,并将结果交给租户就可以了。租户不需要了解房间太多的细节,所以事情租户与中介沟通就好,这样“只与直接的朋友通信”就能够将租户与中介、房间之间复杂关系中抽离出来,降低程序的耦合,稳定性更好。
以上就是Java核心面向对象的原则的解释与场景分析。新手上路,请各位观众老爷df二连带走。
PS:写完这博客的感觉就是