1. 设计模式我会啊,还用你教?
受益于Spring的广泛应用,大家对单例模式和工厂模式耳熟能详。
设计模式归纳的有23种,
可以划分为包括创建型、结构型、行为型三大类。
除了简单的那两种,其他的各种设计模式到底在什么场合使用呢。
本系列将给大家举出案例,带大家一步步的了解和深入。
三种模式的理解难度,应该是递进的,创建型最容易。
因此先从最简单的开始。
创建型模式共有五类,如下:
序号 | 名称 | 英文 |
1 | 工厂方法 | factory method |
2 | 抽象工厂 | abstract factory |
3 | 单例 | singleton |
4 | 建造者 | builder |
5 | 原型 | prototype method |
因为这里面会大量涉及到面向对象的知识,首先从基础部分讲起。
2. 面向对象三要素
OO的三要素:封装、继承、多态,这几个内容我们简要说一下。
2.1 封装
封装最常见,我们有三个关键词:
public protected private
用于修饰属性和方法。
其中,public是公开的,对外和对内皆可用;
private是仅对内使用的。
这就是最基本的封装。
那问题来了,protected是干啥的?
protected是一个双重身份,首先他不对外,仅对内部使用。
看起来很像private。
但是,子类可以使用父类的protected属性或方法。
因此,如果没有继承的概念,protected就相当于private。
下面讲讲继承。
2.2 继承
继承也不难,万物继承于Object
集合继承于Collection
I/O流继承于Closeable
线程继承于Runnable
标准的JDK给我们做了一个很好的示范
继承给我们解决了两个重要功能:
- 继承父类,就可以继承它已经具备的能力
- 可以采用增加子类的方式,扩充新功能
由于父类只能有一个,
因此如果需要继承多个父类,只能采用多层方式,
例如 B extends A, 然后 C extends B,
这种继承体验很差。
因此凡是多重继承,一般都采用接口继承的方式。
这里面有包括接口、抽象类、重载、覆盖等知识点,以及多态性的知识,大家自行学习,不再赘述。
写程序的时候,推荐的是先写接口,再写实现
我们的项目结构往往是这样的:
class DemoController implements IDemoFeign class DemoServiceImpl implements IDemoService class DemoDaoHibernate implements IDemoDao
特别是新人,常常问到,我直接写class不行啊?
非要多写个interface有啥用啊?
为了分层而分层吗?
你需要理解一个关键知识点:里氏替换原则
2.3 里氏替换
里氏替换对继承关系做了限制
要求子类可以继承父类的功能,但不能改变父类的功能。
这就对子类覆盖重写父类方法下了禁令。
里氏替换不是万能的,它强调的是对兼容性和复用性的侧重。
代码里经常会写:
List list=new ArrayList();
这就是说,我只需要知道List接口的用法即可,
ArrayList只是具有特点的一个扩展实现而已,
并不影响使用者的理解,只要会用List接口即可。
这种 父类(接口)= new 子类() 的写法,广泛用于各种领域,
例如Spring的DIP,依赖倒置,往往都是建议定义接口优先,面向接口编程。
这就是可以解答前面的疑问,为什么我们代码里往往都是写
class DemoServiceImpl implements IDemoService
越来越多的人开始使用Slf4j,这也是一个接口编程的典型案例。
掌握这些基础后,开始学习最简单的工厂模式。
3. 工厂模式
3.1 简单工厂模式
简单的工厂其实没有太大的应用价值,
他只是符合工厂模式:对象不是new出来的,而是工厂类生产的,这一特点
例如:
public class FruitFactory{ public Apple createApple(){ //TODO } public Banana createBanana(){ //TODO } public Fruit createFruit(String type){ //TODO if ... else } }
或者可以采用静态方法,可以不要创建工厂对象
public class FruitFactory{ public static Apple createApple(){ //TODO } public static Banana createBanana(){ //TODO } public static Fruit createFruit(String type){ //TODO if ... else } }
我们这个例子,虽有其形,但意义不大,这个工厂太开放了,没有任何约束
工厂真正要解决的是产品出口一致性,以及产品之间的相关性。
一个工厂,又生产铁锅,又生产水果,还生产铅笔,这像话吗?
写这种自己都不知道干什么的工厂,
就是程序员给自己挖坑造bug。
所以我们要做有意义的工厂。
解决方式很简单:
1.通过规范编码,使简单工厂满足使用价值
2.通过规范工厂,使工厂的产品明确
3.2 工厂方法模式
场景:我们需要开发一个发送消息的功能
首先定义接口:
interface IMessage{ void send(String text); }
假设,我们的系统支持 短信通知、微信通知 两种模式,
那自然会有:
public class SmsMessage implements IMessage{ public void send(String text){ //TODO 短信通知 } } public class WechatMessage implements IMessage{ public void send(String text){ //TODO 微信通知 } }
这些我们可以认为是具体的功能实现,划分在组件库中。
对于高级程序员,并不在乎这些东西怎么实现的,只要能满足功能就行了。
然后,将他们写在不同的工厂里
public interface IMessageFactory{ IMessage getInstance(); } public SmsMessageFactory implements IMessageFactory{ public IMessage getInstance(){ return new SmsMessage(); } } public WechatMessageFactory implements IMessageFactory{ public IMessage getInstance(){ return new WechatMessage(); } }
所以这种方式,将选择权交给了工厂类。
你不要让我选用哪个具体的产品,我也选不来,直接告诉我工厂就行。
想象一个场景,太太想买一辆车,你会如何选择?
列出一堆参数,让她选吗?其实没那么复杂
她只会说:老公,我们买一辆奔驰怎么样?
或者说:老公,我闺蜜买了一辆宝马的车,我觉得很好看。
这选的不是车,而是工厂。
这种模式,适合于关注点在工厂的选择,而不是产品本身的情况。
比如我需要接入短信平台,你给我建3个工厂类,移动、联通、电信。
作为用的人,我只要选择厂家,无需关注短信平台这个细节。
3.3 抽象工厂模式
抽象工厂模式用的较少,主要应对复杂的工厂和对象的组合情况。
作为不同的工厂,其中的产品具有共性,或者类似,
则可以用抽象工厂来对工厂再做一次抽象。
一般的项目里,用不到这种场景。
目前比较被大家熟知的例子,就是Spring的BeanFactory
先将产品归类分组,再将其抽象成工厂类。
短信运营商的例子不太好讲,因为国内就只有三巨头,
最多3个工厂类就能解决。
还是之前买车的例子,设想一个场景,
现在选择的范围不仅仅是宝马,奔驰,
还有劳斯莱斯、宾利、迈巴赫、法拉利、兰博基尼、迈凯伦、阿斯顿马丁、布加迪……
你作为一个程序员,该怎么写呢?
每来一个品牌,建一个工厂类?明显不现实,这得频繁修改代码。
所以这时候要对工厂再次抽象,原来我们的类是这样的:
奔驰车=奔驰厂.create(); 宝马车=宝马厂.create(); 宾利车=宾利厂.create(); 劳斯莱斯车=劳斯莱斯厂.create(); 迈巴赫车=迈巴赫厂.create(); ……
现在我们可以找一下厂家的共性,比如按国家,或者按价格,或者按能源
这样我们可以构造如下几种分法:
方法一:按国家
GermanFactory: 生产德国车
AmericanFactory: 生产美国车
ItalianFactory: 生产意大利车
方法二:按能源
OilFactory:生产汽油车
ElecFactory:生产电车
还有更多的分法,这样就将工厂再次抽象了
以后太太问我买什么车,只需要说个大概,
买新能源吧,买德国车吧,就能概括了
这种模式下,工厂类就可以限制在很少的范围下
以解决工厂类太多的情况。
其实很多设计模式都是来自生活,
通过搭配一些常见的生活案例就很容易理解。
工厂模式是比较入门的设计模式
对产品的构造参数,要求越少越好,最好无参数构造。
如果参数较多,就需要选择其他模式了,
我们在后续的系列中会给大家一一介绍。