阿里工作5年面试官分享Java面试,公众号:liebao小奕,回复1024获取Java开发扫盲思维导图。回复加群进粉丝群。粉丝群有学习圈、经验分享、群主模拟面试等活动。一起卷Java技术。
相信大家对封装这个词不陌生,它是面向对象编程特性之一,但是当面试的时候被问到我们应该如何回答好这个问题,首先思考一个问题,为什么需要封装呢,不封装会有什么问题?
回答这个问题要知道java是面向对象编程语言,那么对象怎么来,对象是通过类的实例创建出来的,类里面又有各种属性和行为,那么问题来了,这么多属性和行为怎么知道他们归属哪个类里面,如何管理,况且不同类之间的属性和行为不一样,针对我某个类来说,我又不需要关系和我无关的属性和行为,于是封装这个概念就出来,它解决了用类来将这些属性和行为封装到一起,当它实例化出来一个对象的时候,我们可以说这个对象具有这些属性和行为。
这个问题你回答对了,面试官接着问题,那你能举例子说明这一特性带来的好处吗(这里就是在说你对封装理解到底是停留在概念层面还是真正了解掌握了)
我们还是举上次直播讲的那个例子,假设猎豹粉丝每个人都有钱包,设计了一个粉丝钱包这么一个类,属性有粉丝Id,粉丝昵称, 如下:
@Data
public class LbFensWallet {
private Integer lbFensId;
private String lbFensNickName;
}
那么钱包怎么设计呢?到底是用继承呢,还是再用一个类来封装呢? 这里我们假设是用继承,于是就有了 LbFensWallet extends Wallet 这个类这样会有什么问题? 假设后续这个类需要继承其他的类就完成不了,这个设计就设计死了,因为java是不支持多继承的。
扩展一下,为什么不支持多继承呢,我们假设java支持多继承,Clasa B 、Class C 均继承 Class A, Class D 继承Class B 和 Class C , 如果Calss A 有方法m1 ,Class B Class C 分别重写了m1,那么Class D 到底是用Class B的m1 还是用 Class C的m1 呢。这就矛盾了。
所以这里不能用继承,而是需要用组合。将钱包类作为猎豹粉丝钱包的属性。
@Data
public class LbFensWallet {
private Integer lbFensId;
private String lbFensNickName;
private Wallet wallet;
}
这里教大家一个设计原则,就是对类实体最小粒度拆分,看这个类承担的实体有哪些,LbfensWallet这个类其实可以看成实体有粉丝、钱包。那么如何识别是一个实体,就是看如果说拆分的两个实体之间属性或者方法有相似重叠,那么这种实体就不是最小粒度,我们这个例子粉丝和钱包两个的属性和方法没有关联,所以可以这么设计。
可能有人杠了,我就设计成将钱包里面的属性和方法搬运到猎豹粉丝钱包这个类里面难道不行吗?
我只能说如果你的逻辑没问题你怎么实现都没问题,但是你要考虑后续的可扩展、可维护、可理解等原则,我们假设就像这位同学这么设计,会有什么缺点 ?
第一是会让使用者使用模糊,因为一个类的设计不仅仅是内部使用还有外部给别人使用。关联域不清晰,如果调用方只需要感知粉丝这个实体,它直接就可以不用关心wallet这个类了,而不是说我要看看哪些字段我不关心。 第二点,会使得赋值出现嵌套赋值,怎么理解,如果说你的业务流程很长,方法调用了很多其他方法,那么你在声明对象 和 赋值对象 这两个操作做不到紧急衔接,你需要去关心不同实体的字段通过参数去透传下去。
ok,这个问题我们理解了,让我们来看看钱包类wallet
@Data
public class Wallet {
/**
* 颜色
*/
private String color;
/**
* 金额
*/
private BigDecimal amount;
/**
* 纸币集
*/
private List<MoneyPaper> paperList;
属性有钱包颜色,总金额,和对应的纸币集。 读到这里,粉丝可以先思考这个类有没有什么问题? 或者说有没有什么隐患?
这个类的设计严重违背了封装的设计理念,为什么这么说呢? 且听我慢慢唠叨,首先这个金额字段的对外暴露amount是很危险的,涉及到金额对安全性要求会很高,如果说使用者对amount进行了修改任意赋值,则会存在隐患,主要问题不是这个,更关键的是你会发现如果重新赋值了amount字段,你会发现导致和纸币集paperList 这个字段就自相矛盾了(纸币集里面的钱加起来会等于总金额吗?) 这样也就导致了这个对象其实是一个业务上来说不正确的对象。将会是出现致命问题。
那么我们该如何改造重新设计呢?
首先针对不可修改的字段我们可以设计成对外只读操作,不允许写操作。只需要不提供setXX方法即可(这里杠粉丝可能会说反射可以,这里不讨论这个,关注重点)这样就只允许getXX 就不会去赋值总金额字段,那该如何修改呢? 可能你会发现我只提供对MoneyPaper对外赋值就行,比如说传进来一张10元纸币,我对paperList进行add 进去,然后在重新计算总金额。
不错,思路就是这样,将逻辑暴露在方法的内部,不对外暴露细节这也是OCP原则的一个重要体现。 但是如果我想增加1.5元怎么办 也没有这样的纸币啦(例子,例子,业务细节我们不在意 哈哈)
public class Wallet {
/**
* 钱包颜色
*/
private String color;
/**
* 钱包金额
*/
private BigDecimal amount;
/**
* 纸币集
*/
private List<MoneyPaper> paperList;
/**
* 封装 他是讲关联字段的逻辑不暴露给外部
* 提供可操作参数逻辑封装在内部处理
* 同时也就解决了Q1:金额字段不安全 Q2 关联字段逻辑非法
* @param add
*/
public void addAmount(BigDecimal add){
amount.add(add);
paperList.add(new MoneyPaper());
}
public void deAmount(BigDecimal add) {
//对入参进行判断非负数处理 不能超过amount
amount.add(add);
paperList.add(new MoneyPaper());
}
}
好了,今天的对于封装的分享就到这里了,最基本的问题往往背后都有强大的理论支撑。这些才是值得我们思考和学习的地方,觉得看的不错想了解更多这样的短文,也请粉丝可以关注这个专栏的动态 Java核心&面试 。