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

这篇具有很好参考价值的文章主要介绍了设计模式学习笔记 - 面向对象 - 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 {
	// ...

	@Override
	public void savePicture(Picture picture) {...}
	@Override
	public Image getPicture(String pictureId) {...}
	@Override
	public void deletePicture(String pictureId) {...}
	@Override
	public 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 {
	@Override
	public  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 语句等等。文章来源地址https://www.toymoban.com/news/detail-832875.html

到了这里,关于设计模式学习笔记 - 面向对象 - 2.封装、抽象、继承、多态分别用来解决哪些问题?的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • javascript设计模式-面向对象基础

    在JS这种弱类型的语言中,类型不匹配错误很难跟踪。可以使用接口来进行类型检查。如果强制使用,又会弱化语言的灵活性。因为有额外的代码调用,所以又会降低性能。解决方法就是在开发时进行类型检查,在开始完成后删除此部分代码。 但JS中的接口实现方式是模拟的

    2024年01月18日
    浏览(26)
  • C++设计模式_02_面向对象设计原则

    变化是复用的天敌!面向对象设计或者说使用了抽象原则的面向对象设计最大的优势在于#

    2024年02月11日
    浏览(29)
  • C++中的面向对象设计模式实践

    面向对象程序设计(Object-Oriented Programming,简称OOP)是一种将程序设计问题分解为对象的思维方式。它通过定义对象和对象之间的关系,将问题模型化并转化为代码实现。在面向对象设计模式中,设计模式是一种被普遍接受的解决问题的方法论。 C++作为一种多范式编程语言,

    2024年01月17日
    浏览(27)
  • 设计模式 -- 策略模式(传统面向对象与JavaScript 的对比实现)

    规则:根据员工的工资基数和年底绩效情况计算年终奖 初级实现 缺点 多重 if else 违反开发-封闭原则,可维护性差 复用性差 使用组合函数重构代码 使用组合函数来重构代码,把各种算法封装到一个个的小函数里面,这些小函数有着良好的命名,可以一目了然地知道它对应着

    2024年02月11日
    浏览(32)
  • 一网打尽java注解-克隆-面向对象设计原则-设计模式

    注解 :也叫标注,用于包、类、变量、方法、参数上。可以通过反射获取标注。可以在编译期间使用,也可以被编译到字节码文件中,运行时生效。 内置注解 :Java语言已经定义好的注解。 @Overread :用于方法重写。 @Deprecated :标记过时方法。 @SuppressWarnings :指示编译器去

    2024年02月11日
    浏览(27)
  • 2.python设计模式【面向对象设计的SOLID原则 基础概念】

    概念:一个软件实体如类、模块和函数应该对扩展开放,对修改关闭。即软件实体应该尽量在不修改原有代码的情况下进行扩展 概念:所有引用父类的地方必须能透明地使用其子类的对象 概念:高层模块不应该依赖低层模块,二者都应该依赖其抽象;抽象不依赖细节;细节

    2024年02月16日
    浏览(25)
  • 基于C语言的面向对象设计模式(持续更新)

    首先这篇文章只是初步的尝试,不涉及过于高深的编程技巧;同时需要表明的是, 面向对象只是一种思想 ,不局限于什么样的编程语言,不可否认的是基于面向对象特性而设计的语言确实要比面向过程式的语言更加容易进行抽象和统筹,可以说面向对象的设计模式可以很大

    2024年04月10日
    浏览(29)
  • 【WinForm】C#实现商场收银软件,从面向过程到面向对象,设计模式的应用

    实现商场收银系统从简单的面向过程到面向对象的演变。 最容易想到的: 单价*数量=总价 根据输入的单价和数量,直接计算,将结果显示在listbox控件中。 重置按钮可以清零。 1、运行效果 2、界面设计 3、代码 版本2在版本1的基础上增加了打折优惠。 1、运行效果 打折下拉框

    2024年02月09日
    浏览(31)
  • 【Java基础教程】(十五)面向对象篇 · 第九讲:抽象类和接口——定义、限制与应用的细节,初窥模板设计模式、工厂设计模式与代理设计模式~

    掌握 抽象类和接口的定义、使用、区别、常见设计模式; 抽象类是代码开发中的重要组成部分,利用抽象类可以明确地定义子类需要覆写的方法,这样相当于在语法程度上对子类进行了严格的定义限制,代码的开发也就更加标准。下面具体介绍抽象类的概念。 普通类可以直

    2024年02月16日
    浏览(32)
  • java的面向对象编程(oop)——static概述及初始单例设计模式

    过了入门阶段,开始学习进阶语法了。每天进步一点点,打好基础,daydayup! 什么是面向对象编程(oop),可以看这篇 java的面向对象编程(oop)概述及案例  static的意思为静态,用于修饰成员变量及成员方法。 成员变量根据有无static可以分为两种 ——类变量及实例变量 1,类

    2024年01月19日
    浏览(35)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包