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

news/发布时间2024/5/18 14:57:11

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.bcls.cn/rPSa/1468.shtml

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

相关文章

常见的几种Web安全问题测试简介

Web项目比较常见的安全问题 1.XSS(CrossSite Script)跨站脚本攻击 XSS(CrossSite Script)跨站脚本攻击。它指的是恶意攻击者往Web 页面里插入恶意html代码&#xff0c;当用户浏览该页之时&#xff0c;嵌入其中Web 里面的html 代码会被执行&#xff0c;从而达到恶意用户的特殊…

GitCode配置ssh

下载SSH windows设置里选“应用” 选“可选功能” 添加功能 安装这个 坐等安装&#xff0c;安装好后可以关闭设置。 运行 打开cmd 执行如下指令&#xff0c;启动SSH服务。 net start sshd设置开机自启动 把OpenSSH服务添加到Windows自启动服务中&#xff0c;可避免每…

3dmax渲染为什么是黑色?渲染100邀请码1a12

3dmax是室内设计常用的建模软件&#xff0c;利用3dmax完成建模后最重要的工作就是渲染了&#xff0c;好的渲染效果能帮助推广你的项目&#xff0c;但有时候会遇到渲染出来是黑色的情况&#xff0c;这是为什么呢&#xff1f;一起来看看吧&#xff01; 原因一&#xff1a;相机被挡…

FPGA之多路复选器1

7系列FPGA中的LTU和相关的多路复选器可以实现以下功能: 使用一个LUT的4: 1多路复选器 使用两个LUT的8: 1多路复选器 使用四个 LUT 的 16: 1多路复选器 4:1复选器 1个LUT可以配置为4: 1多路复选器。4:1多路复选器可以通过触发器在同一片中实现。一个slice中最多可以实现四个…

ArcgisForJS基础

文章目录 0.引言1.第一个ArcgisForJS应用程序1.1.安装部署ArcgisForJS1.2.实现ArcgisForJS应用程序 2.开发与调试工具2.1.集成开发环境2.2.调试工具2.3.Firebug 0.引言 ArcGIS API for JavaScript是一款由Esri公司开发的用于创建WebGIS应用的JavaScript库。它允许开发者通过调…

stm32——hal库学习笔记(IWDG)

这里写目录标题 一、IWDG简介&#xff08;了解&#xff09;二、IWDG工作原理&#xff08;熟悉&#xff09;![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/ae55fb4f2d2f49edb468122f67de67e4.png)三、IWDG框图&#xff08;熟悉&#xff09;四、IWDG寄存器&#xff…

OpenHarmony系统解决方案 - 配置屏幕方向导致开机动画和Launcher显示异常

问题环境 系统版本&#xff1a;OpenHarmony-3.2-Release 问题现象 配置设备默认方向&#xff0c;例如修改为横屏显示&#xff0c;修改文件display_manager_config.xml的buildInDefaultOrientation参数值为2(Orientation::HORIZONTAL)。 源码中文件位于foundation/window/win…

HTML学习笔记——08:表单<form>

HTML <form> 元素表示文档中的一个区域&#xff0c;此区域包含交互控件&#xff0c;用于向 Web 服务器提交信息。 例如&#xff1a;登录页面。 作用&#xff1a;搜集不同类型的用户输入&#xff0c;并向服务器传送数据。 注意&#xff1a;表单本身并不可见&#xff01;…

CSS概述 | CSS的引入方式 | 选择器

文章目录 1.CSS概述2.CSS的引入方式2.1.内部样式表2.2.行内样式表2.3.外部样式表 3.选择器 1.CSS概述 CSS&#xff0c;全称Cascading Style Sheets&#xff08;层叠样式表&#xff09;&#xff0c;是一种用来设置HTML&#xff08;或XML等&#xff09;文档样式的语言。CSS的主要…

HTML世界之第二重天

目录 一、HTML 格式化 1.HTML 文本格式化标签 2.HTML "计算机输出" 标签 3.HTML 引文, 引用, 及标签定义 二、HTML 链接 1.HTML 链接 2.HTML 超链接 3.HTML 链接语法 4.文本链接 5.图像链接 6.锚点链接 7.下载链接 8.Target 属性 9.Id 属性 三、HTML …

node+vue3+mysql前后分离开发范式——实现对数据库表的增删改查

文章目录 ⭐前言⭐ 功能设计与实现💖 node后端操作数据库实现增删改查💖 vue3前端实现增删改查⭐ 效果⭐ 总结⭐结束⭐前言 大家好,我是yma16,本文分享关于 node+vue3+mysql前后分离开发范式——实现对数据库表的增删改查。 技术选型 前端:vite+vue3+antd 后端:node k…

【C++】const、static关键字和构造函数初始化

&#x1f497;个人主页&#x1f497; ⭐个人专栏——C学习⭐ &#x1f4ab;点击关注&#x1f929;一起学习C语言&#x1f4af;&#x1f4ab; 目录 1. const修饰成员函数 1.1 语法格式 1.2 权限放大缩小 1.3 思考 1.4 解答 2. 再谈构造函数 2.1 构造函数体赋值 2.2 初始…

2024云服务器ECS_云主机_服务器托管_e实例-阿里云

阿里云服务器ECS英文全程Elastic Compute Service&#xff0c;云服务器ECS是一种安全可靠、弹性可伸缩的云计算服务&#xff0c;阿里云提供多种云服务器ECS实例规格&#xff0c;如ECS经济型e实例、通用算力型u1、ECS计算型c7、通用型g7、GPU实例等&#xff0c;阿里云服务器网al…

harmony 鸿蒙系统学习 安装ohpm报错 ohpm install failed

一. 安装配置 DevEco Studio 安装包时报错 execute ohpm install failed. Install task failed: ArkTS 3.2.12.5. Install ArkTS dependencies failed. 解决办法 找原因&#xff0c;首先&#xff0c;我的电脑中之前安装过node&#xff0c;也许是因为这个。&#xff08;其实…

租用一个服务器需要多少钱?2024阿里云新版报价

2024年最新阿里云服务器租用费用优惠价格表&#xff0c;轻量2核2G3M带宽轻量服务器一年61元&#xff0c;折合5元1个月&#xff0c;新老用户同享99元一年服务器&#xff0c;2核4G5M服务器ECS优惠价199元一年&#xff0c;2核4G4M轻量服务器165元一年&#xff0c;2核4G服务器30元3…

MySQL篇—事务和隔离级别介绍

☘️博主介绍☘️&#xff1a; ✨又是一天没白过&#xff0c;我是奈斯&#xff0c;DBA一名✨ ✌✌️擅长Oracle、MySQL、SQLserver、Linux&#xff0c;也在积极的扩展IT方向的其他知识面✌✌️ ❣️❣️❣️大佬们都喜欢静静的看文章&#xff0c;并且也会默默的点赞收藏加关注❣…

Unable to make field private JavacProcessingEnvironment$DiscoveredPro报错解决办法

maven项目打包报错 报错信息 Unable to make field private com.sun.tools.javac.processing.JavacProcessingEnvironment$DiscoveredProcessors com.sun.tools.javac.processing.JavacProcessingEnvironment.discoveredProcs accessible: module jdk.compiler does not &q…

欲速则不达,慢就是快!

引言 随着生活水平的提高&#xff0c;不少人的目标从原先的解决温饱转变为追求内心充实&#xff0c;但由于现在的时间过得越来越快以及其他外部因素&#xff0c;我们对很多东西的获取越来越没耐心&#xff0c;例如书店经常会看到《7天精通Java》、《3天掌握XXX》等等之类的书籍…

Linux系统之iptables应用SNAT与DNAT

一、SNAT&#xff1a; 1.应用环境 局域网主机共享单个公网IP地址接入Internet &#xff08;私有IP不能在Internet中正常路由&#xff09; 2.SNAT原理 源地址转换&#xff0c;根据指定条件修改数据包的源IP地址&#xff0c;通常被叫做源映谢数据包从内网发送到公网时&#x…

一个服务器实现本机服务互联网化

欢迎来到我的博客&#xff0c;代码的世界里&#xff0c;每一行都是一个故事 一个服务器实现本机服务互联网化 前言痛点关于中微子代理实战演练搭建服务端搭建客户端服务端配置代理实现 前言 在数字世界的网络战场上&#xff0c;中微子代理就像是一支潜伏在黑暗中的数字特工队&…
推荐文章