对于学习设计模式,我推荐:《HeadFirst设计模式》和《大话设计模式》。另外设计模式推崇学以致用。看到任何知识之前,先想想我能学到什么,带着问题去看待问题,将会使得学习事半功倍,否则就是事倍功半。
不要过分拘泥于设计模式的类和形式,只要记住一点:将变与不变抽离的过程就是设计模式
很多人学了设计模式之后隔了一段时间之后,发现自己不使用,忘得一干二净(我也是)。所以希望这些设计模式更多的是结合一些比较实际一点的需求(尽量),毕竟设计模式学了就是拿来用的,如果不用不如不要学,去看点动漫电视剧啥的放松一下。
简单工厂从字面意思来看,就如同我们平常的工厂一般,我们想要重复的生产某样物品,就需要建设工厂不断生产。我们需要给工厂下达指令,比如生产一批“苹果”,生成一批“香蕉”。我们只需要只会工厂生产,而不需要去理会内部的细节。
简单工厂模式,是一种创建型设计模式,定义简单工厂,负责为具体操作对象生成需要的操作类,把创建对象和使用对象进行分开,使用对象方只需要传入调用简单工厂的工厂方法进行创建对象。
优点:
缺点:
if/else
不利于维护下面的案例是个人理解,可能存在偏差,不同人理解有差异。欢迎给出建议。
我们以经典的任天堂游戏坦克大战为例,在进入游戏的关卡的时候,会出现我方的坦克和敌人的坦克,我方坦克和地方坦克不仅形状不同,而且很脆,但是敌人的坦克根据颜色需要打好几枪才会毁灭,那么如果用代码来模拟是什么样的呢?
根据场景,我设计了如下的图表
按照正常方式,我们的定义了一个坦克的父类,接着我们需要定义三个子类来继承父类坦克,以实现自己的扩展。当我们需要创建坦克的时候,我们需要纠结所有的细节,比如到底是创建我方坦克还是敌人坦克,我方的坦克位置,敌人的坦克位置,我方的血量,敌方的血量,等等,从创建坦克到销毁坦克的所有过程,都由我们进行参与。
+ 坦克抽象类 Tank.java
+ 老鼠坦克 MouseTank.java
+ 我方坦克 MyTank.java
+ 巨型坦克 BigTank.java
+ 测试类 Main.java
具体的代码实现如下:
/**
* 坦克的抽象类,定义坦克的行为
*
* @author zxd
* @version 1.0
* @date 2021/1/25 0:14
*/
public abstract class Tank {
/**
* 坦克hp
*/
protected int hp;
/**
* 坦克子弹
*/
protected List<Object> bullet;
/**
* 移动的方法s
*/
abstract void move();
/**
* 攻击
*/
abstract void attack();
/**
* 停止
*/
abstract void stop();
}
/**
* 老鼠坦克
*
* @author zxd
* @version 1.0
* @date 2021/1/25 22:02
*/
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();
}
}
}
}
/**
* 巨型坦克
*
* @author zxd
* @version 1.0
* @date 2021/1/25 22:14
*/
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();
}
}
}
}
/**
* 我方坦克
*
* @author zxd
* @version 1.0
* @date 2021/1/25 21:58
*/
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("停止");
}
}
建议使用单元测试,这里图方便没有用
/**
* 这种频繁的new,让我们逐渐变成面向过程编程。。。。
* @param args
*/
public static void main(String[] args) {
// 虽然我们可以自己生产坦克,但是我们每次都需要自己手动去生产对应的坦克。这种频繁的new 操作
Tank bigTank1 = new BigTank();
Tank bigTank2 = new BigTank();
Tank bigTank3 = new BigTank();
Tank bigTank4 = new BigTank();
// 有多少个对象,就有多少个new
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。因为我们掉进了“细节”的陷阱,下面我们分析一下我们的代码有什么问题:
既然知道了有什么问题,那么我们可以加入一个简单工厂类来管理坦克的创建过程
增加工厂类 TankFactory.java
用工厂来管理具体的坦克创建过程:
/**
* 坦克工厂,专门负责生产坦克
*
* @author zxd
* @version 1.0
* @date 2021/1/25 22:27
*/
public class TankFactory {
/**
* 创建坦克
* @return
*/
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;
}
}
我们重写单元测试:
/**
* 单元测试
*
* @author zxd
* @version 1.0
* @date 2021/1/25 22:15
*/
public class Main {
/**
* 我们将生产坦克的过程全部交给了工厂来处理
* 可能还是奇怪,这和刚才没有什么区别呀?
* 我们来看下区别:
* 1. 创建的过程没有了,虽然是一个简单的new,但是new的过程交给了工厂
* 2. 我们后续如果要在坦克加入别的东西,只需要去改工厂类和具体的实现类,不需要该此处代码
* 3. 如果不支持的操作,工厂还可以通知我们这样做不对
* @param args
*/
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");
}/*//
运行结果:
Exception in thread "main" 老鼠坦克移动
巨型坦克移动
老鼠坦克开枪
巨型坦克开枪
老鼠坦克开枪
java.lang.UnsupportedOperationException: 当前坦克不支持生产
at com.headfirst.factory.use.TankFactory.createTank(TankFactory.java:27)
at com.headfirst.factory.use.Main.main(Main.java:33)
*/
}
@Bean
注解让我们可以在被Spring管理的对象定义Bean的创建过程,而此时这个类就类似一个工厂,对象的创建细节被封装在具体的方法之中,同时这种方式也是一种单例设计模式
,我们定义的@Bean
是单例的,在需要的地方可以使用Spring
的注解进行注入而不需要自己new对象。
案例可能不是十分贴切,因为仅仅只有一个new方法是不需要用工厂模式的,但是这里是个人思考之后觉得最能够联想到的情况,就使用了坦克这个例子作为文章的主体。