设计模式之工厂模式,但是宝可梦
前言
工作一年了,业务代码写太多,还是得自驱提升点技术。希望工作一年后写出来的能有更多自己的思考。
正文
工厂模式是一种创建型设计模式,主要的目的还是在创建一个对象时提供更灵活、更易扩展的机制。
简单工厂模式
情景模拟
小智到商店去买精灵球,精灵球的种类繁多。商店店员会在库房中根据小智的要求拿对应的精灵球。在这个过程中,作为买家,小智不需要关注精灵球放在库房的哪一个位置。
/**
* 精灵球可以查看成功率,实现类有普通球和超级球
**/
public interface Ball {
void showDetails();
}
class NormalBall implements Ball{
@Override
public void showDetails() {
System.out.println("This is a normal ball. 15% to catch");
}
}
class SuperBall implements Ball{
@Override
public void showDetails() {
System.out.println("This is a super ball. 25% to catch");
}
}
/**
* 精灵球商店
*/
public class BallShop {
public static Ball getBall(int ballCode){
if (ballCode == 0){
// 去放普通球的地方...
return new NormalBall();
}
if (ballCode == 1){
// 去放超级球的地方...
return new SuperBall();
}
return null;
}
}
对比一下直接new创建类和用工厂模式创建类:
Ball ball1 = new NormalBall();
Ball ball2 = new SuperBall();
Ball normalBall = BallShop.getBall(0);
Ball superBall = BallShop.getBall(1);
有一个很直观的区别:我根本不需要关心new的是什么实现类,我只需要传入对应的参数,告诉工厂我需要什么实现类。更规范一点,可以把0,1等数替换为枚举类,这样作为调用方,我无需关注怎么new的,new的什么类,我只需要相信工厂类会返回给我想要的类。
举例
DateFormat是简单工厂模式的经典运用,获取实例对象时,我只需要在getTimeInstance()
方法传入类提供的枚举类,我无需关注内部实现细节,传入不同参数,后续的具体实现也就不同。
DateFormat timeInstance = DateFormat.getTimeInstance(DateFormat.FULL);
String format1 = timeInstance.format(new Date());
DateFormat timeInstance2 = DateFormat.getTimeInstance(DateFormat.DATE_FIELD);
String format2 = timeInstance2.format(new Date());
System.out.println(format1); // 中国标准时间 上午12:37:33
System.out.println(format2); // 上午12:37
不足
简单工厂模式对于简单场景是很友好的,实现很简单,如果能确保业务不再扩展,简单工厂模式是很好的选择。然而如果业务有扩展,简单工厂模式的弊端就体现出来了。
如果现在我新增了一个新的球种类——大师球。现在商店里的店员都需要知道大师球放在哪个位置,加重了商店店员的逻辑(工厂内创建逻辑)
class MasterBall implements Ball{
@Override
public void showDetails() {
System.out.println("MasterBall!!. 100% to catch");
}
}
对应的工厂类就要做相应的适配:
public class BallShop {
public static Ball getBall(int ballCode){
if (ballCode == 0){
// 去放普通球的地方...
return new NormalBall();
}
if (ballCode == 1){
// 去放超级球的地方...
return new SuperBall();
}
if (ballCode == 2){
// 去放大师球的地方...
return new MasterBall();
}
return null;
}
}
新增一个if分支的同时打破了开闭原则(对扩展开放,对修改封闭),其次,工厂类涵盖了所有的创建逻辑,高内聚。
为了解决业务会有新增的情况,根据面向对象编程的原则,抽象!引入工厂方法模式。
工厂方法模式
场景模拟
由于精灵球的种类不断增多,店员没办法记住每种球的位置(内聚太多逻辑),所以精灵球商店把店面分成了几个区域,每个区域售卖一种球,一个店员负责一个区域,这个店员只需要关注这个区域售卖的球在哪里。作为买家小智,他只需要知道他想买哪种球,然后去对应的分区,他仍然不需要关心球在哪里存放。
于是工厂类修改为:
public interface BallShopNew {
Ball findBall();
}
class NormalBallFactory implements BallShopNew{
@Override
public Ball findBall() {
return new NormalBall();
}
}
class SuperBallFactory implements BallShopNew{
@Override
public Ball findBall() {
return new SuperBall();
}
}
class MasterBallFactory implements BallShopNew{
@Override
public Ball findBall() {
return new MasterBall();
}
}
使用方式:
public class FactoryDemo {
public static void main(String[] args) {
Ball masterBall = new MasterBallFactory().findBall();
Ball normalBall = new NormalBallFactory().findBall();
masterBall.showDetails();
normalBall.showDetails();
}
}
现在如果新推出了一种新的球,只需要开辟新的分区,聘请一个新店员(创建新的实现类),遵循了开闭原则。
不足
如果这个时候,产品维度发生了扩展,商店不止卖球了,还要卖贴纸。当产品变成复数,每个工厂就要进行相应的修改来支持新的产品,或者新增对应数量的新的工厂和实现类。系统中的类会变得极其多。抽象工厂模式用于解决复数产品的场景。
抽象工厂模式
抽象工厂模式首先将产品抽象:
public interface Label {
void showColor();
}
class NormalLabel implements Label{
@Override
public void showColor() {
System.out.println("红白配色");
}
}
class MasterLabel implements Label{
@Override
public void showColor() {
System.out.println("紫色配色");
}
}
再在抽象工厂中引入抽象产品。
public interface BallLabelShop {
Ball findBall();
Label findLabel();
}
class NormalBallLabelShop implements BallLabelShop{
@Override
public Ball findBall() {
return new NormalBall();
}
@Override
public Label findLabel() {
return new NormalLabel();
}
}
class MasterBallLabelShop implements BallLabelShop{
@Override
public Ball findBall() {
return new MasterBall();
}
@Override
public Label findLabel() {
return new MasterLabel();
}
}
使用:
public class FactoryDemo {
public static void main(String[] args) {
MasterBallLabelShop masterBallLabelShop = new MasterBallLabelShop();
Ball ball = masterBallLabelShop.findBall();
Label label = masterBallLabelShop.findLabel();
ball.showDetails();
label.showColor();
NormalBallLabelShop normalBallLabelShop = new NormalBallLabelShop();
Ball ball2 = normalBallLabelShop.findBall();
Label label2 = normalBallLabelShop.findLabel();
ball2.showDetails();
label2.showColor();
}
}
/**
MasterBall!!. 100% to catch
紫色配色
This is a normal ball. 15% to catch
红白配色
**/
这里可能就有读者会问,这不是和工厂方法模式差不多吗? 其实我的理解是抽象方法模式中,工厂的实现类是将以产品组合为单位的。
上面的例子,两个工厂分别是组合了Normal
和Master
两种类型,让同一工厂生产的是能配套的产品组合。回到工厂模式的维度,使用者只需要知道什么工厂会给他想要的产品组合。
总结
作为一个细分了三个种类的设计模式,到底该如何取舍?比起直接new一个对象,使用对应模式的好处到底在哪?
- 简单工厂模式:
- 根据传入的参数决定产出的对象,可以隐藏一些创建的细节
- 适用于需要根据条件创建不同对象的场景。
- 工厂方法模式:
- 将简单工厂转化为抽象工厂的子类,每个子类负责相应对象的创建,将创建逻辑从简单工厂中解耦到各自实现类。
- 适用于要创建的对象会出现扩展的场景;或者是希望将创建逻辑分别封装在具体工厂类的场景。
- 抽象工厂模式:
- 抽象工厂提供接口,用于创建一系列相关或者相互依赖的对象。
- 适用于要创建复数种类的对象;或者是希望将创建逻辑封装在具体工厂类的场景。