参考博客:
https://blog.csdn.net/jason0539/article/details/23020989
https://blog.csdn.net/jason0539/article/details/44976775
https://juejin.im/entry/58f5e080b123db2fa2b3c4c6
https://www.cnblogs.com/java-my-life/archive/2012/03/28/2418836.html
初次接触设计模式,结合看到的博文先记录下初次的理解。有了语言还不够,面对实际的业务,根据不同的需求,需要不同的代码逻辑结构。目的是为了节省程序员的开发维护成本。于是有了23种设计模式,其中工厂模式有3种,分别为简单工厂模式,工厂方法模式,和抽象工厂模式,我现在梳理我理解的工厂模式。
工厂模式背后的思想
在没有设计模式的情况下,我们每创建一个实例就需要new一个对象,但是很多对象的创建可能并没有那么简单,他可能依赖于某些对象的创建,创建对象不再是简单的一步完成。为了代码的重用性,可读性,方便维护。我们想把创建对象的过程封装起来。用户按照一定的顺序调用方法即可。
工厂模式,顾名思义,可以把生产对象的过程,从客户手中脱离,交代给工厂去完成,这个思想叫解耦和。
下面介绍这三类工厂模式。
简单工厂模式
定义:定义一个工厂类,根据传入的参数不同返回不同的实例,被创建的实例具有共同的父类或接口。
适用场景:因为只有一个工厂类,所以工厂类创建的对象不能很多,否则工厂类的业务逻辑就太复杂了,其次由于工厂类封装了对象的创建过程,所以客户端应该不关心对象的创建。总结下使用场景:
- 需要创建的对象较少
- 客户端不关心创建对象的过程
组成:
- 工厂类角色:这是本模式的核心,含有一定的商业逻辑和判断逻辑,用来创建产品
- 抽象产品角色:它一般是具体产品继承的父类或者实现的接口。
-具体产品角色:工厂类所创建的对象就是此角色的实例。在java中由一个具体类实现。
实例:
创建一个可以绘制不同形状的绘图工具,可以绘制圆形,正方形,三角形,每个图形都会有一个draw()方法用于绘图,不看代码先考虑一下如何通过该模式设计完成此功能。
由题可知圆形,正方形,三角形都属于一种图形,并且都具有draw方法,所以首先可以定义一个接口或者抽象类,作为这三个图像的公共父类,并在其中声明一个公共的draw方法。
将我们要创建的图形的类叫做产品类,生成图像的工厂叫做工厂类。下面的可以叫做产品基类。
public interface Shape {
void draw();
}
这里定义成抽象类也是可以的,只不过接口是更高一级的抽象,所以习惯定义成接口,而且接口支持多实现,方便以后扩展。
下面就是编写具体的图形,每种图形都实现Shape 接口。
圆形
public class CircleShape implements Shape {
public CircleShape() {
System.out.println( "CircleShape: created");
}
@Override
public void draw() {
System.out.println( "draw: CircleShape");
}
}
正方形
public class CircleShape implements Shape {
public RectShape() {
System.out.println( "RectShape: created");
}
@Override
public void draw() {
System.out.println( "draw: RectShape");
}
}
三角形
public class CircleShape implements Shape {
public TriangleShape() {
System.out.println( "TriangleShape: created");
}
@Override
public void draw() {
System.out.println( "draw: TriangleShape");
}
}
产品类完成了,下面是工厂类的具体实现。
public class ShapeFactory {
public static final String TAG = "ShapeFactory";
public static Shape getShape(String type) {
Shape shape = null;
if (type.equalsIgnoreCase("circle")) {
shape = new CircleShape();
} else if (type.equalsIgnoreCase("rect")) {
shape = new RectShape();
} else if (type.equalsIgnoreCase("triangle")) {
shape = new TriangleShape();
}
return shape;
}
}
在这个工厂类种通过传入不同的type就可以new不同的形状,返回结果是Shape类型。这个就是简单工厂核心的地方了。客户端使用如下。
画各种形状。
Shape shape= ShapeFactory.getShape("circle");
shape.draw();
Shape shape= ShapeFactory.getShape("rect");
shape.draw();
Shape shape= ShapeFactory.getShape("triangle");
shape.draw();
只通过给ShapeFactory传入不同的参数就实现了各种形状的绘制。以上就是简单工厂方式。
工厂方法模式
工厂方法模式是简单工厂的进一步深化, 在工厂方法模式中,我们不再提供一个统一的工厂类来创建所有的对象,而是针对不同的对象提供不同的工厂。也就是说每个对象都有一个与之对应的工厂。在简单工厂模式种,工厂是没有基类的,现在我们要创建一个工厂基类。工厂方法模式去掉了简单工厂模式中工厂方法的静态属性,使得它可以被子类继承。这样在简单工厂模式里集中在工厂方法上的压力可以由工厂方法模式里不同的工厂子类来分担。
如果要新增加星形,在简单工厂模式中,我们需要修改原来的工厂类,增加创建星形的代码。这不符合开闭原则(对拓展开放,对修改封闭)。而在工厂方法模式中,我们只需要增加一个工厂子类,而无需修改原来的工厂类代码。
定义:
定义一个用于创建对象的接口,让子类决定将哪一个类实例化。工厂方法模式让一个类的实例化延迟到其子类。
这次我们先用实例详细解释一下这个定义,最后总结它的使用场景。
组成:
- 抽象工厂角色: 这是工厂方法模式的核心,它与应用程序无关。是具体工厂角色必须实现的接口或者必须继承的父类。在java中它由抽象类或者接口来实现。
- 具体工厂角色:它含有和具体业务逻辑有关的代码。由应用程序调用以创建对应的具体产品的对象。
- 抽象产品角色:它是具体产品继承的父类或者是实现的接口。在java中一般有抽象类或者接口来实现。
- 具体产品角色:具体工厂角色所创建的对象就是此角色的实例。在java中由具体的类来实现。
实例:
现在需要设计一个这样的图片加载类,它具有多个图片加载器,用来加载jpg,png,gif格式的图片,每个加载器都有一个read()方法,用于读取图片。下面我们完成这个图片加载类。
产品类:
public interface Reader {
void read();
}
\Jpg图片加载器
public class JpgReader implements Reader {
@Override
public void read() {
System.out.print("read jpg");
}
}
\Png图片加载器
public class PngReader implements Reader {
@Override
public void read() {
System.out.print("read png");
}
}
\Gif图片加载器
public class GifReader implements Reader {
@Override
public void read() {
System.out.print("read gif");
}
}
工厂类:
public interface ReaderFactory {
Reader getReader();
}
\Jpg加载器工厂
public class JpgReaderFactory implements ReaderFactory {
@Override
public Reader getReader() {
return new JpgReader();
}
}
\Png加载器工厂
public class PngReaderFactory implements ReaderFactory {
@Override
public Reader getReader() {
return new PngReader();
}
}
\Gif加载器工厂
public class GifReaderFactory implements ReaderFactory {
@Override
public Reader getReader() {
return new GifReader();
}
}
客户端使用:
\读取Jpg
ReaderFactory factory=new JpgReaderFactory();
Reader reader=factory.getReader();
reader.read();
\读取Png
ReaderFactory factory=new PngReaderFactory();
Reader reader=factory.getReader();
reader.read();
\读取Gif
ReaderFactory factory=new GifReaderFactory();
Reader reader=factory.getReader();
reader.read();
可以看到上面三段代码,分别读取了不同格式的图片,不同之处在于针对不同的图片格式声明了不同的工厂,进而创建了相应的图片加载器。
和简单工厂对比一下,最根本的区别在于,简单工厂只有一个统一的工厂类,而工厂方法是针对每个要创建的对象都会提供一个工厂类,这些工厂类都实现了一个工厂基类(本例中的ReaderFactory )。下面总结一下工厂方法的适用场景。
适用场景:
- 客户端不需要知道它所创建的对象的类。例子中我们不知道每个图片加载器具体叫什么名,只知道创建它的工厂名就完成了创建过程。
- 客户端可以通过子类来指定创建对应的对象。
抽象工厂模式
这个模式最不好理解,而且在实际应用中局限性也蛮大的,因为这个模式并不符合开闭原则。实际开发还需要做好权衡。
抽象工厂模式是工厂方法的仅一步深化,在这个模式中的工厂类不单单可以创建一个对象,而是可以创建一组对象。这是和工厂方法最大的不同点。
定义:
提供一个创建一系列相关或相互依赖对象的接口,而无须指定它们具体的类。( 在抽象工厂模式中,每一个具体工厂都提供了多个工厂方法用于产生多种不同类型的对象)
抽象工厂和工厂方法一样可以划分为4大部分:
AbstractFactory(抽象工厂):声明了一组用于创建对象的方法,注意是一组。
ConcreteFactory(具体工厂):它实现了在抽象工厂中声明的创建对象的方法,生成一组具体对象。
AbstractProduct(抽象产品):它为每种对象声明接口,在其中声明了对象所具有的业务方法。
ConcreteProduct(具体产品):它定义具体工厂生产的具体对象。
下面还是先看一个具体实例。
实例:
现在需要做一款跨平台的游戏,需要兼容Android,Ios,Wp三个移动操作系统,该游戏针对每个系统都设计了一套操作控制器(OperationController)和界面控制器(UIController),下面通过抽闲工厂方式完成这款游戏的架构设计。
由题可知,游戏里边的各个平台的UIController和OperationController应该是我们最终生产的具体产品。所以新建两个抽象产品接口。
产品类:
\抽象操作控制器
public interface OperationController {
void control();
}
\抽象界面控制器
public interface UIController {
void display();
}
\然后完成各个系统平台的具体操作控制器和界面控制器
\Android
public class AndroidOperationController implements OperationController {
@Override
public void control() {
System.out.println("AndroidOperationController");
}
}
public class AndroidUIController implements UIController {
@Override
public void display() {
System.out.println("AndroidInterfaceController");
}
}
\Ios
public class IosOperationController implements OperationController {
@Override
public void control() {
System.out.println("IosOperationController");
}
}
public class IosUIController implements UIController {
@Override
public void display() {
System.out.println("IosInterfaceController");
}
}
\Wp
public class WpOperationController implements OperationController {
@Override
public void control() {
System.out.println("WpOperationController");
}
}
public class WpUIController implements UIController {
@Override
public void display() {
System.out.println("WpInterfaceController");
}
}
工厂类:
\Android
public class AndroidFactory implements SystemFactory {
@Override
public OperationController createOperationController() {
return new AndroidOperationController();
}
@Override
public UIController createInterfaceController() {
return new AndroidUIController();
}
}
\Ios
public class IosFactory implements SystemFactory {
@Override
public OperationController createOperationController() {
return new IosOperationController();
}
@Override
public UIController createInterfaceController() {
return new IosUIController();
}
}
\Wp
public class WpFactory implements SystemFactory {
@Override
public OperationController createOperationController() {
return new WpOperationController();
}
@Override
public UIController createInterfaceController() {
return new WpUIController();
}
}
客户端调用:
SystemFactory mFactory;
UIController interfaceController;
OperationController operationController;
//Android
mFactory=new AndroidFactory();
//Ios
mFactory=new IosFactory();
//Wp
mFactory=new WpFactory();
interfaceController=mFactory.createInterfaceController();
operationController=mFactory.createOperationController();
interfaceController.display();
operationController.control();
针对不同平台只通过创建不同的工厂对象就完成了操作和UI控制器的创建。如果这个游戏使用工厂方法模式搭建需要创建多少个工厂类呢?两个工厂基类,分别各三个子类。需要8个类,而现在只需要4个类。下面总结一下抽象工厂的适用场景。
适用场景:
- 和工厂方法一样客户端不需要知道它所创建的对象的类,只需要知道创建它的工厂名。
- 需要一组对象共同完成某种功能时。并且可能存在多组对象完成不同功能的情况。
- 系统结构稳定,不会频繁的增加对象。(如果增加功能,这里的工厂基类是需要被修改的,不符合开闭原则)。
以上就是三种工厂模式的总结。
抽象工厂模式的起源
抽象工厂模式的起源或者最早的应用,是用于创建分属于不同操作系统的视窗构建。比如:命令按键(Button)与文字框(Text)都是视窗构建,在UNIX操作系统的视窗环境和Windows操作系统的视窗环境中,这两个构建有不同的本地实现,它们的细节有所不同。
在每一个操作系统中,都有一个视窗构建组成的构建家族。在这里就是Button和Text组成的产品族。而每一个视窗构件都构成自己的等级结构,由一个抽象角色给出抽象的功能描述,而由具体子类给出不同操作系统下的具体实现。
可以发现在上面的产品类图中,有两个产品的等级结构,分别是Button等级结构和Text等级结构。同时有两个产品族,也就是UNIX产品族和Windows产品族。UNIX产品族由UNIX Button和UNIX Text产品构成;而Windows产品族由Windows Button和Windows Text产品构成。
系统对产品对象的创建需求由一个工程的等级结构满足,其中有两个具体工程角色,即UnixFactory和WindowsFactory。UnixFactory对象负责创建Unix产品族中的产品,而WindowsFactory对象负责创建Windows产品族中的产品。这就是抽象工厂模式的应用,抽象工厂模式的解决方案如下图:
显然,一个系统只能够在某一个操作系统的视窗环境下运行,而不能同时在不同的操作系统上运行。所以,系统实际上只能消费属于同一个产品族的产品。
在现代的应用中,抽象工厂模式的使用范围已经大大扩大了,不再要求系统只能消费某一个产品族了。
总结
无论是简单工厂模式,工厂方法模式,还是抽象工厂模式,他们都属于工厂模式,在形式和特点上也是极为相似的,他们的最终目的都是为了解耦。在使用时,我们不必去在意这个模式到底工厂方法模式还是抽象工厂模式,因为他们之间的演变常常是令人琢磨不透的。经常你会发现,明明使用的工厂方法模式,当新需求来临,稍加修改,加入了一个新方法后,由于类中的产品构成了不同等级结构中的产品族,它就变成抽象工厂模式了;而对于抽象工厂模式,当减少一个方法使的提供的产品不再构成产品族之后,它就演变成了工厂方法模式。
所以,在使用工厂模式时,只需要关心降低耦合度的目的是否达到了。