工厂模式
工厂模式属于设计模式中的创建型。
工厂模式的分类
工厂模式又可以细分为三类。
分别为简单工厂、工厂方法、抽象工厂三种。
其中简单工厂一般看做是工厂方法中的一个特例。
工厂模式的好处
封装变化: 创建逻辑有可能变化,封装成工厂类之后,创建逻辑的变更对调用者透明。
代码复用: 创建代码抽离到独立的工厂类之后可以复用。
隔离复杂性: 封装复杂的创建逻辑,调用者无需了解如何创建对象。
控制复杂度: 将创建代码抽离出来,让原本的函数或类职责更单一,代码更简洁
增加代码可读性: 工厂内部封装不同的入参构造函数并提供语义清晰的调用方法,避免了类构造函数只能与类名相同。
什么情况下选择工厂模式
- 代码中存在if-else 或者 switch-case 语句,动态的根据不同的类型创建不同的对象,针对这种情况就可以考虑使用工厂模式,将这部分创建对象的代码抽离出来,放到工厂类中。
- 单个对象的创建过程复杂,例如需要组合其他类对象,做各种初始化操作,这种情况下,也可以封装这部分创建逻辑,放到工厂类中。
简单工厂
在创建一个对象时不向客户暴露内部细节,并提供一个创建对象的通用接口。
简单工厂把实例的创建过程单独放到一个类中,这个类就是简单工厂类,由简单工厂类来决定具体实例化哪个子类。
例如一个游戏启动程序,根据不同的输入打开不同的游戏
public class GameConfigSource{
public Game load(String gameName){
Game game = null;
if ("lol".equalsIgnoreCase(gameName)) {
game = new LolGameClient();
} else if ("cs".equalsIgnoreCase(gameName)) {
game = new CsGameClient();
} else if ("wow".equalsIgnoreCase(gameName)) {
game = new WowGameClient();
} else if ("ddz".equalsIgnoreCase(gameName)) {
game = new DdzGameClient();
} else {
throw new InvalidGameException("Game is not found: " + ruleConfigFilePath);
}
game.start();
return game;
}
}
这个类目前来看好像没什么问题。我们来具体看一下,这个类对象的创建和对象的使用耦合在了一起,未来不管是新增对象还是新增使用方法,我都需要重新修改这个类,这违反了开闭原则。
那么为了使类的职责更加单一、代码更加清晰,我们可以将创建这一部分放到另一个独立的类中,只负责创建
public class GameFactory{
public static Game createGame(String gameName){
Game game = null;
if ("lol".equalsIgnoreCase(gameName)) {
game = new LolGameClient();
} else if ("cs".equalsIgnoreCase(gameName)) {
game = new CsGameClient();
} else if ("wow".equalsIgnoreCase(gameName)) {
game = new WowGameClient();
} else if ("ddz".equalsIgnoreCase(gameName)) {
game = new DdzGameClient()
}
return game;
}
}
public class GameConfigSource{
public Game load(String gameName){
Game game = GameFactory.createGame(gameName);
if(null == game){
throw new InvalidGameException("Game is not found: " + gameName);
}
game.start();
return game;
}
}
上述代码如果频繁的添加新的游戏,那么势必会频繁的修改GameFactory类,这违反了开闭原则,但是如果不是特别频繁,只是偶尔修改,还是可以接受的。
如果不想违反开闭原则的话,可以通过反射加配置文件的方式,通常类的全路径较长,通过配置文件可读性好一些。
配置文件中 : lol=com.test.game.LolGameClient;
//读取配置文件
String name = parserConfig(gameName);
//通过反射获取类
Object obj = Class.forName(name).newInstance();
工厂方法
定义了一个创建对象的接口,但由子类决定要实例化哪个类。工厂方法把实例化操作推迟到子类
工厂方法模式是简单工厂的进一步深化, 在工厂方法模式中,我们不再提供一个统一的工厂类来创建所有的对象,而是针对不同的对象提供不同的工厂。也就是说 每个对象都有一个与之对应的工厂。
public interface GameFactory{
public Game getGame();
}
public class LolGameFactory implements GameFactory{
@Override
public Game getGame() {
return new LolGameClient();
}
}
public class CsGameFactory implements GameFactory{
@Override
public Game getGame() {
return new CsGameClient();
}
}
public class WowGameFactory implements GameFactory{
@Override
public Game getGame() {
return new WowGameClient();
}
}
public class DdzGameFactory implements GameFactory{
@Override
public Game getGame() {
return new DdzGameClient();
}
}
public class GameConfigFactory{
public static GameFactory createFactory(String gameName){
GameFactory gameFactory = null;
if ("lol".equalsIgnoreCase(gameName)) {
game = new LolGameFactory();
} else if ("cs".equalsIgnoreCase(gameName)) {
game = new CsGameFactory();
} else if ("wow".equalsIgnoreCase(gameName)) {
game = new WowGameFactory();
} else if ("ddz".equalsIgnoreCase(gameName)) {
game = new DdzGameFactory();
}
return gameFactory;
}
}
其实这样看来,还是不能避免大量if-else的问题,反而加深了代码的复杂性,而且每个类的创建只是简单的new操作。所以这种情况下简单工厂比工厂方法要合适。
什么时候用简单工厂什么时候用工厂方法
- 当创建的对象只是通过new操作,推荐使用简单工厂模式。
- 当创建的对象逻辑比较复杂,不只是简单的new操作,而是要组合其他类,做各种初始化操作的时候,推荐使用工厂方法,将复杂的创建逻辑拆分到多个工厂类中,这样不至于使每个类过于臃肿。
抽象工厂
提供一个接口,用于创建 相关的对象家族 。
抽象工厂模式创建的是对象家族,也就是很多对象而不是一个对象,并且这些对象是相关的,也就是说必须一起创建出来。而工厂方法模式只是用于创建一个对象,这和抽象工厂模式有很大不同。
例如我们的例子中,开启游戏不仅需要游戏客户端,还需要打开游戏音效。
public interface GameFactory{
public GameClient getClient();
public GameVoice getVoice();
}
public class LolGameFactory implements GameFactory{
@Override
public GameClient getClient() {
return new LolGameClient();
}
@Override
public GameVoice getVoice() {
return new LolGameVoice();
}
}
public class CsGameFactory implements GameFactory{
@Override
public GameClient getClient() {
return new CsGameClient();
}
@Override
public GameVoice getVoice() {
return new CsGameVoice();
}
}
public class WowGameFactory implements GameFactory{
@Override
public GameClient getClient() {
return new WowGameClient();
}
@Override
public GameVoice getVoice() {
return new WowGameVoice();
}
}
public class DdzGameFactory implements GameFactory{
@Override
public GameClient getClient() {
return new DdzGameClient();
}
@Override
public GameVoice getVoice() {
return new DdzGameVoice();
}
}
public class Client {
public static void main(String[] args) {
GameFactory gameFactory = new LolGameFactory();
GameClient client = gameFactory.getClient();
GameVoice voice = gameFactory.getVoice();
// do something
}
}
扩展
如果工厂对象可以复用,可以通过一个map来缓存工厂实例,达到复用的效果,也避免了大量if-else的情况,每次添加维护map即可。
// 简单工厂
public class GameFactoryMap {
private static final Map<String, Game> cachedGame = new HashMap<>();
static {
cachedGame.put("lol", new LolGameClient());
cachedGame.put("cs", new CsGameClient());
cachedGame.put("wow", new WowGameClient());
cachedGame.put("ddz", new DdzGameClient());
}
public static Game createGame(String gameName) {
if (gameName == null || gameName.isEmpty()) {
return null;
}
Game geme = cachedGame.get(gameName.toLowerCase());
return geme;
}
}
// 工厂方法、抽象工厂
public class GameFactoryMap {
private static final Map<String, GameFactory> cachedGameFactory = new HashMap<>();
static {
cachedGameFactory.put("lol", new LolGameFactory());
cachedGameFactory.put("cs", new CsGameFactory());
cachedGameFactory.put("wow", new WowGameFactory());
cachedGameFactory.put("ddz", new DdzGameFactory());
}
public static GameFactory createFactory(String gameName) {
if (gameName == null || gameName.isEmpty()) {
return null;
}
GameFactory gemeFactory = cachedGameFactory.get(gameName.toLowerCase());
return gemeFactory;
}
}