设计思想:封装、抽象、继承、多态

您所在的位置:网站首页 怎么实现封装和继承 设计思想:封装、抽象、继承、多态

设计思想:封装、抽象、继承、多态

2023-06-16 00:06| 来源: 网络整理| 查看: 265

面对对象编程与面对对象语言的四大特性:封装、抽象、继承、多态。一般来说,我们知道这些特性,但是却不知道怎么使用,为什么要使用。接下来,我们一起去解决这些问题。

封装

封装也叫作信息隐藏或者数据访问保护。 类通过暴露有限的访问接口,授权外部仅能通过类提供的方式(或者叫函数)来访问内部信息或者数据。

那究竟什么是封装呢?我们通过简单的例子来描述一下封装特性。

下面是一段游戏装备强化的代码实现,在玩家获得装备时,我们为每个装备对象创建 Equip 对象,以此保存每个装备的数据。

/** * 装备 */ public class Equip { // 装备流水id private int id; // 装备配置id private int configId; // 强化时刻 private long upgradeLevelTime; // 强化等级 private int level; // 更多属性 ... private Equip() {} public Equip(int configId) { this.id = IdGenerator.obtainId(); this.configId = configId; this.level = 1; } /** * 装备流水id */ public int getId() { return id; } /** * 装备配置id */ public int getConfigId() { return configId; } /** * 强化时刻 */ public long getUpgradeLevelTime() { return upgradeLevelTime; } /** * 获得强化等级 */ public int getLevel() { return level; } /** * 强化 * @return 强化是否成功 */ public boolean upgradeLevel() { // 判断是否能强化到下一个等级 ... // 强化成功 this.level ++; this.upgradeLevelTime = System.currentTimeMillis(); return true; } ... } 复制代码

从代码中,我们可以看到装备存在属性:

id 装备流水id configId 装备配置id upgradeLevelTime 强化时刻 level 强化等级

存在方法:

getId() 装备流水id getConfigId() 装备配置id getReceiveLevelTime() 强化时刻 getLevel() 获得强化等级 upgradeLevel() 强化

装备流水id、装备配置id,这些属性为什么就只有 get 方法呢?因为这些方法都在对象初始化构造时,在构造函数中进行赋值,固化数值,后续不允许修改,因此不应提供修改方法。

upgradeLevelTime 强化时刻、强化等级level 这俩个属性,皆只提供了 get 方法来获取数据,只通过强化方法 upgradeLevel 去修改强化等级,这又为什么呢?之所有这么设计,强化时刻和强化等级这俩个属性的变化,都在会发生在强化操作,因此不应该提供 set 方法修改方式。

对于封装这个特性,我们需要编程语言本身提供一定的语法机制来支持。这个语法机制就是访问权限控制。 例子中的 private、public 等关键字就是 Java 语言中的访问权限控制语法。private 关键字修饰的属性只能类本身访问,可以保护其不被类之外的代码直接访问。如果 Java 语言没有提供访问权限控制语法,所有的属性默认都是 public 的,那任意外部代码都可以通过类似 equip.id=30; 这样的方式直接访问、修改属性,也就没办法达到隐藏信息和保护数据的目的了,也就无法支持封装特性了。

什么是封装?如何进行封装?我们在上面都已经解决了。那封装到底解决了什么编程痛点呢?

如果类中属性的访问不做任何限制,任意代码皆可访问或者修改类中属性,虽然使用此类时更灵活了,但是这种“灵活”却要付出代价的,造成类的状态不可控,严重影响代码的 可读性、可维护性 。像上述的例子中,若upgradeLevelTime、level 都提供 set 方法,某个小伙伴在不了解的情况下,setUpgradeLevelTime(0L) ,手动设置了强化时刻,就造成强化时刻与强化等级数据不一致的情况。因强化时刻这个属性的设置,本身的修改就依赖于强化操作,此番操作势必为非法。

除此之外,类仅仅通过有限的方法暴露必要的操作,也能提高类的 易用性 。如果将所有的属性接口都暴露,除非调用者对此类的业务充分了解,否则很容易出错。这对调用者极不友好,为了降低出错的概率,应该只暴露真正需要的接口。假如我们现在拥有一个电水壶,水壶上有很多按钮开关,用户需要花很长的时间去研究说明书,最终也不一定能看懂,这样太闹腾了。对于用户,拿到一个电水壶,我只想插上电,开始煮水,能尽快喝到热水。简单的开、关按钮足以应对用户场景,效率高,用户体验好,出错率低。

抽象

封装解决的是隐藏信息和数据保护的问题,而抽象解决的是隐藏方法实现的问题。

在面向对象编程中,我们常借助编程语言提供的接口类(比如 Java 中的 interface)或者抽象类(比如 Java 中的 abstract)这两种语法机制,来实现抽象这一特性。

我们可以通过下面的例子来看一下抽象:

public interface IUserService { /** * 登录 */ void login(User user); /** * 登出 */ void logout(User user); /** * 改名 */ void rename(User user); } public class UserService implement IUserService { @Override public void login(User user){ //... } @Override public void logout(User user){ //... } @Override public void rename(User user){ //... } } 复制代码

抽象这一特性,并不一定要通过接口或者抽象类来实现。 即使不编写 IUserService 接口类,UserService 类本身也能满足抽象特性。因为,类的方法是通过编程语言中的“函数”这一语法机制来实现的。通过函数包裹具体的实现逻辑,这本身就是一种抽象。我们使用函数时,并不需要知道方法内部怎么实现,直接调用即可。

什么是抽象?如何进行抽象?我们在上面都已经解决了。那抽象到底解决了什么编程痛点呢?

我们去处理复杂的问题时,问题的处理实现细节没必要暴露出来给调用者,这时候我们需要做上层抽象,把非必要性的实现细节抽象出来,这样我们可以更好地关注功能,而非具体的实现的设计思路。我们在命名类的方法时,需要抽象思维,不能暴露太多的实现细节,以此确保后续变更内部逻辑时,不用再次修改方法定义。比如 getMySqlDBUrl() 就暴露了实现细节,后续假如将数据库改成 MongoDB ,方法定义与实现就不一致了,需要重新定义方法了,这里应该使用 getDBUrl() 更为合适。

在代码设计中,起到非常重要的指导作用。很多设计原则都体现了抽象这种设计思想,比如基于接口而非实现编程、开闭原则(对扩展开放、对修改关闭)、代码解耦(降低代码的耦合性)等。

继承

如果你熟悉的是类似 Java、C++ 这样的面向对象的编程语言,那你对继承这一特性,应该不陌生了。继承是用来表示类之间的 is-a 关系。

为了实现继承这个特性,编程语言需要提供特殊的语法机制来支持,比如 Java 使用 extends 关键字来实现继承,C++ 使用冒号(class B : public A),Python 使用 parentheses (),Ruby 使用



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3