工厂模式
与单例模式一样,工厂模式同样属于创建型设计模式的一种。单例模式用于保证一个类只有一个实例,而工厂模式则用于创建类型相关的不同对象,它同样具有不同的实现方式,具体可以细分为简单工厂、工厂方法、抽象工厂,分别适用于不同的场景。
简单工厂
我们项目中经常会做一些报警相关的功能,报警有可以分为短信报警、语音报警、邮箱报警等各种方式,接到这个需求之后我们使用在最简单的方式实现如下。
public class Service { public void doSomething() { // 省略业务代码... String type = ".."; Alert alert = new Alert(); if ("sms".equals(type)) { alert.sendSms(); } else if ("email".equals(type)) { alert.sendEmail(); } else if ("voice".equals(type)) { alert.sendVoice(); } // 省略业务代码 } } public class Alert { public void sendSms(){ } public void sendEmail(){ } public void sendVoice(){ } }
为了保证业务逻辑中不夹杂太多的报警代码,我们把不同类型报警的代码分别抽象到了报警类的方法中,但是每次新增一种报警类型仍需要新增一种方法,然后修改业务类,如果报警的逻辑比较复杂还是会导致报警类的代码量不断增加,为了解决报警类复杂的问题,我们可以为报警抽象出一个接口,改动后的代码如下。
public class Service { public void doSomething() { // 省略业务代码... String type = ".."; Alert alert = null; if ("sms".equals(type)) { alert = new SmsAlert(); } else if ("email".equals(type)) { alert = new EmailAlert(); } else if ("voice".equals(type)) { alert = new VoiceAlert(); } alert.send(); // 省略业务代码... } } public interface Alert { void send(); } public class SmsAlert implements Alert{ @Override public void send() { } } public class EmailAlert implements Alert{ @Override public void send() { } } public class VoiceAlert implements Alert{ @Override public void send() { } }
此时,如果增加新的报警类型,新增对应的子类然后修改业务代码即可。虽然我们已经解决了报警类代码复杂的问题,但创建报警实例的代码仍然耦合在业务代码中,如果类型比较多的话就会影响业务代码的可读性,为了让业务类的职责更单一、代码更清晰,我们可以很自然的可以将创建通知类的代码放到其他地方,修改后的代码如下。
public class Service { public void doSomething() { // 省略业务代码... String type = ".."; Alert alert = AlertFactory.getAlert(type); alert.send(); // 省略业务代码... } } public class AlertFactory { public static Alert getAlert(String type){ Alert alert = null; if ("sms".equals(type)) { alert = new SmsAlert(); } else if ("email".equals(type)) { alert = new EmailAlert(); } else if ("voice".equals(type)) { alert = new VoiceAlert(); } return alert; } }
重构后的 AlertFactory 就是一个工厂类,#getAlert 方法就是工厂方法,这种设计模式被称为简单工厂,由于创建类实例的方法是静态的,因此简单工厂又被称为静态工厂。可以看到软件的发展是不断演进的,设计模式的提出就是为了解决特定的问题。使用简单工厂之后,将创建实例的代码从业务代码中隔离出来,使业务类符合单一职责原则,增加了代码了可读性、扩展性。
如果实例化的资源消耗比较大,并且实例化的对象是可以复用的,还有另一种方法实现简单工厂。
public class AlertFactory { private static Map<String, Alert> cache = new HashMap<>(); static { cache.put("sms", new SmsAlert()); cache.put("email", new EmailAlert()); cache.put("voice", new VoiceAlert()); } public static Alert getAlert(String type) { Alert alert = cache.get(type); return alert; } }
工厂方法
对于上述的简单工厂来说,如果增加新的告警类型,仍需要对工厂类进行修改,如果改动不频繁的情况下稍微不满足开闭原则也是可以接受的。对于第一种简单工厂,如果一定要把 if 去掉那么可以利用多态的方式。
public interface AlertFactory { Alert getAlert(String type); } public class SmsAlertFactory implements AlertFactory{ @Override public Alert getAlert(String type) { return new SmsAlert(); } } public class EmailAlertFactory implements AlertFactory { @Override public Alert getAlert(String type) { return new EmailAlert(); } } public class VoiceAlertFactory implements AlertFactory{ @Override public Alert getAlert(String type) { return new VoiceAlert(); } }
我们通过将工厂类修改为接口,然后由每个具体的工厂来创建对象,这样如果增加新的报警类型就不需要对工厂类进行修改,相对简单工厂更符合开闭原则。这种工厂模式被成为工厂方法。
虽然增加新的报警类型工厂类不需要进行修改,但是使用方的代码确复杂了。
public class Service { public void doSomething() { // 省略业务代码... String type = ".."; Alert alert = null; if ("sms".equals(type)) { alert = new SmsAlertFactory().getAlert(type); } else if ("email".equals(type)) { alert = new EmailAlertFactory().getAlert(type); } else if ("voice".equals(type)) { alert = new VoiceAlertFactory().getAlert(type); } alert.send(); // 省略业务代码... } }
又回到了最开始的代码,为了解决创建工厂对象的问题,我们还需要为工厂再创建一个工厂。
public class Service { public void doSomething() { // 省略业务代码... String type = ".."; Alert alert = AlertFactoryMap.getAlertFactory(type).getAlert(type); alert.send(); // 省略业务代码... } } public class AlertFactoryMap { private static Map<String, AlertFactory> cache = new HashMap<>(); static { cache.put("sms", new SmsAlertFactory()); cache.put("email", new EmailAlertFactory()); cache.put("voice", new VoiceAlertFactory()); } public static AlertFactory getAlertFactory(String type) { return cache.get(type); } }
使用了工厂方法之后,项目中的类已经增加了很多,因此在大多数情况下,个人更推荐使用简单工厂。
抽象工厂
抽象工厂相对简单工厂、工厂方法来说,使用场景相对较少。对于上面报警的案例,我们需要配置不同的报警规则,如每分钟请求此时超过多少或者出现异常多少次进行报警;还可能分别将配置放在不同的地方,如本地文件、配置中心、zookpeer等;还可能使用不同的格式,如xml、json、properties、yaml等。如果我们还需要支持将配置放在不同的地方,这样就有不同的维度来对报警进行划分。支持本地文件和短信报警、支持配置中心和邮箱告警等等,组合方法就会越来越多,如果还使用简单工厂或工厂方法无法解决我们的问题,我们可以让我们的工厂同时支持多种创建多种维度的告警实例,具体如下。
public interface AlertFactory { Alert getLocalFileAlert(); Alert getNacosAlert(); } public class SmsAlertFactory implements AlertFactory{ @Override public Alert getLocalFileAlert() { return new SmsLocalFileAlert(); } @Override public Alert getNacosAlert() { return new SmsNacosAlert(); } } public class EmailAlertFactory implements AlertFactory { @Override public Alert getLocalFileAlert() { return new EmailLocalFileAlert(); } @Override public Alert getNacosAlert() { return new EmailNacosAlert(); } }
总结
工厂模式用于解决对象的创建问题。当使用 if else 创建相关类型的不同对象时可以改用工厂模式进行优化;当创建对象的逻辑比较复杂时同样可以将创建对象逻辑抽象到工厂中。使用工厂模式可以使类的职责更为清晰、封装创建对象的“变化”。