浅谈设计模式 - 简单工厂模式(二) 前言: 对于学习设计模式,我推荐:《HeadFirst设计模式》 和《大话设计模式》 。另外设计模式推崇学以致用。看到任何知识之前,先想想我能学到什么,带着问题去看待问题,将会使得学习事半功倍,否则就是事倍功半。
不要过分拘泥于设计模式的类和形式,只要记住一点:将变与不变抽离的过程 就是设计模式
为什么设计模式学了就忘?
不敢尝试(当然也不要过度自信,看到代码就想用设计模式)
过于关注设计模式的结构,忘记了业务本身。
很多时候我们拘泥于形式和设计,纠结于用什么设计模式,其实设计模式本身就是继承,封装,多态的三者结合,很多时候只要可以解决问题,就不需要用过多的技巧
学习之前神志不清
大致就是邯郸学步,看到别人学设计模式,自己也跑去学设计模式
很多人学了设计模式之后隔了一段时间之后,发现自己不使用,忘得一干二净(我也是)。所以希望这些设计模式更多的是结合一些比较实际一点的需求(尽量),毕竟设计模式学了就是拿来用的,如果不用不如不要学,去看点动漫电视剧啥的放松一下。
什么是简单工厂模式? 现实理解: 简单工厂从字面意思来看,就如同我们平常的工厂一般,我们想要重复的生产某样物品,就需要建设工厂不断生产。我们需要给工厂下达指令,比如生产一批“苹果”,生成一批“香蕉”。我们只需要只会工厂生产,而不需要去理会内部的细节。
工厂模式: 简单工厂模式,是一种创建型设计模式 ,定义简单工厂,负责为具体操作对象生成需要的操作类,把创建对象和使用对象进行分开,使用对象方只需要传入调用简单工厂的工厂方法进行创建对象。
简单工厂的特点:
返回抽象的接口或者父类,由工厂管理子类创建过程
让创建过程变成一个黑盒
封闭创建过程,客户端只需要关注结果。
工厂模式优缺点: 优点:
使用创建工厂的方法,我们实现了获取具体对象和生产对象的解耦,由生产对象的工厂通过我们传入的参数生产对应的对象,调用方只需要传递需要生产的对象来实现具体的效果。
解耦了创建和被创建的过程。
根据不同的逻辑判断生成不同的具体对象。
缺点:
每增加一个工厂对象具体的实现类,就需要增加if/else
不利于维护
大量的子类会造成工厂类的迅速膨胀和臃肿
简单工厂的方法一般处理简单的业务逻辑,如果创建逻辑复杂不建议使用。
实际案例: 下面的案例是个人理解,可能存在偏差,不同人理解有差异。欢迎给出建议。
场景模拟: 我们以经典的任天堂游戏坦克大战为例,在进入游戏的关卡的时候,会出现我方的坦克和敌人的坦克,我方坦克和地方坦克不仅形状不同,而且很脆,但是敌人的坦克根据颜色需要打好几枪才会毁灭,那么如果用代码来模拟是什么样的呢?
不使用设计模式: 根据场景,我设计了如下的图表
按照正常方式,我们的定义了一个坦克的父类,接着我们需要定义三个子类来继承父类坦克,以实现自己的扩展。当我们需要创建坦克的时候,我们需要纠结所有的细节,比如到底是创建我方坦克还是敌人坦克,我方的坦克位置,敌人的坦克位置,我方的血量,敌人的血量,等等,从创建坦克到销毁坦克的所有过程,都由我们进行参与。
1 2 3 4 5 + 坦克抽象类 Tank.java + 老鼠坦克 MouseTank.java + 我方坦克 MyTank.java + 巨型坦克 BigTank.java + 测试类 Main.java
具体的代码实现如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 public abstract class Tank { protected int hp; protected List<Object> bullet; abstract void move () ; abstract void attack () ; abstract void stop () ; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 public class MouseTank extends Tank implements Runnable { public void display () { System.err.println("长得尖尖的,很像老鼠" ); } public MouseTank () { hp = 1 ; new Thread(this ).start(); bullet = new ArrayList<>(); bullet.add(new Object()); bullet.add(new Object()); bullet.add(new Object()); bullet.add(new Object()); bullet.add(new Object()); } @Override void move () { System.err.println("老鼠坦克移动" ); } @Override void attack () { System.err.println("老鼠坦克开枪" ); if (bullet.size() <= 0 ) { System.err.println("老鼠坦克没有子弹了" ); return ; } bullet.remove(bullet.get(bullet.size() - 1 )); } @Override void stop () { System.err.println("停止" ); } @Override public void run () { while (true ) { move(); attack(); attack(); try { Thread.sleep(1000 ); } catch (InterruptedException e) { e.printStackTrace(); } if (new Random(100 ).nextInt() % 2 == 0 ) { stop(); } } } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 public class BigTank extends Tank implements Runnable { public void display () { System.err.println("巨型坦克" ); } public BigTank () { hp = 5 ; new Thread(this ).start(); bullet = new ArrayList<>(); bullet.add(new Object()); bullet.add(new Object()); bullet.add(new Object()); } @Override void move () { System.err.println("巨型坦克移动" ); } @Override void attack () { System.err.println("巨型坦克开枪" ); if (bullet.size() <= 0 ) { System.err.println("巨型坦克没有子弹了" ); return ; } bullet.remove(bullet.get(bullet.size() - 1 )); } @Override void stop () { System.err.println("巨型坦克停止" ); } @Override public void run () { while (true ) { move(); attack(); try { Thread.sleep(2000 ); } catch (InterruptedException e) { e.printStackTrace(); } if (new Random(1000 ).nextInt() % 2 == 0 ) { stop(); } } } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 public class MyTank extends Tank { public MyTank () { hp = 1 ; bullet = new ArrayList<>(); bullet.add(new Object()); bullet.add(new Object()); bullet.add(new Object()); } @Override void move () { System.err.println("移动" ); } @Override void attack () { System.err.println("攻击地方坦克" ); if (bullet.size() == 0 ){ System.err.println("没有子弹了" ); return ; } bullet.remove(bullet.get(bullet.size() -1 )); } @Override void stop () { System.err.println("停止" ); } }
建议使用单元测试,这里图方便没有用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 public static void main (String[] args) { Tank bigTank1 = new BigTank(); Tank bigTank2 = new BigTank(); Tank bigTank3 = new BigTank(); Tank bigTank4 = new BigTank(); Tank mouseTank1 = new MouseTank(); Tank mouseTank2 = new MouseTank(); Tank mouseTank3 = new MouseTank(); Tank mouseTank4 = new MouseTank(); Tank myTank1 = new MyTank(); Tank myTank2 = new MyTank(); }
上面的代码有什么问题: 咋看一下好像没啥问题呀,我们既有定义抽象的父类,同时又定义了子类去继承,在需要的时候我们直接new就是了。
其实问题就出在new这一步,可以说我们写烂代码的第一步就是new 。因为我们掉进了“细节”的陷阱,下面我们分析一下我们的代码有什么问题:
我要加一个坦克,虽然可以继承,但是如果要加入到战场,需要我们记住新坦克,并且new出来
我想要老鼠坦克,却不小心new了一个普通地方坦克,当代码较少的时候可能没啥问题,但是如果代码多了,我们要花大量时间查找
我们的测试类掌控了一切,他的活太重了,不仅需要new,还需要new之后的所有操作。
用简单工厂模式改进: 既然知道了有什么问题,那么我们可以加入一个简单工厂类来管理坦克的创建过程
增加工厂类 TankFactory.java
用工厂来管理具体的坦克创建过程:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 public class TankFactory { public Tank createTank (String check) { Tank tank = null ; if (Objects.equals(check, "my" )){ tank = new MyTank(); }else if (Objects.equals(check, "mouse" )){ tank = new MouseTank(); }else if (Objects.equals(check, "big" )){ tank = new BigTank(); }else { throw new UnsupportedOperationException("当前坦克不支持生产" ); } return tank; } }
我们重写单元测试:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 public class Main { public static void main (String[] args) { TankFactory tankFactory = new TankFactory(); Tank my = tankFactory.createTank("my" ); Tank mouse = tankFactory.createTank("mouse" ); Tank big = tankFactory.createTank("big" ); Tank mybig = tankFactory.createTank("mybig" ); } }
改进之后有什么变化:
首先,我们把创建的具体过程交给了工厂,不在需要关注创建的细节
如果需要修改创建的过程,不需要改客户端代码,只需要修改工厂的代码
扩展同样只需要继承工厂的生产抽象对象即可。
简单工厂模式在spring中的体现: @Bean
注解让我们可以在被Spring管理的对象定义Bean的创建过程,而此时这个类就类似一个工厂,对象的创建细节被封装在具体的方法之中,同时这种方式也是一种单例设计模式
,我们定义的@Bean
是单例的,在需要的地方可以使用Spring
的注解进行注入而不需要自己new对象。
总结: 案例可能不是十分贴切,因为仅仅只有一个new方法是不需要用工厂模式的,但是这里是个人思考之后觉得最能够联想到的情况,就使用了坦克这个例子作为文章的主体。