【重温设计模式】面向对象,面向过程编码
一、为什么要学习设计模式
1、为什么学习设计模式
- 数据结构与算法之美”让你写出高效的代码,那这个设计模就是让你写出高质量的代码。
- 告别写被人吐槽的烂代码
- 提高复杂代码的设计和开发能力
- 让读源码、学框架事半功倍
- 为你的职场发展做铺垫
2、好的代码标准
1. 可维护性(maintainability)
- 所谓“代码易维护”就是指,在不破坏原有代码设计、不引入新的 bug 的情况下,能够快速地修改或者添加代码。所谓“代码不易维护”就是指,修改或者添加代码需要冒着极大的引入新 bug 的风险,并且需要花费很长的时间才能完成。
2. 可读性(readability)
- 代码是否符合编码规范、命名是否达意、注释是否详尽、函数是否长短合适、模块划分是否清晰、是否符合高内聚低耦合等等。
3. 可扩展性(extensibility)
- 表示我们的代码应对未来需求变化的能力。跟可读性一样,代码是否易扩展也很大程度上决定代码是否易维护。
- 代码的可扩展性表示,我们在不修改或少量修改原有代码的情况下,通过扩展的方式添加新的功能代码。说直白点就是,代码预留了一些功能扩展点,你可以把新功能代码,直接插到扩展点上,而不需要因为要添加一个功能而大动干戈,改动大量的原始代码。
- 关于代码的扩展性,在后面讲到“对修改关闭,对扩展开放”这条设计原则的时候。
4. 灵活性(flexibility)
- 当我们添加一个新的功能代码的时候,原有的代码已经预留好了扩展点,我们不需要修改原有的代码,只要在扩展点上添加新的代码即可。这个时候,我们除了可以说代码易扩展,还可以说代码写得好灵活。
- 当我们要实现一个功能的时候,发现原有代码中,已经抽象出了很多底层可以复用的模块、类等代码,我们可以拿来直接使用。这个时候,我们除了可以说代码易复用之外,还可以说代码写得好灵活。
- 当我们使用某组接口的时候,如果这组接口可以应对各种使用场景,满足各种不同的需求,我们除了可以说接口易用之外,还可以说这个接口设计得好灵活或者代码写得好灵活。
5. 简洁性(simplicity)
- 思从深而行从简,真正的高手能云淡风轻地用最简单的方法解决最复杂的问题。这也是一个编程老手跟编程新手的本质区别之一。
- 代码要尽量写得简洁,符合 KISS 原则:(待补充)
6. 可复用性(reusability)
- 简单地理解为,尽量减少重复代码的编写,复用已有的代码。
- 解耦、高内聚、模块化等都能提高代码的可复用性;
- 单一职责原则也跟代码的可复用性相关;
- 继承、多态存在的目的之一,就是为了提高代码的可复用性
- DRY 设计原则
7. 可测试性(testability)
- 代码可测试性的好坏,能从侧面上非常准确地反应代码质量的好坏。代码的可测试性差,比较难写单元测试,那基本上就能说明代码设计得有问题。关于代码的可测试性,我们在重构那一部分,会花两节课的时间来详细讲解。现在,你暂时只需要知道,代码的可测试性非常重要就可以了。
二、面向对象
1、面向对象四大特性
- 封装
- 抽象
- 继承
- 多态
(1)封装
- 定义:封装也叫作信息隐藏或者数据访问保护。类通过暴露有限的访问接口,授权外部仅能通过类提供的方式(或者叫函数)来访问内部信息或者数据
- 意义和价值:信息隐藏或者数据访问保护,对类的数据访问进行限制,保证类的数据的可控性;类仅仅通过有限的方法暴露必要的操作,也能提高类的易用性;(电视和电冰箱就几个按钮,内部实现很复杂)
(2)抽象
- 定义:如何隐藏方法的具体实现,让调用者只需要关心方法提供了哪些功能,并不需要知道这些功能是如何实现的。
- 意义和价值:抽象作为一个非常宽泛的设计思想,在代码设计中,起到非常重要的指导作用。很多设计原则都体现了抽象这种设计思想,比如基于接口而非实现编程、开闭原则(对扩展开放、对修改关闭)、代码解耦(降低代码的耦合性);一方面是提高代码的可扩展性、维护性,修改实现不需要改变定义,减少代码的改动范围;另一方面,它也是处理复杂系统的有效手段,能有效地过滤掉不必要关注的信息;
(3)继承
- 定义:继承是用来表示类之间的 is-a 关系,比如猫是一种哺乳动物。从继承关系上来讲,继承可以分为两种模式,单继承和多继承。单继承表示一个子类只继承一个父类,多继承表示一个子类可以继承多个父类,比如猫既是哺乳动物,又是爬行动物。
- 意义和价值:继承最大的一个好处就是代码复用;继承这个特性也是一个非常有争议的特性。很多人觉得继承是一种反模式。我们应该尽量少用,甚至不用。“多用组合少用继承”这种设计思想;
(4)多态
- 定义:多态是指,子类可以替换父类,在实际的代码运行过程中,调用子类的方法实现
- 意义和价值:多态提高了代码的可扩展性。多态也是很多设计模式、设计原则、编程技巧的代码实现基础,比如策略模式、基于接口而非实现编程、依赖倒置原则、里式替换原则、利用多态去掉冗长的 if-else 语句等等
2、六大设计原则
- SOLID 原则 -SRP 单一职责原则
- SOLID 原则 -OCP 开闭原则
- SOLID 原则 -LSP 里式替换原则
- SOLID 原则 -ISP 接口隔离原则
- SOLID 原则 -DIP 依赖倒置原则
- DRY 原则、KISS 原则、YAGNI 原则、LOD 法则
3、23种设计模式
1. 创建型
- 常用的有:单例模式、工厂模式(工厂方法和抽象工厂)、建造者模式。
- 不常用的有:原型模式。
2. 结构型
- 常用的有:代理模式、桥接模式、装饰者模式、适配器模式。
- 不常用的有:门面模式、组合模式、享元模式。
3. 行为型
- 常用的有:观察者模式、模板模式、策略模式、职责链模式、迭代器模式、状态模式。
- 不常用的有:访问者模式、备忘录模式、命令模式、解释器模式、中介模式。
4、重构
- 重构的目的(why)、对象(what)、时机(when)、方法(how);
- 保证重构不出错的技术手段:单元测试和代码的可测试性;
- 两种不同规模的重构:大重构(大规模高层次)和小重构(小规模低层次)。
三、面向对象编程的指导思想和原则
1、什么是面向对象编程
- 面向对象编程是一种编程范式或编程风格。它以类或对象作为组织代码的基本单元,并将封装、抽象、继承、多态四个特性,作为代码设计和实现的基石 。
2、抽象类和接口
- 抽象类:表示类之间的 is-a 关系,不能被实力化,解决代码复用问题,同时保持多态特性。 自下而上的行为,先发现子类逻辑重复,再抽象到抽象类。
- 接口:表示解藕,对行为的抽象,基于接口而非实现的编程思想。提高程序的可扩展性,和灵活性。自上而下的设计行为,先定义规则,再去实现。
3、接口(面向接口而非实现编程思想)
- 接口是一组协议,或者约定。是功能提供者提供给使用者的一组功能列表。
- 将接口和实现分离,封装不稳定的实现,暴露稳定的接口给上游系统,上游系统面向稳定的接口而非实现编程,不依赖不稳定的实现细节,当实现发生变化时,上游系统不用改动,以此降低耦合性,提高代码扩展性。
- 接口的定义,要抽象,共性,不要有特殊性的定义。
4、多用组合少用继承
(1)继承
- 继承最大的问题就在于:继承层次过深、继承关系过于复杂会影响到代码的可读性和可维护性。这也是为什么我们不推荐使用继承;
- 如果类之间的继承结构稳定(不会轻易改变),继承层次比较浅(比如,最多有两层继承关系),继承关系不复杂,我们就可以大胆地使用继承。
(2)组合
- 利用组合(composition)、接口、委托(delegation)三个技术手段,一块儿来解决刚刚继承存在的问题。
- 继承层次深,且容易变化,利用组合还能解决层次过深、过复杂的继承关系影响代码可维护性的问题。
基于继承实现的代码示意
/** * 会吃的鸟 */ abstract class AbstractBird { abstract void eat(); } /** * 会吃,会飞,会下蛋的鸟 */ abstract class AbstractFlyAndEggBird extends AbstractBird { abstract void fly(); abstract void egg(); } /** * 会吃,会叫,会游泳的鸟 */ abstract class AbstractTweetAndSwimBird extends AbstractBird { abstract void tweet(); abstract void swim(); } /** * 鸡 */ class Chicken extends AbstractFlyAndEggBird { @Override void eat() { System.out.println("吃东西"); } @Override void fly() { System.out.println("飞"); } @Override void egg() { System.out.println("下蛋"); } } /** * 鸭 */ class Duck extends AbstractTweetAndSwimBird { @Override void eat() { System.out.println("吃东西"); } @Override void tweet() { System.out.println("叫"); } @Override void swim() { System.out.println("游泳"); } }
基于组合实现的代码示意(组合、接口、委托)
/** * 吃 */ interface Eat { void eat(); } /** * 飞 */ interface Fly { void fly(); } /** * 下蛋 */ interface Egg { void egg(); } /** * 叫 */ interface Tweet { void tweet(); } /** * 游泳 */ interface Swim { void swim(); } class Earter implements Eat { @Override public void eat() { System.out.println("吃"); } } class FlyEr implements Fly { @Override public void fly() { System.out.println("飞"); } } class Egger implements Egg { @Override public void egg() { System.out.println("下蛋"); } } class TweetEr implements Tweet { @Override public void tweet() { System.out.println("叫"); } } class SwimEr implements Swim { @Override public void swim() { System.out.println("游泳"); } } /** * 鸡 */ class Chicken1 implements Eat, Fly, Egg { private Eat eat = new Earter(); private Fly fly = new FlyEr(); private Egg egg = new Egger(); @Override public void eat() { eat.eat(); } @Override public void fly() { fly.fly(); } @Override public void egg() { egg.egg(); } } /** * 鸭 */ class Duck1 implements Eat, Tweet, Swim { private Eat eat = new Earter(); private Tweet tweet = new TweetEr(); private Swim swim = new SwimEr(); @Override public void eat() { eat.eat(); } @Override public void tweet() { tweet.tweet(); } @Override public void swim() { swim.swim(); } }