设计模式学习笔记 - 面向对象 - 2.封装、抽象、继承、多态分别用来解决哪些问题?

1.封装

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

下面这段代码是一个简化版的虚拟钱包的代码实现。在金融系统中,我们会给每个用户创建一个虚拟钱包,用来记录用户在我们系统中的虚拟货币量。

public class Wallet {private String id;private long createTime;private long balance;private long balanceLastModifiedTime;//...public Wallet() {this.id = IdGenerator.getInstance().generate();this.createTime = System.currentTimeMillis();this.balance = 0L;this.balanceLastModifiedTime = System.currentTimeMillis();}// 下面对 get 方法做了代码折叠,是为了减少代码所占的篇幅public String getId() { return this.id; }public long getCreateTime() { return this.createTime; }public long getBalance() { return this.balance; }public long getBalanceLastModifiedTime() { return this.balanceLastModifiedTime; }public void increaseBalance(long increasedAmount) {if (increasedAmount < 0L) {throw new InvalidAmountException("...");}this.balance += increasedAmount;this.balanceLastModifiedTime = System.currentTimeMillis();}public void decreaseBalance(long decreasedAmount) {if (decreasedAmount < 0L) {throw new InvalidAmountException("...");}if (decreasedAmount > this.balance) {throw new InsufficientAmountException("...");}this.balance -= increasedAmount;this.balanceLastModifiedTime = System.currentTimeMillis();}
}

从代码中可以发现,Wallet 类主要有四个属性(也就做成员变量),就是我们前面定义中提到的信息或数据。

  • id 表示钱包的唯一编号
  • createTime 表示钱包的创建时间
  • balance 表示钱包中的余额
  • balanceLastModifiedTime 表示上次余额变更时间

按照封装特性,对钱包的四个属性的访问进行了限制。调用者只允许通过下面的方法来访问或者修改钱包里的数据。

  • getId()
  • getCreateTime()
  • getBalance()
  • getBalanceLastModifiedTime()
  • increaseBalance()
  • decreaseBalance()

之所以这样设计,是因为从业务角度来说,id、createTime 在创建钱包时就确定好了,之后不应该在改动,所以并没有提供修改这两个属性的方法,比如 set 方法。

对于钱包余额 balance 来说,只能增加或者减少,不会被重新设置,所以,只提供了 increaseBalance() 和 decreaseBalance() 方法,并没有暴露 set 方法。

对于 balanceLastModifiedTime 这个属性,它完全是根balance 这个属性的修改绑定在一起的。只有在 balance 修改时,这个属性才会被修改。所以,我们把 balanceLastModifiedTime 这个属性的修改操作完全封装在了 increaseBalance() 和 decreaseBalance() 方法中,不对外暴露任何修改这个属性的方法和细节。这样也可以保证 balance 和 balanceLastModifiedTime 两个数据的一致性。

对于封装这个特性,需要编程语言本身提供一定的语法机制来支持,这个机制就是访问权限控制。例子中的 private、public 等关键字就是 Java 语言中的访问权限控制语法。

  • private 关键之修饰的熟悉只能类本身访问,可以保护其不被类之外的代码直接访问。
  • public 则是所有的代码都可以访问。

上面讲了封装的定义,那么封装的意义是什么?他能解决什么编程问题呢?

如果我们对类中属性的访问不做任何限制,那任何代码都可以访问、修改属性,虽然这样看起来很灵活,但是也意味着不可控,属性可能被以各种奇葩的方式修改,而修改的逻辑可能散落在代码的各个角落,势必影响代码的可读性、可维护性

除此之外,类通过有限的方法暴露必要的操作,能提高类的易用性。如果我们把类属性都暴露给类的调用者,调用者想要正确地操作这些属性,就势必对业务细节有足够的了解。而这对于调用者来说也是一种负担。而我们把属性封装起来,暴露少许几个必要的方法给调用者,调用者就不需要了解太多背后的业务细节,用错的概率就减少很多。

2.抽象

封装主要讲的是如何隐藏信息、保护数据,而抽象讲的是如何隐藏方法的具体实现,让调用者只关心方法提供了哪些功能,并不需要知道这些功能是如何实现的。

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

举一个例子来解释下。

public interface IPictureStorage {void savePicture(Picture picture);Image getPicture(String pictureId);void deletePicture(String pictureId);void modifyMetaInfo(String pictureId, PictureMetaInfo metaInfo);
}public class PictureStorage implements IPictureStorage {// ...@Overridepublic void savePicture(Picture picture) {...}@Overridepublic Image getPicture(String pictureId) {...}@Overridepublic void deletePicture(String pictureId) {...}@Overridepublic void modifyMetaInfo(String pictureId, PictureMetaInfo metaInfo) {...}}

上面的代码,使用了 Java 中的 interface 接口语法来实现抽象特性。调用者在使用图片存储功能时,只要了解 IPictureStorage 这个接口暴露了哪些方法就可以了,不需要去查看 PictureStorage 类里面的具体实现。

抽象的意义是什么?它能解决什么问题?

其实,抽象以及封装都是人类处理复杂问题的有效手段。在面对复杂系统的时候,人脑能承受的复杂度时有限的,所以我们必须忽略掉一些非关键性的实现细节。而抽象作为一种只关注功能点不关注实现的设计思路,正好帮我们的大脑过滤掉许多非必要的信息。

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

换一个角度劳考虑,我们在定义类的方法时,也要有抽象思维,不要在方法定义中,暴露太多细节,以保证在某个时间点需要改变方法的实现逻辑时,不用去修改其定义。举个简单例子,比如 getAliyunPictureUrl() 就不是一个具有抽象思维的命名,因为如果哪天我们不再把图片存储在阿里云上,而是存储在私有云上,那这个命名也要随之修改。相反,如果我们定义一个比较抽象的函数,比如叫做 getPictureUrl() ,那几遍内部存储方式修改了,也不需要修改命名。

3.继承

如果你熟悉 Java、C++ 这样的面向对象编程语言,那你对继承这一特性应该不陌生了。继承是用来表示类之间的 is-a 关系,比如猫是一种哺乳动物。从继承关系上来讲,继承可以分为单继承和多继承模式。

  • 单继承表示一个子类只继承一个父类
  • 多继承表示一个子类可以继承多个父类,比如脑既是哺乳动物,又是爬行动物。

编程语言需要提供特殊的语法来支持继承这个特性,比如 Java 使用 extends 关键字来实现继承,C++ 使用冒号(class B : public A)等等。另外,有些编程语言支持单继承,有些编程语言支持多重继承。

继承存在的意义是什么?它能解决什么问题?

继承最大的好处就是代码复用。比如,两个类有相同的属性和方法,我们可以把相同的部分抽取到父类中,让两个子类继承父类。这样,两个子类就可以重用父类中的代码,避免代码重复写多遍。

不过,我们可以使用其他方式来解决代码复用这个问题,比如利用组合关系。

不过,过度使用继承,继承层次过深,就会导致代码可读性、可维护下变差。为了了解一个类的功能,我们不仅需要查看这个类的代码,还要按照继承关系一层层地网上查看“父类、父类的父类、…” 代码。还有,子类和父类高度耦合,修改父类的代码,会直接影响到子类。

4.多态

多态是指,子类可以替换父类,在实际的代码运行过程中,调用子类的方法实现。对于多态,纯文字不好解释,我们结合例子来看一下。

public class DynamicArray {private static final int DEFAULT_CAPACITY = 10;protected int size = 0;protected int capacity = DEFAULT_CAPACITY;protected Integer elements = new Integer[DEFAULT_CAPACITY];public int size() { return this.size; }public Integer get(int index) { return elements[index]; }//...public void add (Integer e) {ensureCapacity();elements[size++] = e;}protected void ensureCapacity() {// ...如果数组满了就扩容...}
}public class StortedDynamicArray extends DynamicArray {@Overridepublic  void add (Integer e) {ensureCapacity();int i;for (i = this.size - 1; i >= 0; i--) { // 保证数组中的数据有序if (elements[i] > e) {elements[i+1] = elements[i];} else {break;}}elements[i+1] = e;++size;}
}public class Example {public static void main(String args[]) {DynamicArray dynamicArray = new StortedDynamicArray();dynamicArray.add(5);dynamicArray.add(1);dynamicArray.add(3);for (int i = 0; i < dynamicArray.size; i++) {System.out.println(dynamicArray.get(i));}}
}

多态这种特性要和需要编程语言提供特殊的语法机制来实现。在上面的例子中,使用到了三个语法机制来实现多态。

  • 第一个语法机制,是编程语言要支持父类对象可以引用子类对象,就是将 StortedDynamicArray 传递给 DynamicArray。
  • 第二个语法机制,是编程语言要支持继承,也就是 StortedDynamicArray 继承了 DynamicArray。
  • 第三个语法机制,是编程语言要支持子类可以重写(Override)父类中的方法,也就是 StortedDynamicArray 重写了 DynamicArray 的 add() 方法。

通过这三种语法机制配合在一起,就实现了在 test() 方法中,子类 StortedDynamicArray 替换父类 DynamicArray,执行 StortedDynamicArray 的 add() 方法。

对于多态这种特性,除了使用“继承家方法重写”这种方式之外,还可以利用接口类语法以及 duck-typing 语法。

接下来,先看下如何利用接口类来实现多态特性

public interface Iterator {boolean hasNext();String next();String remove();
}public class Array implements Iterator {private String[] data;public boolean hasNext() { ... } public String next() { ... } public String remove() { ... }//...省略其他方法...
}public class LinkedList implements Iterator {private LinkedListNode head;public boolean hasNext() { ... } public String next() { ... } public String remove() { ... } //...省略其他方法...
}public class Demo {private static void print(Iterator iterator) { while (iterator.hasNext()) { System.out.println(iterator.next());}}public static void main(String[] args) {Iterator arrayIterator = new Array();print(arrayIterator);Iterator linkedListIterator = new LinkedList();print(linkedListIterator);}
}

在这段代码中,Iterator 是一个接口类,定义了一个可以遍历集合数据的迭代器。Array 和 LinkedList 都实现了 Iterator 接口。我们通过传递不同的实现类到 print(Iterator iterator) 函数中,支持动态地调用不同的 next()、hasNext() 实现。

刚刚讲的是用接口类来实现多态。现在,我们在看下,如何用 duck-typing 来实现多态特性。下面是一段 python 代码例子。

class Logger:def record(selt):print("I write a log into file.")class DB:def record(selt):print("I insert a log into db.")def test(recorder):recorder.record()def demo():logger = Logger()db = DB()test(logger)test(db)

从这段代码,我们发现,duck-typing 实现多态的方式非常灵活。Logger 和 DB 两个类没有任何关系,既不是继承关系,也不是接口和实现关系,但是只要它们都定义了 record() 方法,就可以被传递到 test() 方法中,在实际运行的时候,执行对应的方法。

也就是说,只要两个类具有相同的方法,就可以实现多态,并不要求两个类之间有任何关系,这就是所谓的 duck-typing,它是一些动态语言所特有的语法机制。而像 Java 这样的静态语言,通过继承实现多态特性,必须要求两个类之间有继承关系或者是接口-实现的关系。

多态存在的意义是什么?它能解决什么问题?

多态可提供代码的可扩展性和复用性。可以回头看看刚刚举的第二个例子(Iterator 的例子)。

在那个例子中,利用多态的特性,仅用一个 print() 函数就可以实现遍历打印不同类型集合的数据。当再增加一种要遍历打印类型的时候,比如 HashMap,我们只要让它实现 Iterator 接口,重新实现自己的 hasNext()、next() 等方法就可以了,完全不需要改动 print() 函数的代码。所以说,多态提高了代码的可扩展性。

如果不使用多态,就无法将不同的集合类型传递给相同的函数,需要针对每种集合,都要实现打印函数。

此外,多态也是很多设计模式、设计原则、编程技巧的代码实现基础,比如策略模式、基于接口而非实现编程、依赖倒置原则、里氏替换原则、利用多态去掉冗长的 if-else 语句等等。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/692673.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

电脑恢复删除数据的原理和方法

在恢复数据的时候&#xff0c;很多人都会问&#xff0c;为什么删除的数据还能恢复&#xff1f;本篇和大家一起了解下硬盘上数据的存储方式&#xff0c;文件被删除的时候具体发生了什么&#xff0c;帮助大家理解数据恢复的基本原理。最后还会分享一个好用的数据恢复工具并附上图…

垂起固定翼无人机基础知识,垂起固定翼无人机应用前景,垂起固定翼无人机优缺点分析

无人机定义与类型 无人机&#xff0c;也称为无人驾驶飞行器&#xff0c;是一种无需人工直接操作的航空器。根据其用途、设计及技术特点&#xff0c;可以分为多种类型。垂起固定翼无人机是其中的一种&#xff0c;它具有垂直起降的能力并采用固定翼设计以提高飞行效率和稳定性。…

Excel SUMPRODUCT函数用法(成绩求和,分组排序)

SUMPRODUCT函数是Excel中功能比较强大的一个函数&#xff0c;可以实现sum,count等函数的功能&#xff0c;也可以实现一些基础函数无法直接实现的功能&#xff0c;常用来进行分类汇总&#xff0c;分组排序等 SUMPRODUCT 函数基础 SUMPRODUCT函数先计算多个数组的元素之间的乘积…

解决flask结合layui前端框架模板(laytpl 语法)与gin语法出现冲突的问题。

在模板中加了laytpl的写法 例如&#xff1a; gin框架渲染数据的语法也是{{ }} 例如&#xff1a; 如何两者都出现在html模板页&#xff0c;运行gin框架会识别为框架定义的变量&#xff0c;运行之后发现报如上错误&#xff0c;出现了错误是因为刚好gin的写法也是"{{ 变量 }…

【JavaEE】_CSS选择器

目录 1. 基本语法格式 2. 引入方式 2.1 内部样式 2.2 内联样式 2.3 外部样式 3. 基础选择器 3.1 标签选择器 3.2 类选择器 3.3 ID选择器 4. 复合选择器 4.1 后代选择器 4.2 子选择器 4.3 并集选择器 4.4 伪类选择器 1. 基本语法格式 选择器若干属性声明 2. 引入…

力扣:40. 组合总和 II

回溯&#xff1a; 1.先声明好大集合和小集合&#xff0c;在调用回溯函数&#xff0c;终止条件为sumtarget&#xff0c;要进行剪枝操作减少遍历的次数&#xff0c;去重操作防止数组中有两个相同的值来组成的集合相同。 class Solution {List<List<Integer>> li1ne…

Leetcode日记 290. 单词规律 给定一种规律 pattern 和一个字符串 s ,判断 s 是否遵循相同的规律。 这里的 遵循 指完全匹配

Leetcode日记 290. 单词规律 给定一种规律 pattern 和一个字符串 s &#xff0c;判断 s 是否遵循相同的规律。 这里的 遵循 指完全匹配 解题思路制作不易&#xff0c;感谢三连&#xff0c;谢谢啦 给定一种规律 pattern 和一个字符串 s &#xff0c;判断 s 是否遵循相同的规律。…

ChatGPT:你的数字生活助手

ChatGPT&#xff1a;你的数字生活助手 随着人工智能的飞速发展&#xff0c;AI写作技术成为了一个备受争议的话题。ChatGPT作为OpenAI开发的先进语言模型&#xff0c;不仅提供了对话和问题解答服务&#xff0c;还成为了提升日常工作和生活效率的强大工具。然而&#xff0c;许多…

WEB基础及http协议(Apache)

一、httpd安装组成 http服务基于C/S结构 1、常见http服务器程序 httpd apache&#xff0c;存在C10K&#xff08;10K connections&#xff09;问题nginx 解决C10K问题lighttpdIIS .asp 应用程序服务器tomcat .jsp 应用程序服务器jetty 开源的servlet容器&#xff0c;基于Java…

【Java代码审计】本地命令执行函数

1.本地命令执行函数 在服务器中时常会调用命令执行的代码&#xff0c;以完善或加强系统的功能需求&#xff0c;一旦这些调用命令执行的接口被攻击者恶意利用&#xff0c;就会导致服务器沦陷。 在Java中可用于执行系统命令的方式有API有&#xff1a;java.lang.Runtime、java.la…

基于微信小程序的比赛赛程管理系统设计与实现

在全面健身的倡导下通过各级赛事的举办完成体育人才的选拔&#xff0c;当由于缺乏信息化的管理手段而只能通过人工完成比赛报名、赛程制定及成绩记录等流程的管理&#xff0c;因此常常因意外而导致比赛赛程管理不善、成绩不理想等问题出现。为了帮助比赛组织者优化赛程管理流程…

最大加权矩阵(洛谷)

题目 原题 题目描述 为了更好的备战 NOIP2013&#xff0c;电脑组的几个女孩子 LYQ,ZSC,ZHQ 认为&#xff0c;我们不光需要机房&#xff0c;我们还需要运动&#xff0c;于是就决定找校长申请一块电脑组的课余运动场地&#xff0c;听说她们都是电脑组的高手&#xff0c;校长没有…

【Webpack】基本配置

核心概念 entry&#xff08;入口&#xff09; 指示 Webpack 从哪个文件开始打包 output&#xff08;输出&#xff09; 指示 Webpack 打包完的文件输出到哪里去&#xff0c;如何命名等 loader&#xff08;加载器&#xff09; webpack 本身只能处理 js、json 等资源&#x…

微服务建构思想

微服务架构思想 微服务架构优点 1、易于开发和维护:一个微服务只会关注一个特定的业务功能。所以它业务清晰。代码量较少。开发和维护 单个微服务相对简单。而整个应用是由若干个微服务构建而成的。 2、单个微服务启动较快:单个微服务代码量较少&#xff0c;所以启动比较快。 …

CSS position属性sticky

在开发时&#xff0c;经常会碰到需要这样一种情况 —— 网站滚动到一定高度的时候&#xff0c;让一部分内容作为navbar&#xff0c;也就是置顶显示&#xff0c;我们一般会使用js监听scroll事件来实现&#xff0c;但是新增的css3属性position:sticky可以简单实现&#xff0c;省去…

【动态规划专栏】专题二:路径问题--------6.地下城游戏

本专栏内容为&#xff1a;算法学习专栏&#xff0c;分为优选算法专栏&#xff0c;贪心算法专栏&#xff0c;动态规划专栏以及递归&#xff0c;搜索与回溯算法专栏四部分。 通过本专栏的深入学习&#xff0c;你可以了解并掌握算法。 &#x1f493;博主csdn个人主页&#xff1a;小…

C# 二分查找

二分查找&#xff08;Binary Search&#xff09;是一种在有序数组或列表中查找特定元素的搜索算法。该算法比较要搜索的值和数组的中间元素。如果要搜索的值小于中间元素&#xff0c;则在数组的左半部分继续搜索&#xff1b;如果要搜索的值大于中间元素&#xff0c;则在数组的右…

Vue | (三)使用Vue脚手架(中)| 尚硅谷Vue2.0+Vue3.0全套教程

文章目录 &#x1f4da;Todo-list 案例&#x1f407;组件化编码流程&#xff08;通用&#xff09;&#x1f407;实现静态组件&#x1f407;展示动态数据&#x1f407;交互⭐️添加一个todo⭐️todo勾选实现⭐️删除功能实现⭐️底部统计功能实现⭐️底部全选功能实现⭐️底部一…

每日一练:前端js实现算法之两数之和

方法一&#xff1a;暴力法 function twoSum(nums, target) {for (let i 0; i < nums.length; i) {for (let j i 1; j < nums.length; j) {if (nums[i] nums[j] target) {return [i, j];}}}return null; }方法二&#xff1a;哈希表 function twoSum(nums, target) …

函数——递归4(c++)

正整数N转换成一个二进制数 题目描述 输入一个不大于 32767 的整数 n &#xff0c;将它转换成一个二进制数。 输入 输入只有一行&#xff0c;包括一个整数 n (0≤ n ≤32767)。 输出 输出只有一行。 样例 输入复制 100 输出复制 1100100 输入复制 0 输出复制…