Factory Pattern
1、缘起
Sunny需开发一套图表库,图表可以根据系统的不同提供不同的外观,于是便有了下面的代码,根据入参type的不同,我们可以构造不同的Chart对象。乍一看代码没有问题,但仔细一想这样书写代码可能会带来哪些不便呢?
- 首先最明显的就是使用了大量的if-else语句,不仅执行效率低,而且不利于阅读、修改。
- Chart类的职责过重,既需要关注对象的创建初始化,还需要书写对象的"显示"方法,违反了"单一职责"原则。
- 当需要添加新的Chart类型时,需要修改代码,违反了"开闭原则"。
- 客户端只能使用new关键字创建对象,客户端与Chart耦合度较高。
- 没有提供默认的初始化设置,如柱状图,每次都需要在客户端如参进行初始化,带来大量重复冗余代码。
class Chart {
private String type; //图表类型
public Chart(Object[][] data, String type) {
this.type = type;
if (type.equalsIgnoreCase("histogram")) {
//初始化柱状图
}
else if (type.equalsIgnoreCase("pie")) {
//初始化饼状图
}
else if (type.equalsIgnoreCase("line")) {
//初始化折线图
}
}
public void display() {
if (this.type.equalsIgnoreCase("histogram")) {
//显示柱状图
}
else if (this.type.equalsIgnoreCase("pie")) {
//显示饼状图
}
else if (this.type.equalsIgnoreCase("line")) {
//显示折线图
}
}
}
2、简单工厂模式
其实简单工厂模式的思路也不复杂,就是两部分:
①将Chart抽象成接口,用不同的子类实现接口,这样就不用再去传参(Object[][] data),直接根据入参的type就能够创建对应的对象。
②定义一个Chart的工厂,专注于Chart对象的创建,这样就将对象的初始化与对象的创建分离。
//抽象图表接口:抽象产品类
interface Chart {
public void display();
}
//柱状图类:具体产品类
class HistogramChart implements Chart {
public HistogramChart() {
System.out.println("创建柱状图!");
}
public void display() {
System.out.println("显示柱状图!");
}
}
//饼状图类:具体产品类
class PieChart implements Chart {
public PieChart() {
System.out.println("创建饼状图!");
}
public void display() {
System.out.println("显示饼状图!");
}
}
//图表工厂类:工厂类
class ChartFactory {
//静态工厂方法
public static Chart getChart(String type) {
Chart chart = null;
if (type.equalsIgnoreCase("histogram")) {
chart = new HistogramChart();
System.out.println("初始化设置柱状图!");
}
else if (type.equalsIgnoreCase("pie")) {
chart = new PieChart();
System.out.println("初始化设置饼状图!");
}
else if (type.equalsIgnoreCase("line")) {
chart = new LineChart();
System.out.println("初始化设置折线图!");
}
return chart;
}
}
//-------------------------------------------------------------
//客户端:
class Client {
public static void main(String args[]) {
Chart chart;
//只需要更换getChart的入参即可创建不同的对象
chart = ChartFactory.getChart("histogram"); //通过静态工厂方法创建产品
chart.display();
}
}
//-----------------------------------------------------------------
//我们甚至可以把创建对象所需的参数放到配置文件中,这样每次只需改配置文件即可
class Client {
public static void main(String args[]) {
Chart chart;
String type = XMLUtil.getChartType(); //读取配置文件中的参数
chart = ChartFactory.getChart(type); //创建产品对象
chart.display();
}
}
简单工厂模式解决的是对象的创建与对象初始化分离的功能,工厂只需关注创建何种对象,创建对象所需的参数也不用想原始代码那样给,我们已经定义好实现类了,需要创建什么对象直接加一个新的Chart的实现类,然后再去工厂中添加一个判断语句即可。
对象的初始化及其所需方法放到对象身上去实现,每个实现类有自己的一套初始化及所需方法的实现,工厂只负责创建这些对象。原来的代码在Chart中既需要关注对象的初始化,还需要提供对象所需方法,还要关注对象的生成,太重。
优点:
①Client不用再传参就能够得到对象,解耦。
②需要不同的对象修改配置文件即可,符合"开闭原则"。
3、工厂方法模式
乍一看上述代码得到了优化,但其实细心的同学还是会觉得工厂类太过冗余
①包含了大量if–else的问题没有解决
②想要在工厂中添加一个新的对象时必须修改工厂代码
如下我们想要返回不同的日志类型,用上面的方法书写为:
//日志记录器工厂
class LoggerFactory {
//静态工厂方法
public static Logger createLogger(String args) {
if(args.equalsIgnoreCase("db")) {
//连接数据库,代码省略
//创建数据库日志记录器对象
Logger logger = new DatabaseLogger();
//初始化数据库日志记录器,代码省略
return logger;
}
else if(args.equalsIgnoreCase("file")) {
//创建日志文件
//创建文件日志记录器对象
Logger logger = new FileLogger();
//初始化文件日志记录器,代码省略
return logger;
}
else {
return null;
}
}
}
简单工厂模式中我们将所需对象抽象成接口,然后用不同的实现类去实现,那么工厂我们可否也使用同样的思路解耦呢?答案是肯定的,我们仍然可以将工厂抽象成接口,然后用不同的工厂实现类去实现工厂接口,不同的实现类创建不同的日志对象。
//日志记录器接口:抽象产品
interface Logger {
public void writeLog();
}
//数据库日志记录器:具体产品
class DatabaseLogger implements Logger {
public void writeLog() {
System.out.println("数据库日志记录。");
}
}
//文件日志记录器:具体产品
class FileLogger implements Logger {
public void writeLog() {
System.out.println("文件日志记录。");
}
}
//日志记录器工厂接口:抽象工厂
interface LoggerFactory {
public Logger createLogger();
}
//数据库日志记录器工厂类:具体工厂
class DatabaseLoggerFactory implements LoggerFactory {
public Logger createLogger() {
//连接数据库,代码省略
//创建数据库日志记录器对象
Logger logger = new DatabaseLogger();
//初始化数据库日志记录器,代码省略
return logger;
}
}
//文件日志记录器工厂类:具体工厂
class FileLoggerFactory implements LoggerFactory {
public Logger createLogger() {
//创建文件日志记录器对象
Logger logger = new FileLogger();
//创建文件,代码省略
return logger;
}
}
//-----------------------------------------------------------
class Client {
public static void main(String args[]) {
LoggerFactory factory;
Logger logger;
factory = new FileLoggerFactory(); //可引入配置文件实现
logger = factory.createLogger();
logger.writeLog();
}
}
//------------------------------------------------------------
class Client {
public static void main(String args[]) {
LoggerFactory factory;
Logger logger;
//getBean()的返回类型为Object,需要进行强制类型转换,将类名放到配置文件中通过反射创建工厂实现类
factory = (LoggerFactory)XMLUtil.getBean();
logger = factory.createLogger();
logger.writeLog();
}
}
我们甚至可以在工厂中提供writeLog的方法,这样客户端直接使用工厂即可调用该方法,而无需关注logger对象的创建。
//改为抽象类
abstract class LoggerFactory {
//在工厂类中直接调用日志记录器类的业务方法writeLog(),工厂调用此方法时,实则调用子类覆盖后的方法
public void writeLog() {
Logger logger = this.createLogger();
logger.writeLog();
}
public abstract Logger createLogger();
}
或者我们可以在工厂中添加重载的方法,可以根据参数创建不同的logger实现。
interface LoggerFactory {
public Logger createLogger();
public Logger createLogger(String args);
public Logger createLogger(Object obj);
}
--------------------------------------------------
class DatabaseLoggerFactory implements LoggerFactory {
public Logger createLogger() {
//使用默认方式连接数据库,代码省略
Logger logger = new DatabaseLogger();
//初始化数据库日志记录器,代码省略
return logger;
}
public Logger createLogger(String args) {
//使用参数args作为连接字符串来连接数据库,代码省略
Logger logger = new DatabaseLogger();
//初始化数据库日志记录器,代码省略
return logger;
}
public Logger createLogger(Object obj) {
//使用封装在参数obj中的连接字符串来连接数据库,代码省略
Logger logger = new DatabaseLogger();
//使用封装在参数obj中的数据来初始化数据库日志记录器,代码省略
return logger;
}
}
工厂方法模式是使用最多的设计模式之一,是简单方法模式的补充,我们将工厂进行了抽象,这样如果我们想添加一个新的logger实现,只需要添加一个logger实现类和一个工厂实现类即可,无需在原有代码上修改,系统的扩展性变得非常好。
但同时工厂方法模式也带来了理解和实现的难度,需要理解的东西较多,且使用了反射。
4、抽象工厂模式
咱们先来看一张图片,下图是工厂方法模式的一个体现,其实工厂方法模式就是将对象和工厂分隔开,对象进行对象的抽象,工厂进行工厂的抽象,然后用工厂的实现去创建对象的实现,对象的实现已经实现了对象的方法,所以我们创建出的对象实现直接就可以调用该实现的方法。
理解了上述过程之后咱们再来看看这张图,我们想定义一个皮肤库,其中包含按钮、文本边框、文本组合框等样式,各个样式构成我们需要的皮肤产品。很明显我们的按钮只是一个抽象,旗下可以有很多不同的按钮样式,而文本边框和文本组合框亦如此。我们可以很自然的想到使用对三个样式进行抽象,然后分别提供实现类进行实现。但此时我们的工厂该如何设计呢?
如果按照一个实现类一个工厂,那么这个工厂的数量将会十分巨大,而且我们在client中使用时,也只能拿到每个样式各自的实现类,client中的代码会十分不简洁、优雅。我们可以看看下面这张图,其实我们真正需要的是每个样式的组合,所以我们的工厂就不止是生产一个对象了,这里包含三个对象:按钮、文本边框、文本组合框。其实就是工厂方法模式的一个扩展,将生产的对象进行了扩充。
//在本实例中我们对代码进行了大量简化,实际使用时,界面组件的初始化代码较为复杂,还需要使用JDK中一些已有类,为了突出核心代码,在此只提供框架代码和演示输出。
//按钮接口:抽象产品
interface Button {
public void display();
}
//Spring按钮类:具体产品
class SpringButton implements Button {
public void display() {
System.out.println("显示浅绿色按钮。");
}
}
//Summer按钮类:具体产品
class SummerButton implements Button {
public void display() {
System.out.println("显示浅蓝色按钮。");
}
}
//文本框接口:抽象产品
interface TextField {
public void display();
}
//Spring文本框类:具体产品
class SpringTextField implements TextField {
public void display() {
System.out.println("显示绿色边框文本框。");
}
}
//Summer文本框类:具体产品
class SummerTextField implements TextField {
public void display() {
System.out.println("显示蓝色边框文本框。");
}
}
//组合框接口:抽象产品
interface ComboBox {
public void display();
}
//Spring组合框类:具体产品
class SpringComboBox implements ComboBox {
public void display() {
System.out.println("显示绿色边框组合框。");
}
}
//Summer组合框类:具体产品
class SummerComboBox implements ComboBox {
public void display() {
System.out.println("显示蓝色边框组合框。");
}
}
//界面皮肤工厂接口:抽象工厂
interface SkinFactory {
public Button createButton();
public TextField createTextField();
public ComboBox createComboBox();
}
//Spring皮肤工厂:具体工厂
class SpringSkinFactory implements SkinFactory {
public Button createButton() {
return new SpringButton();
}
public TextField createTextField() {
return new SpringTextField();
}
public ComboBox createComboBox() {
return new SpringComboBox();
}
}
//Summer皮肤工厂:具体工厂
class SummerSkinFactory implements SkinFactory {
public Button createButton() {
return new SummerButton();
}
public TextField createTextField() {
return new SummerTextField();
}
public ComboBox createComboBox() {
return new SummerComboBox();
}
}
----------------------------------------------------------------
class Client {
public static void main(String args[]) {
//使用抽象层定义
SkinFactory factory;
Button bt;
TextField tf;
ComboBox cb;
factory = (SkinFactory)XMLUtil.getBean();
bt = factory.createButton();
tf = factory.createTextField();
cb = factory.createComboBox();
bt.display();
tf.display();
cb.display();
}
}
工厂方法模式只是返回了一个对象,如Logger的实现。而抽象工厂模式就是在工厂中可以返回多个对象的实现,如:Button、TextField、ComboBox。可以看出抽象工厂模式是针对于多个对象创建时的一种简化方案,减少了工厂实现类的数量。同时它仍让满足"开闭原则",如果需要添加新的皮肤类型,只需要添加n个样式实现及一个新的工厂实现即可。
不过抽象工厂模式也有一个缺点,如果你想在工厂中添加一个新的样式,如:单选按钮。那么就需要在所有工厂实现中添加该对象的实现。所以在使用抽象工厂模式时一定要考虑好不会在工厂中增加新的样式才行1,这个现象也叫作"开闭原则"的倾斜性。
补充
其实我们上述的案例中,工厂可以生产多个对象(样式),这些对象构成一个产品族,而同产品族下又构成产品等级。比如:手机厂商就是一个产品族,有华为、三星、苹果,而华为手机、三星手机、苹果手机则构成一个产品等级,华为笔记本、三星笔记本、苹果笔记本也构成一个产品等级。
其实也不一定,Java 8中接口可以书写默认方法,如果实在需要添加的话,可以在工厂接口中添加一个默认方法返回增加的样式也可,子类工厂还可以覆盖此方法使用。 ↩︎