这一篇文章是来自群友分享阿里面试过程,我想通过这种情景模拟地方式来告诉大家在面试地时候,应该如何有条理地回答问题。
面试官: 能否解释一下简单工厂模式存在的问题,为何会违背开放-封闭原则?
求职者: 嗯,简单工厂模式中,工厂类包含了实例化所有具体运算类的逻辑。如果要增加新的运算符功能,比如‘求余’,我们需要在工厂类中添加新的case
分支。这就意味着每次增加新的运算符,都要修改工厂类代码,这样就违反了开放-封闭原则,因为它对修改是开放的。
面试官: 对,那么工厂方法模式是如何解决这个问题的呢?
求职者: 工厂方法模式通过定义一个创建对象的接口,并让子类决定实例化哪一个类来解决这个问题。这样,新增运算符时,我们只需添加一个新的具体工厂类来实现创建接口,而不必修改现有的代码。
面试官: 好的,那你能根据这个原理,给我展示一个工厂方法模式的Java代码实现吗?
求职者: 当然可以。这里是一个工厂接口和一个具体的乘法运算工厂类的例子:
// 工厂接口
public interface IFactory {Operation createOperation();
}// 乘法运算类的工厂
public class MultiFactory implements IFactory {@Overridepublic Operation createOperation() {return new OperationMulti();}
}
面试官: 那么,在客户端代码中,工厂方法模式是如何工作的呢?
求职者: 在客户端代码中,我们首先根据用户输入的运算符号来选择对应的工厂实例。然后,使用这个工厂实例创建具体的运算对象。这里是客户端的一个示例:
public class CalculatorClient {public static void main(String[] args) {IFactory iFactory = null;int numberA = 0;int numberB = 0;String operationStr = null;Scanner scanner = new Scanner(System.in);// ...用户输入处理...switch (operationStr) {case "+":iFactory = new AddFactory();break;case "-":iFactory = new SubFactory();break;case "*":iFactory = new MultiFactory();break;case "/":iFactory = new DivFactory();break;default:System.out.println("操作符为空");System.exit(0);}Operation operation = iFactory.createOperation();operation.setNumberA(numberA);operation.setNumberB(numberB);double result = operation.getResult();System.out.println("结果为" + numberA + operationStr + numberB + "=" + result);}
}
面试官: 很好。那么,工厂方法模式相比简单工厂模式有什么优势和劣势?
求职者: 工厂方法模式的主要优势是它遵守了开放-封闭原则,易于扩展新的运算符类,而不需要修改现有的工厂类代码。不过,它的劣势是每增加一个新的产品类,就需要额外增加一个对应的工厂类,这会增加系统的复杂性和开发量。而且,由于判断逻辑转移到了客户端,所以增加了客户端的代码复杂性。
面试官: 明白了。那这种情况下,你怎么看待这种劣势呢?
求职者: 虽然每增加一个产品类就需要增加一个工厂类,这确实增加了开发量,但这种开销通常是可接受的,因为它为系统的可维护性和可扩展性提供了很大的好处。而且,在一些语言中,比如Java,我们可以通过抽象工厂模式结合反射机制来减少客户端代码的复杂性。
面试官: 很好,你对工厂方法模式有深刻的理解。你能举例说明工厂方法模式适用于哪些场景吗?
求职者: 当然。工厂方法模式适用于以下几种场景:
- 当一个类不知道它所必须创建的对象的类的时候。例如,在工具软件中,我们可能需要根据不同的文件类型来创建不同的解析器,但是具体使用哪个解析器,是由用户提供的文件决定的。
- 当一个类希望由它的子类来指定它所创建的对象的时候。这种情况下,设计者可以利用工厂方法将类实例化的过程延迟到子类进行。
- 当类的创建逻辑比较复杂,且需要大量重构的时候。将创建逻辑放在工厂方法中可以避免重复的代码,并且更容易进行修改。
- 当系统需要考虑扩展性,不应依赖于产品类实例如何被创建、组合和表达的细节时。这时候使用工厂方法模式可以在不修改客户端代码的前提下,在系统中引入新的产品类型。
面试官: 很好。那你能给出一个具体的代码例子来说明工厂方法模式在这些场景中是如何运用的吗?
求职者: 好的,以日志记录器为例,我们的系统可以输出不同类型的日志,如文件日志、数据库日志等。我们可以为每种日志类型提供一个具体的工厂类。这里是一个简化的例子:
// 日志记录器接口
public interface Logger {void log(String message);
}// 文件日志记录器
public class FileLogger implements Logger {@Overridepublic void log(String message) {// 文件日志记录逻辑}
}// 数据库日志记录器
public class DatabaseLogger implements Logger {@Overridepublic void log(String message) {// 数据库日志记录逻辑}
}// 日志记录器工厂接口
public interface LoggerFactory {Logger createLogger();
}// 文件日志记录器工厂
public class FileLoggerFactory implements LoggerFactory {@Overridepublic Logger createLogger() {// 可以在这里进行文件日志记录器的初始化操作return new FileLogger();}
}// 数据库日志记录器工厂
public class DatabaseLoggerFactory implements LoggerFactory {@Overridepublic Logger createLogger() {// 可以在这里进行数据库日志记录器的初始化操作return new DatabaseLogger();}
}// 客户端代码
public class LoggerClient {public static void main(String[] args) {LoggerFactory loggerFactory;Logger logger;// 根据配置或环境选择不同的日志记录器工厂loggerFactory = new FileLoggerFactory();// 或者// loggerFactory = new DatabaseLoggerFactory();logger = loggerFactory.createLogger();logger.log("这是一条日志信息");}
}
在这个例子中,客户端代码不需要知道具体的日志记录器类别,只需要知道日志记录器工厂接口,这样就可以在不同的环境下切换日志记录器的实现,而不需要修改客户端代码。
面试官: 这个例子很好地说明了工厂方法模式的应用,以及它是如何帮助实现依赖倒置和扩展性的。非常感谢你的分享。这就结束了我们今天的面试。