【问题引入】
初始情况:A 类实例与 B 类实例通过硬编码耦合(即 A 类实例中的方法直接使用 new 关键字创建 B 类实例
系统重构要求:使用 C 类实例来代替系统中的 B 类实例
问题:如果系统中有100个或者10000个使用 B 类实例的地方,是否只能通过逐个的将 B 类实例修改为 C 类的实例来完成系统的重构呢?有没有更好的解决方案?
【解决方案】
考虑让 B 类实现一个 IB 接口,而 A 类只需要和 IB 接口耦合——A 类个并不直接使用 new 关键字来创建 B 类实例,而是重新定义一个工厂类:IBFactory, 由该工厂类负责创建 IB 实例;而 A 类通过调用 IBFactory 工厂的方法类得到 IB 的实例。
【应用场景 A —— Computer 与 Outpet 实现类的解耦】
1. 具体问题
假设程序中有个 Computer 对象需要依赖一个输出设备,现在有两个选择:直接让 Computer 对象依赖一个 Printer 对象(实现类),或者让 Computer 对象依赖一个 Output 属性(接口),选用哪种方式更好?为什么?如果系统需要重构,即替换系统中的 Printer 对象为其他对象,应该怎样设计重构方案?
2. 方案设计
为使系统具有更好的可维护性和可扩展性,一般选用工厂模式。根据简单工厂设计模式,程序应该让 Computer 依赖一个 Output 属性,将 Computer 类与 Printer 实现类分离开来。Computer 对象只需面向 Output 接口编程即可;而 Computer 具体依赖于 Output 的那个实现类则完全透明。
3. 代码实现
Computer.java
public class Computer {
private Output out;
// 通过构造器注入方式实现 Computer 类与 Output 接口的耦合
public Computer(Output out) {
this.out = out;
}
// 定义一个模拟获取字符串输入的方法
public void keyIn(String msg) {
out.getData(msg);
}
// 定义一个模拟打印的方法
public void print() {
out.out();
}
public static void main(String[] args) {
// 创建OutputFactory
OutputFactory of = new OutputFactory();
// 将Output对象传入,创建Computer对象
Computer c = new Computer(of.getOutput());
c.keyIn("轻量级Java EE企业应用实战");
c.keyIn("疯狂Java讲义");
c.print();
}
}
Output.java
public interface Output {
// 接口里定义的属性只能是常量
public static final int MAX_CACHE_LINE = 50;
// 接口里定义的只能是public的抽象实例方法
public void out();
public void getData(String msg);
}
Printer.java
// 让Printer类实现Output
public class Printer implements Output {
private String[] printData = new String[MAX_CACHE_LINE];
// 用以记录当前需打印的作业数
private int dataNum = 0;
public void out() {
// 只要还有作业,继续打印
while (dataNum > 0) {
System.out.println("打印机打印:" + printData[0]);
// 把作业队列整体前移一位,并将剩下的作业数减1
System.arraycopy(printData, 1, printData, 0, --dataNum);
}
}
public void getData(String msg) {
if (dataNum >= MAX_CACHE_LINE) {
System.out.println("输出队列已满,添加失败");
} else {
// 把打印数据添加到队列里,已保存数据的数量加1。
printData[dataNum++] = msg;
}
}
}
OutputFactory.java
public class OutputFactory {
public Output getOutput() {
// 通过修改下面一行代码可以控制系统到底使用Output的哪个实现类。
return new Printer();
}
}
4. 代码分析
Computer——场景类,模拟具体实现场景
Output——接口,抽象输出设备的共有属性和方法
OutputFactory——工厂类,完成 Output 实现类的创建过程,实现 Computer 类与 Output 实现类的解耦
Printer——Output 实现类,实现输出设备的具体属性和方法
5. 系统重构
要求:使用 BetterPrinter 类来代替 Printer 类
解决:只需让 BetterPrinter 类实现 Output 接口,并改写 OutputFactory 类的 getOutput() 方法即可
6. 系统重构源码
Computer.java
public class Computer {
private Output out;
// 通过构造器注入方式实现 Computer 类与 Output 接口的耦合
public Computer(Output out) {
this.out = out;
}
// 定义一个模拟获取字符串输入的方法
public void keyIn(String msg) {
out.getData(msg);
}
// 定义一个模拟打印的方法
public void print() {
out.out();
}
public static void main(String[] args) {
// 创建OutputFactory
OutputFactory of = new OutputFactory();
// 将Output对象传入,创建Computer对象
Computer c = new Computer(of.getOutput());
c.keyIn("轻量级Java EE企业应用实战");
c.keyIn("疯狂Java讲义");
c.print();
}
}
Output.java
public interface Output {
// 接口里定义的属性只能是常量
public static final int MAX_CACHE_LINE = 50;
// 接口里定义的只能是public的抽象实例方法
public void out();
public void getData(String msg);
}
BetterPrinter.java
public class BetterPrinter implements Output {
private String[] printData = new String[MAX_CACHE_LINE * 2];
// 用以记录当前需打印的作业数
private int dataNum = 0;
public void out() {
// 只要还有作业,继续打印
while (dataNum > 0) {
System.out.println("高速打印机正在打印:" + printData[0]);
// 把作业队列整体前移一位,并将剩下的作业数减1
System.arraycopy(printData, 1, printData, 0, --dataNum);
}
}
public void getData(String msg) {
if (dataNum >= MAX_CACHE_LINE * 2) {
System.out.println("输出队列已满,添加失败");
} else {
// 把打印数据添加到队列里,已保存数据的数量加1。
printData[dataNum++] = msg;
}
}
}
OutputFactory.java
public class OutputFactory {
public Output getOutput() {
// 通过修改下面一行代码可以控制系统到底使用Output的哪个实现类。
return new Printer();
}
}
7. 系统重构源码分析
Computer——场景类,模拟具体实现场景
Output——接口,抽象输出设备的共有属性和方法
OutputFactory——工厂类,完成 Output 实现类的创建过程,实现 Computer 类与 Output 实现类的解耦
BetterPrinter——Output 实现类,实现输出设备的具体属性和方法,用于替换系统中的 Printer 对象
8. 代码问题整理
①java.lang.System 类 public static void arraycopy(Object src, int srcPos, Object dest, int destPos, int length) 方法的作用是什么?
public static void arraycopy(Object src, int srcPos, Object dest, int destPos, int length)
作用:从源数组 src 中复制一个数组到目标数组 dest 中。原数组起始位置为 srcPos,目标数组起始位置为 destPos,复制长度为 length
②Computer.java类中问题 1 ?
public class Computer {
/********************************************************************/
//1. 以下代码是否可以重写写入一个新的类中?
private Output out;
// 通过构造器注入方式实现 Computer 类与 Output 接口的耦合
public Computer(Output out) {
this.out = out;
}
// 定义一个模拟获取字符串输入的方法
public void keyIn(String msg) {
out.getData(msg);
}
// 定义一个模拟打印的方法
public void print() {
out.out();
}
/********************************************************************/
public static void main(String[] args) {
// 创建OutputFactory
OutputFactory of = new OutputFactory();
// 将Output对象传入,创建Computer对象
Computer c = new Computer(of.getOutput());
c.keyIn("轻量级Java EE企业应用实战");
c.keyIn("疯狂Java讲义");
c.print();
}
}
该问题的解决方案如下:
Client.java
/**
* 场景类——用于测试简单工厂模式
*/
public class Client {
public static void main(String[] args) {
// 获取OutputFactory
OutputFactory of = new OutputFactory();
// 获取Output
Output output = of.getOutput();
System.out.println("打印机" + output.getClass().getName() + "正在工作");
// 将Output对象传入,创建Computer对象
Computer c = new PersonComputer(output);
c.keyIn("轻量级Java EE企业应用实战");
c.keyIn("疯狂Java讲义");
c.keyIn("设计模式之禅");
c.print();
}
}
Computer.java
public interface Computer {
public void keyIn(String msg);
public void print();
}
PersonComputer.java
/**
* 调用者
*
*/
public class PersonComputer implements Computer {
private Output out;
// 通过构造器注入方式实现 Computer 类与 Output 接口的耦合
public PersonComputer(Output out) {
this.out = out;
}
// 定义一个模拟获取字符串输入的方法
public void keyIn(String msg) {
out.getData(msg);
}
// 定义一个模拟打印的方法
public void print() {
out.out();
}
}
Output.java
/**
* 输出接口——定义输出设备的通用属性和方法
*
*/
public interface Output {
// 接口里定义的属性只能是常量
public static final int MAX_CACHE_LINE = 50;
// 接口里定义的只能是public的抽象实例方法
public void out();
public void getData(String msg);
}
Printer.java
/**
* 被调用者
*
* 接口的实现类——接口中定义的方法和属性的具体实现
*
*/
public class Printer implements Output {
private String[] printData = new String[MAX_CACHE_LINE];
// 用以记录当前需打印的作业数
private int dataNum = 0;
public void out() {
// 只要还有作业,继续打印
while (dataNum > 0) {
System.out.println("打印机打印:" + printData[0]);
// 把作业队列整体前移一位,并将剩下的作业数减1
System.arraycopy(printData, 1, printData, 0, --dataNum);
}
}
public void getData(String msg) {
if (dataNum >= MAX_CACHE_LINE) {
System.out.println("输出队列已满,添加失败");
} else {
// 把打印数据添加到队列里,已保存数据的数量加1。
printData[dataNum++] = msg;
}
}
}
OutputFactory.java
/**
* 工厂——实现解耦
*
*/
public class OutputFactory {
public Output getOutput() {
// 通过修改下面一行代码可以控制系统到底使用Output的哪个实现类。
return new Printer();
// return new BetterPrinter();
}
}
【相关问题】
1. 简单工厂模式使用的三个要求:
① 调用者面向被依赖对象的接口编程;
② 将被依赖对象的创建交给接口完成;
③ 调用者通过工厂来获得被依赖组件;
2. 简单工厂模式的优点有哪些?
① 让对象的调用者和对象的创建过程分离,当对象调用者需要对象时,直接从工厂获取;
② 避免了对象的调用者与对象的实现类以硬编码的方式耦合,以提高系统的可维护性、可扩展性;
3. 简单工厂模式的缺点有哪些?
当产品修改时,工厂类必须做出相应的修改