设计模式总结
今天心血来潮想搞一下之前也一直没有搞清楚的设计模式来看看。这块知识点说实话一直是我一个知识盲区,之前一直也没有搞清楚,在今天我详细总结了五个常用并且好用的设计模式,相信只要看过这篇文章,一定会有所触类旁通,其他设计模式理解起来也不成问题了。
工厂设计模式
首先要明确,工厂模式分为三种:
简单工厂设计模式
工厂方法
抽象方法
一:简单工厂模式
首先来简单了解一下简单工厂模式是什么吧~
介绍: 简单工厂模式是属于创建型模式,又叫做静态工厂方法(Static Factory Method)模式,但不属于23种GOF设计模式之一。简单工厂模式是由一个工厂对象决定创建出哪一种产品类的实例。简单工厂模式是工厂模式家族中最简单实用的模式,可以理解为是不同工厂模式的一个特殊实现。
~~好了好了,牛皮牛皮~~ 说实话看概念我是真的一点都没看懂,什么垃圾解释,下面让我用人话重新剖析一下这句话在说什么吧~
首先我想举个例子: 好,现在我们来到了一片果园,这里漫山遍野都是果树,里面有葡萄,苹果等等等等。这里提一嘴,为什么我们需要工厂模式,首先,我们不能说想用一个小黄车,我们当场去创建这个小黄车,零件什么的都由我们自己去创造,我们需要的是一个工厂帮我们代工,我们想用的时候推出来一辆就可以了,工厂模式就是可以帮我们隐藏具体的建造细节。好了差不多就这样,我们将话题再转向这片果园~
好的,有些同学要说了:“我知道!你要抽象出来一个水果类”。
哎呀真聪明,都学会抢答了,是的,我们这里抽象一个水果类出来:
public abstract class Fruit {
abstract void grow();
abstract void harvest();
abstract void plant();
}
我觉得大家都是成年人了,不会的单词百度一下也应该知道什么意思了,这里还是帮你们标注一下吧(harvest:收获)
这里的知识很简单,用一个抽象类,把水果共有的特征抽象出来,有些同学觉得抽象的东西太抽象了,不好理解,这里的抽象,你可以理解成 抽出像的部分。比方说这里的成长,种植,收获,都是水果必经之路。
好,我们可以创建一个苹果类和葡萄类来继承这个抽象类,重写这些方法,让水果更加丰满
public class Apple extends Fruit{
@Override
void grow() {
System.out.println("苹果成长");
}
@Override
void harvest() {
System.out.println("苹果收获");
}
@Override
void plant() {
System.out.println("苹果种植");
}
}
然后这里是葡萄类:
public class Grape extends Fruit{
@Override
void grow() {
System.out.println("葡萄成长");
}
@Override
void harvest() {
System.out.println("葡萄收获");
}
@Override
void plant() {
System.out.println("葡萄种植");
}
}
好的,有些同学就要问了,工厂在哪里,别急,看下面的代码:
public class FruitFactory {
public static Fruit factory(String fruitName){
if ("apple".equals(fruitName)){
System.out.println("包装苹果");
return new Apple();
}else if ("grape".equals(fruitName)){
System.out.println("包装葡萄");
return new Grape();
}else {
return null;
}
}
}
这里我得解释一下这段代码:这是一个静态的工厂,传进来一个水果名,我们这里做一下判断,如果是苹果,我们让他包装苹果,并且返回一个新的苹果实例。葡萄也是一样的方法,直接 else if 判断一下就行(极大的加强了程序的拓展能力)。这里我们可以看到,我们丰富了建造过程,给他包装了一下。 接下来让我们看看客户端怎么操作(也就是主函数)
public class Client {
public static void main(String[] args) {
try {
Fruit grape = FruitFactory.factory("grape");
grape.plant();
}catch (Exception e){
e.printStackTrace();
}
}
}
这里我们明显的看到:我现在要一个水果,但是我们没有new这个水果,而是把工厂拿过来,直接调用方法,传参数。
好了,讲完了,是不是很简单,但是我们可以设想一下:
是不是在某些系统中,有控制台会询问你执行什么操作的话,反反复复总是那几句,所以你有没有想过,这些话不是写死在程序里面的,这个菜单可能是在外边一个文件里面,用的时候加载进来,当你需要用的时候,就像引用常量一样,把它映射过去,这样将来调用欢迎语句的时候,欢迎语句可能会改变,但是引用没有改变。这里我们就可以使用简单工厂模式的思想来考虑。
下面我把这个简单工厂模式的UML图放在下边了: 
二:工厂方法
接下来要讲的工厂方法其实就是一个升级,接下来我还是举一个例子来理解工厂方法,以及为什么要使用工厂方法。

这里我们先看一张UML图,请认真看图,这里是关键,这个例子是有关苹果公司生产的产品的,众所周知,苹果公司比较出名的有iPad以及iPhone(当然还有其他的我就不举例了),我们也知道,虽然这两个产品感觉差不多,但是实际上是两个不同的产品,不管是屏幕,大小,功能都不太一样,然后你让我用同一个工厂去代工又有点别扭,所以这里我把工厂类也抽象了,就是说,你要iPad,我就给你调用iPadFactory(iPad工厂),你用iPhone,我就给你调用iPhoneFactory(iPhone工厂)。懂了吗,等于就是更具体了,更详细了。接下来我们看一下代码你就更了解了。
首当其冲的当然是创新的地方,也就是抽象的工厂类(我这里定义的是接口,一个意思),这里工厂就比较简单了,主要功能就是生产产品。
public interface Factory {
public Product factory();
}
然后就是两个加工厂了,实现Factory接口,重写生产方法(大体都差不多是相似的):
public class IpadFactory implements Factory{
@Override
public Product factory() {
return new Ipad();
}
}
public class IphoneFactory implements Factory{
@Override
public Product factory() {
return new Iphone();
}
}
还有更简单的,我定义了一个产品接口:
public interface Product {
}
是不是很简单,里面啥也没有hhh,但是这样可以提高你的一个代码复用以及拓展的能力。 接下来就是我们的两个产品了,一个是iPad,另一个是iPhone:
public class Ipad implements Product{
public Ipad(){
System.out.println("Ipad");
}
}
public class Iphone implements Product{
public Iphone(){
System.out.println("Iphone");
}
}
好了,产品定义好了,工厂也定义好了,是不是该看看主函数什么样了~ 主函数:
public class Client {
public static void main(String[] args) {
Factory c1,c2;
Product p1,p2;
c1 = new IpadFactory();
p1 = c1.factory();
c2 = new IphoneFactory();
p2 = c2.factory();
}
}
这我都不用解释什么吧,都很简单,c1,c2两个产品,p1,p2两个工厂,最后各用各的工厂,代码就很明了清晰。工厂方法我就先简单介绍到这里。
三:抽象工厂
有些同学又要问了,这又是什么**玩意(自动消音),别急,听完我的介绍你会豁然开朗。
这里我还是举一个例子供大家理解,这里给大家也不说苹果了,说说男人的梦想——奔驰汽车吧,众所周知,汽车也分好多品种,比方说等级不同:A系奔驰、B系奔驰。还有产地不同:中国奔驰、德国奔驰。这里产地不同,其实可以理解成完全两个产品,比方说,中国奔驰A系,和德国奔驰A系就不一样,比方说气囊数量以及位置啊,车体钢铁硬度啊什么的都不一样(~~总感觉越解释越麻烦~~) 反正你们就当是两个商品就好了。
重点在这里,我的根本意思就是说,这里情况更加复杂了,不仅工厂抽象了,这里产品也得抽象一下,你们继续看我的代码就能理解了:
首先给大家看一下这个例子的类有哪些:

这里可以清晰的看见:
有抽象的奔驰A和B轿车(BenzA 和 BenzB)
有中国奔A和中国奔B(BenzACN 和 BenzBCN)
有德国奔A和德国奔B(BenzAGermany 和 BenzBGermany)
有奔驰工厂(MercedesFactory)
有中国奔驰工厂(MercedesFactoryCN)
有德国奔驰工厂(MercedesFactoryGermany)
还有主函数(Client)
接下来就是详细代码,其实你只要理解了我什么意思就行,我的意思就是想说,这里的产品也是抽象出来的,工厂方法比简单工厂模式更抽象,抽象工厂又比工厂方法抽象,一级比一级抽象,一级比一级更高级,代码灵活度越来越高,拓展能力越来越强,代码能复用的可能性越大。
接下来就是代码,我们先看抽象的车辆制造工厂这里:
public interface MercedesFactory {
public BenzA creatBenzA();
public BenzB creatBenzB();
}
然后是详细的中国工厂和德国工厂:
public class MercedesFactoryCN implements MercedesFactory{
@Override
public BenzA creatBenzA() {
System.out.println("中国生产一辆奔驰A级轿车");
return new BenzACN();
}
@Override
public BenzB creatBenzB() {
System.out.println("中国生产一辆奔驰B级轿车");
return new BenzBCN();
}
}
public class MercedesFactoryGermany implements MercedesFactory{
@Override
public BenzA creatBenzA() {
System.out.println("德国生产一辆奔驰A级轿车");
return new BenzAGermany();
}
@Override
public BenzB creatBenzB() {
System.out.println("德国生产一辆奔驰B级轿车");
return new BenzBGermany();
}
}
工厂就分这三种,让我们看看产品类:
public abstract class BenzA {
}
public abstract class BenzB {
}
两种车我就简单表示一下,大家也可以加个构造方法让车辆更加丰满。 然后看看继承产品类的中国的两款车吧:
public class BenzACN extends BenzA{
public BenzACN(){
System.out.println("中国的奔驰A");
}
}
public class BenzBCN extends BenzB{
public BenzBCN(){
System.out.println("中国的奔驰B");
}
}
然后再看看德国的两款车:
public class BenzAGermany extends BenzA{
public BenzAGermany(){
System.out.println("德国的奔驰A");
}
}
public class BenzBGermany extends BenzB{
public BenzBGermany(){
System.out.println("德国的奔驰B");
}
}
就是这么简单,我们可以通过这种方法,让代码更加整洁,所以就剩下主函数没有看了:
public class Client {
public static void main(String[] args) {
MercedesFactory factory_germany = new MercedesFactoryGermany();
BenzA a2 = factory_germany.creatBenzA();
}
}
这里主函数只要new一个工厂就可以生产轿车啦!完美!是不是很简洁!这里工厂模式就讲完啦,感觉怎么样,懂了吗我的宝贝嘻嘻嘻
好了,其实设计模式是一种思维,不是说强迫你使用这些设计模式,而是能用到就用,用不到也不所谓,只是当你有一天觉得自己的代码臃肿不堪的时候,你可以试试设计模式,会让你的代码水平提升到一个新的水平。
单例设计模式(Singleton)
工厂模式说完,这里我再说一下单例模式,这里只是简单的总结一下,说一下概念什么的,具体的单例模式可以看我之后的blog(~~应该会写的吧,我也不确定哈哈哈~~ )
好,不开玩笑了,既然大家都是学java的,必然听过这个单例模式。很多公司都会让你手撕一段单例模式,可见单例模式的重要了,我以前面试的时候,这个东西都是写烂的玩意儿了,但是有一次一个公司问我,你说说为什么要使用单例模式,说实话我直接懵逼了,确实之前一直没有考虑过这个事情。相信很多初学者也很好奇为什么使用,这里我把这个问题摆到最前面解决一下。
什么是单例模式
在此之前我先介绍一下单例模式吧:单例模式是确保某个类只有一个实例,而且自行实例化并向整个系统提供这个实例。
※为啥要用单例模式※(这里借鉴百度百科)
对于系统中的某些类来说,只有一个实例很重要的,例如:
一个系统中可以存在多个打印任务,但是只能有一个正在工作的任务
一个系统只能有一个窗口管理器或文件系统,如在Windows中就只能打开一个任务管理器。如果不使用机制对窗口对象进行唯一化,将弹出多个窗口,如果这些窗口显示的内容完全一致,则是重复对象,浪费内存资源
如果这些窗口显示的内容不一致,则意味着在某一瞬间系统有多个状态,与实际不符,也会给用户带来误解,不知道哪一个才是真实的状态(系统需要保证数据正确性),因此有时确保系统中某个对象的唯一性即一个类只能有一个实例非常重要。
nice!懂了吧!单例模式是多么的重要!
然后大家要知道,单例模式有两种创建方法:
饿汉式
懒汉式
一:单例模式 之 饿汉式
“饿汉”,看名字你就应该知道这玩意是有多饥渴,就特别着急,给他一碗饭,一上来就立马要吃,这就是饿汉式的精髓!
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton(){}
public static Singleton getInstance(){return instance;}
}
这里我感觉得解释一下这段代码了:首先定义一个类叫做Singleton(单个的),然后正如饿汉式所言,饿的不行了,赶紧一上来就new了一个静态对象,名字叫instance。然后来了一个私有的构造函数,最后有一个获取实例化对象的函数getInstance()。这就是单例模式。 我们可以新建一个TestCase测试方法,新建两个对象来比较一下:
Singleton c1 = Singleton.getInstance();
Singleton c2 = Singleton.getInstance();
System.out.println(c1==c2);
这段代码是为了比较一下两个对象是不是一个对象,单例模式是不是人如其名,检测是不是真的只创建了一个对象。运行出来的结果我就不截图了,答案是true,这意味着什么?这意味着这两个对象的hashcode是一样的,简单来说就是一个对象!神奇了吧~
二:单例模式 之 懒汉式
你看看,开发人员和翻译人员多牛X,懒汉式也是人如其名,他快懒死了,他的心路历程是这样的:“不就是个实例化对象吗!瞅你们着急的,你们之前有没有实例化对象啊?让我看一看,奥!没有实例化啊,来来来,大爷给你实例化一个(或者:啥?你已经实例化了?那你叫我干啥,滚犊子!)” 这段话就已经凸显出了懒汉式和饿汉式的区别了,饿死鬼是一上来就要实例化,生怕忘了。但是这懒汉式的死宅男,只有在需要实例化的时候才慢吞吞的实例化,不仅这样,他还要加一个判断语句,判断一下之前有没有实例化过。下面是懒汉式的代码:
public class Singleton {
private static Singleton instance = null;
private Singleton(){}
public static Singleton getInstance(){
if (instance==null){
instance = new Singleton();
}
return instance;
}
}
可以看出,两种方式差别不大,但是懒汉式容易出问题,这里不赘述了,之后我会详细剖析这个问题的,这个问题主要还是表现在多线程那一块,加一个锁就可以解决问题。
装饰者设计模式(Decorator)
装饰者设计模式你也可以称之为装饰器设计模式,反正你想叫啥叫啥,我这里习惯叫装饰者了。
有同学估计已经学的累了,心想,这个sb博主,怎么唠唠叨叨的还没写完呢,大家稍安勿躁,我都是讲解的精品设计模式,大家稍微有点耐心,学习不是一蹴而就的事情,大家共勉!
这里的装饰者模式就非常有意思了!这里我还是举个例子说明这个设计模式:
这样,大家都知道孙悟空吧,会七十二般变化,还能翻个跟头就十万八千里,假如今天孙悟空想变成条鱼,你说这条鱼能飞行十万八千里吗?当然可以,这里鱼只是外壳,实际上里面还是孙悟空,对吧,那你说假如让你用代码描述这件事情,你怎么说?鱼继承了孙悟空?明显不行,因为两个东西是两个物种,但是你说鱼能不能十万八千里,好像也还行,是不是这个问题就比较贱,比较棘手,但是这里我们使用装饰者模式就可以完美的解决这个问题。 咱们慢慢梳理一下这个问题,首先我问大伙,孙悟空能十万八千里,变成鱼也能吧,这里我们就提取一个共同点,就是这个Move类,没毛病吧老铁:
public interface Move {
void fly();
}
然后主角登场了:
public class MonkeyKing implements Move{
@Override
public void fly() {
System.out.println("孙悟空飞了十万八千里");
}
}
主角孙悟空实现了并重写了Move接口,那么问题来了,鱼怎么写?继承孙悟空?绝对不行,这里就用到了装饰者模式,首先我们来看看代码怎么写:
public class Decorator implements Move{
private MonkeyKing mk;
public Decorator(MonkeyKing mk){
this.mk = mk;
}
@Override
public void fly() {
System.out.println("孙悟空变成鱼,飞了十万八千里");
}
}
这个就是装饰者模式,实现重写了Move接口,相当于一个壳子,里面有孙悟空飞十万八千里的功能,接下来我详细解释一下这个代码:首先大家明白,这个只是一个壳子,外壳是一只鱼,内核实际上是孙悟空,所以我们需要在这个类里面实例化出来这个孙悟空,并且写一个孙悟空的有参构造函数,到时候传进来是只鱼,你也是孙悟空,传进来是个屎壳郎,你也是孙悟空,你也能飞。
这里相当于把装饰者模式(就是一个特殊类)当成一个桥,把孙悟空接进来,放到这个伪装的壳里面去
然后我们看看鱼这个类怎么写:
public class Fish extends Decorator{
public Fish(MonkeyKing mk){
super(mk);
}
}
鱼继承了装饰者,书写了一个构造函数,super调用父类方法,实现能飞的愿望。 大体就算完成了,我们主函数调用一下看一眼:
public class TestCase {
public static void main(String[] args) {
MonkeyKing mk = new MonkeyKing();
mk.fly();
Fish fish = new Fish(mk);
fish.fly();
}
}
主函数先实例化了一个孙悟空,一运行,能飞。 然后实例化一个鱼,把孙悟空这个实例传进去,一运行,神了!也能飞,这也许是最牛X的鱼了吧。 好了,装饰者模式大致也就是这样了。大家听懂了吗~
门面设计模式(facade)
这个贼鸡儿简单,快看两眼,这是我介绍的最后两个设计模式了!坚持,成功一定是属于你的! 这个设计模式叫个啥?门面设计模式??这又是什么玩意(消音ing),好了好了我举个例子你就知道了:这里咱们来到了一家奢侈品专柜,里面都是价值连城的宝贝,这么好的宝贝肯定不能让贼盯上啊,怎么办呢?懂了!来个安防系统吧!先不管三七二十一丁玲桄榔安装上摄像头,安装上报警器,安装上传感器:
public class Camera {
public void on(){
System.out.println("摄像机已经启动!");
}
public void off(){}
}
public class Alarm {
public void active(){
System.out.println("报警系统已经启动!");
}
}
public class Sensor {
public void active(){
System.out.println("传感器已经启动!");
}
}
这里大家可以自由发挥,我只写了开启的方法,关闭的方法我就没写,不过只开不关是有点奇葩啊哈哈哈哈
我现在是这样想的,我总不能一个个开吧,太麻烦了吧,我需要一个按钮,一键启动,这样多方便了,于是说干就干,想个方法整合一下,哎呦喂!这不就是门面设计模式嘛!看看我怎么写的:
public class SecurityFacade {
private Alarm alarm;
private Sensor sensor;
private Camera camera;
public SecurityFacade(){
alarm = new Alarm();
sensor = new Sensor();
camera = new Camera();
}
public void init(){ //总调度
alarm.active();
sensor.active();
camera.on();
}
}
是不是以为我要全写到主函数里哈哈哈哈哈哈,好了不逗你了,看看这个门面设计模式,首先映入眼帘的就是构造函数的实例化过程,当你实例化成功之后,其中包含的摄像头,传感器,报警器也就同时开始了他们的生命周期。然后写一个方法init,作为一个总调度方法,开启整个系统。
这样你可以想到,在主函数里面,就可以直接调用这个方法就可以了。很简洁很奈斯。所以让我们看一眼主函数是个什么样:
public class Client {
public static void main(String[] args) {
SecurityFacade facade = new SecurityFacade();
facade.init();
}
}
不错吧?这就是门面设计模式!讲完啦!快不快!还有最后一个!冲鸭!!!!
适配器设计模式(adapter)
首先问一下大家,知道什么是适配器吧?就你那个和砖头一样的笔记本的电源就是一个适配器,你也可以想象成变压器。适配器模式也是这种意思。
首先!!!还是举个例子!我们就说这个笔记本电源吧,大家知道日本的电压是110V吧,和中国的220V是不一样的,这并不代表我们的电脑带到日本就不能使用了,通过适配器我们依旧可以继续使用,所以说就这样一个例子来说,无非是很切题了!
首先我们要知适配器模式包三个角色:
Target(目标抽象类):目标抽象类定义客户所需的接口,可以是一个抽象类或接口,也可以是具体类。
Adapter(适配器类):它可以调用另一个接口,作为一个转换器,对Adaptee和Target进行适配。它是适配器模式的核心。
Adaptee(适配者类):适配者即被适配的角色,它定义了一个已经存在的接口,这个接口需要适配,适配者类包好了客户希望的业务方法。
是不是觉得一下子生涩了很多!~~哈哈哈哈哈哈都是我复制的!~~ 好了好了,让我用白话文来说一下这几个概念。
首先,我们可以用电脑打游戏,打代码,但是前提是得有电源才行,但是就像之前提出的,这个电源往往还不太一样,有110V的,有220V的,这相当于一个变量,但是我们要求不管这个电源怎么变化,传输给电脑的必须是一样的电流才行,首先就像刚才~~复制的~~ 所说,我们需要一个Target,这里我们的Target就是供电和打代码:
public interface Target {
void power(); //供电
void development(); //写代码
}
那么很明显的可以看到,这是一个接口,需要有人能实现它,那么谁实现它呢?我们接着往下看:
public class Adapter implements Target{
private Adaptee adaptee; //适配器
public Adapter(){}
public Adapter(Adaptee adaptee) {
super();
this.adaptee = adaptee;
}
@Override
public void power() {
adaptee.power();
}
@Override
public void development() {
System.out.println("开始编程");
}
}
这里一下子就很烦,大段大段的代码我也不想看,这里我给大家解释一下:这里出现了adaptee,你可以把这个当成真正的适配器,这里面有客户希望的业务方法,在我们这里就是要求供电平稳,这里我们通过构造函数将适配器接入,然后用接进来的适配器进行供电然后编程:
public interface Adaptee {
void power();
}
这是一个接口,提供一个规范。然后接下来就是两种电压的情况:
public class Adaptee110 implements Adaptee {
@Override
public void power() {
System.out.println("提供110V的电压");
}
}
public class Adaptee220 implements Adaptee {
@Override
public void power() {
System.out.println("提供220V的电压");
}
}
这两种电压都是实现Adaptee并重写供电方法。 然后看看主函数就大彻大悟了:
public class TestCase {
public static void main(String[] args) {
Adaptee a1 = new Adaptee110();
Adaptee a2 = new Adaptee220();
Target target = new Adapter(a1);
target.power();
target.development();
}
}
现在虽然实例化了两个电源,但是明显可以看到,我们用的是a1这个电源,也就是110V的电源:
Target target = new Adapter(a1);
但是很明显,无论怎么更换电源,我依旧可以编程,通过适配器转化成统一的标准,这就是适配器模式,在面试里面也是经常会考的。
好了,五种设计模式介绍完了,大家感觉怎么样?喜欢我这样的解释方式吗?博主码字真的不容易,做电脑前码了6个多小时才完成这篇文章。希望喜欢的小伙伴可以关注我一下!!不对!我要无耻的求一波关注!!爱你!!!有疑问的地方大家也可以联系我(微信:652355283),或者有错误的地方还请大家留言告诉我,我将不胜感激!
呼卓宇 2019年2月11日夜




