浅谈设计模式 - 简单工厂模式(二) 前言:     对于学习设计模式,我推荐:《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方法是不需要用工厂模式的,但是这里是个人思考之后觉得最能够联想到的情况,就使用了坦克这个例子作为文章的主体。