Spring 设计模式之工厂模式
在软件开发领域,设计模式 是解决常见问题的最佳实践。
Spring 框架作为 Java 生态中的佼佼者,其成功在很大程度上 归功于对设计模式的巧妙运用。
“Spring 中用到了哪些设计模式?”,这个问题,在 面试 中也比较常见,在此进行整理。
用到的场景
要基于 不同条件(大量的if-else或switch-case)创建不同对象的情况时。可以考虑使用工厂模式
一、简单工厂模式(静态工厂模式)
简单工厂模式又叫静态工厂方法模式,就是建立一个工厂类(是类的形式,而不是接口),对实现了同一接口的一些类进行实例的创建。
其核心是 由一个工厂类根据传入的参数,动态决定创建哪一个产品类的实例。
优点
- 责任分割:工厂类包含判断逻辑,决定何时创建哪个产品实例,客户端仅负责“消费”产品。
- 灵活性:根据外界信息决定创建哪个具体对象,客户端无需直接创建对象。
- 低耦合:外界与具体产品类隔离,降低耦合性。
- 结构优化:明确区分职责和权力,利于软件体系结构优化。
缺点
- 扩展困难:增加或删除产品需修改工厂逻辑,系统扩展性受限。
- 违背OCP:修改工厂逻辑可能导致过于复杂,违背开放-封闭原则(对新增开放,对修改关闭)。
- 无法继承:静态工厂方法使得工厂角色无法形成基于继承的等级结构。
Spring框架中的体现
举例说明
您走进了一家名为“万能奶茶店”的饮品店。这家店里只有一位店员(相当于工厂类),他负责制作店里所有种类的奶茶。当您告诉店员您想喝的奶茶类型(比如珍珠奶茶、抹茶拿铁或草莓奶茶)时,店员会根据您的要求为您精心制作一杯(即创建奶茶对象)。
然而,如果这家奶茶店想要推出新口味的奶茶(比如芒果奶茶),店员就需要学习新口味的制作方法,并且还要在菜单上添加这个新选项。这就意味着店员(即工厂类)的职责范围需要扩大,因此其代码也需要相应地进行修改,以支持这种新口味的奶茶。
这种模式下,每当有新产品加入时,都需要对工厂类进行修改,这可能会影响到系统的稳定性和可维护性。
Java代码实现
下面是一个使用简单工厂模式来实现上述场景的Java示例。
知识小贴士:
咖啡对象也可以用类来创建,但是如果是类的话,就必须把咖啡类定义为抽象类(public abstract class MilkTea )是为了强制子类实现必要的方法,防止直接实例化,以及(可选地)提供共享代码。
// 奶茶类,作为所有奶茶的基类
interface MilkTea {
// 制作奶茶的方法
public abstract void make();
}
// 珍珠奶茶类
class PearlMilkTea implements MilkTea {
@Override
public void make() {
System.out.println("制作珍珠奶茶");
}
}
// 抹茶拿铁类
class MatchaLatte implements MilkTea {
@Override
public void make() {
System.out.println("制作抹茶拿铁");
}
}
// 草莓奶茶类
class StrawberryMilkTea implements MilkTea {
@Override
public void make() {
System.out.println("制作草莓奶茶");
}
}
// 简单工厂类,负责创建所有种类的奶茶
class SimpleFactory {
// 根据奶茶类型创建对应的奶茶对象
public static MilkTea createMilkTea(String type) {
switch (type) {
case "pearl":
return new PearlMilkTea();
case "matcha":
return new MatchaLatte();
case "strawberry":
return new StrawberryMilkTea();
default:
throw new IllegalArgumentException("未知的奶茶类型");
}
}
}
// 客户端测试类
public class SimpleFactoryClient {
public static void main(String[] args) {
MilkTea tea1 = SimpleFactory.createMilkTea("pearl");
tea1.make(); // 输出:制作珍珠奶茶
MilkTea tea2 = SimpleFactory.createMilkTea("matcha");
tea2.make(); // 输出:制作抹茶拿铁
// 如果添加新口味的奶茶,比如芒果奶茶,需要修改SimpleFactory类
}
}
如果这家奶茶店想要推出新口味的奶茶(比如芒果奶茶),店员就需要学习新口味的制作方法,并且还要在菜单上添加这个新选项。这就意味着店员(即工厂类)的职责范围需要扩大,因此其代码也需要相应地进行修改,以支持这种新口味的奶茶。
// 新增芒果奶茶类
class MangoMilkTea implements MilkTea {
@Override
public void make() {
System.out.println("制作芒果奶茶");
}
}
// 简单工厂类,负责创建所有种类的奶茶(已修改以支持芒果奶茶)
class SimpleFactory {
// 根据奶茶类型创建对应的奶茶对象(已修改以支持芒果奶茶)
public static MilkTea createMilkTea(String type) {
switch (type) {
case "pearl":
return new PearlMilkTea();
case "matcha":
return new MatchaLatte();
case "strawberry":
return new StrawberryMilkTea();
case "mango": // 新增的芒果奶茶选项
return new MangoMilkTea();
default:
throw new IllegalArgumentException("未知的奶茶类型");
}
}
}
// 客户端测试类(可以添加测试芒果奶茶的代码)
public class SimpleFactoryClient {
public static void main(String[] args) {
MilkTea tea1 = SimpleFactory.createMilkTea("pearl");
tea1.make(); // 输出:制作珍珠奶茶
MilkTea tea2 = SimpleFactory.createMilkTea("matcha");
tea2.make(); // 输出:制作抹茶拿铁
MilkTea tea3 = SimpleFactory.createMilkTea("strawberry");
tea3.make(); // 输出:制作草莓奶茶
// 测试新口味的芒果奶茶
MilkTea tea4 = SimpleFactory.createMilkTea("mango");
tea4.make(); // 输出:制作芒果奶茶
}
}
如果 类存在@Autowired 的使用时,new 对象的操作就需要换成 ApplicationContext 去获取
// 珍珠奶茶类,现在是一个 Spring 组件
@Component
class PearlMilkTea implements MilkTea {
@Autowired
private PearMapper pearMapper;
@Override
public void make() {
// 使用 pearMapper 做一些事情(例如查询数据库)
pearMapper.select()
// ...
System.out.println("制作珍珠奶茶");
}
}
class SimpleFactory implements ApplicationContextAware {
ApplicationContext applicationContext;
// 根据奶茶类型创建对应的奶茶对象
public static MilkTea createMilkTea(String type) {
switch (type) {
case "pearl":
return applicationContext.getBean(PearlMilkTea.Class);
case "matcha":
return new MatchaLatte();
case "strawberry":
return new StrawberryMilkTea();
default:
throw new IllegalArgumentException("未知的奶茶类型");
}
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext=applicationContext;
}
}
二、工厂方法模式
工厂方法模式:定义了一个用于创建对象的接口,让子类决定实例化哪一个类。
工厂方法使一个类的实例化延迟到子类。
优点
- 单一职责:每个具体工厂类只负责创建一种产品,代码更加简洁。
- 扩展性强:完全满足开闭原则(OCP:对新增开放,对修改关闭),新增产品类时无需修改已有代码,只需新增具体产品类和对应工厂类。
缺点
- 增加开发量:每增加一个新产品,都需要新增一个具体产品类和一个对应的具体工厂类,增加了额外的开发工作量。
- 维护复杂:当需要修改多个产品类时,可能需要同时修改多个对应的工厂类,维护成本增加。
举例说明
您来到了一个奶茶一条街,这里有多家不同品牌的奶茶店,比如“A品牌奶茶店”和“B品牌奶茶店”, 奶茶店(品牌) 本身就相当于工厂。每家店都有自己的特色奶茶(比如珍珠奶茶、抹茶拿铁和草莓奶茶),并且每家店都有自己的店员(相当于具体工厂类)来负责制作这些奶茶。
您可以根据自己的喜好选择一家奶茶店,然后走进店里告诉店员您想喝的奶茶类型。
店员会根据自己店铺的特色为您制作一杯奶茶。
如果某家店想要推出新口味的奶茶(比如芒果奶茶),他们只需要在自己的店铺里添加这种新口味的奶茶,并培训店员掌握新的制作方法。
这样,其他店铺就不会受到影响,因为每家店都有自己的店员和独特的制作流程。
这种模式下,新增产品时只需要修改对应的具体工厂类,而无需修改工厂接口或现有的其他具体工厂类,从而提高了系统的扩展性和灵活性。
Java代码示例
// 奶茶接口
interface MilkTea {
void make();
}
// 珍珠奶茶类
class PearlMilkTeaImpl implements MilkTea {
@Override
public void make() {
System.out.println("制作珍珠奶茶");
}
}
// 抹茶拿铁类
class MatchaLatteImpl implements MilkTea {
@Override
public void make() {
System.out.println("制作抹茶拿铁");
}
}
// 草莓奶茶类
class StrawberryMilkTeaImpl implements MilkTea {
@Override
public void make() {
System.out.println("制作草莓奶茶");
}
}
// 工厂接口
interface MilkTeaFactory {
MilkTea createMilkTea();
}
// A品牌奶茶店工厂类
class BrandAMilkTeaFactory implements MilkTeaFactory {
@Override
public MilkTea createMilkTea() {
return new PearlMilkTeaImpl(); // 假设A品牌只卖珍珠奶茶
}
}
// B品牌奶茶店工厂类
class BrandBMilkTeaFactory implements MilkTeaFactory {
@Override
public MilkTea createMilkTea() {
return new MatchaLatteImpl(); // 假设B品牌只卖抹茶拿铁
}
}
// 客户端测试类
public class FactoryMethodClient {
public static void main(String[] args) {
MilkTeaFactory factoryA = new BrandAMilkTeaFactory();
MilkTea teaA = factoryA.createMilkTea();
teaA.make(); // 输出:制作珍珠奶茶
MilkTeaFactory factoryB = new BrandBMilkTeaFactory();
MilkTea teaB = factoryB.createMilkTea();
teaB.make(); // 输出:制作抹茶拿铁
// 如果A品牌想要推出新口味的奶茶,
// 比如芒果奶茶,只需要在BrandAMilkTeaFactory中添加新的实现
}
}
如果A品牌奶茶店想要推出新口味的奶茶,比如芒果奶茶
// 奶茶接口
interface MilkTea {
void make();
}
// 珍珠奶茶类
class PearlMilkTeaImpl implements MilkTea {
@Override
public void make() {
System.out.println("制作珍珠奶茶");
}
}
// 抹茶拿铁类
class MatchaLatteImpl implements MilkTea {
@Override
public void make() {
System.out.println("制作抹茶拿铁");
}
}
// 草莓奶茶类
class StrawberryMilkTeaImpl implements MilkTea {
@Override
public void make() {
System.out.println("制作草莓奶茶");
}
}
// 新增芒果奶茶类
class MangoMilkTeaImpl implements MilkTea {
@Override
public void make() {
System.out.println("制作芒果奶茶");
}
}
// 奶茶类型枚举
enum MilkTeaType {
PEARL,
MATCHA_LATTE,
STRAWBERRY,
MANGO // 新增芒果奶茶类型
}
// 工厂接口(稍作修改以支持类型参数)
interface MilkTeaFactory {
MilkTea createMilkTea(MilkTeaType type); // 引入类型参数
}
// A品牌奶茶店工厂类(修改以支持多种奶茶)
class BrandAMilkTeaFactory implements MilkTeaFactory {
@Override
public MilkTea createMilkTea(MilkTeaType type) {
switch (type) {
case PEARL:
return new PearlMilkTeaImpl();
case MANGO: // 新增对芒果奶茶的支持
return new MangoMilkTeaImpl();
default:
throw new IllegalArgumentException("Unsupported MilkTeaType: " + type);
}
}
}
// B品牌奶茶店工厂类(保持不变,但也可以按类似方式扩展)
class BrandBMilkTeaFactory implements MilkTeaFactory {
@Override
public MilkTea createMilkTea(MilkTeaType type) {
// 这里为了简单起见,我们只提供一种奶茶,但可以根据需要扩展
if (type == MilkTeaType.MATCHA_LATTE) {
return new MatchaLatteImpl();
} else {
throw new IllegalArgumentException("Unsupported MilkTeaType: " + type);
}
}
}
// 客户端测试类
public class FactoryMethodClient {
public static void main(String[] args) {
MilkTeaFactory factoryA = new BrandAMilkTeaFactory();
MilkTea teaA1 = factoryA.createMilkTea(MilkTeaType.PEARL);
teaA1.make(); // 输出:制作珍珠奶茶
MilkTea teaA2 = factoryA.createMilkTea(MilkTeaType.MANGO); // 新增对芒果奶茶的创建
teaA2.make(); // 输出:制作芒果奶茶
MilkTeaFactory factoryB = new BrandBMilkTeaFactory();
MilkTea teaB = factoryB.createMilkTea(MilkTeaType.MATCHA_LATTE);
teaB.make(); // 输出:制作抹茶拿铁
}
}
三、抽象工厂模式
定义了一个接口用于创建相关或依赖对象的家族,而无需明确指定具体类。
该模式是对工厂方法模式的进一步升级,工厂方法模式只生产一个等级的产品,而抽象工厂模式可生产多个等级的产品。
优点:
- 高内聚低耦合:抽象工厂模式将产品的创建与使用分离,降低了客户端代码与产品类之间的耦合度,同时产品族内的产品以高内聚的形式存在,有助于代码的维护和管理。
- 扩展性好:完全满足开闭原则(OCP:对新增开放,对修改关闭),当需要添加新的产品族时,只需要添加一个新的具体工厂类和相应的产品类,而不需要修改原有的客户端代码。
- 灵活性高:通过抽象工厂模式设计,系统可以更加灵活地应对需求的变化,新增组件或平台时,只需新增新的抽象组件和具体平台工厂,无需修改已有代码。
缺点:
- 增加复杂性:抽象工厂模式增加了系统的抽象性和复杂性,可能导致系统难以理解和维护。
- 扩展新产品困难:如果只需要添加新的产品而不是新的产品族,那么抽象工厂模式可能会变得笨拙,因为需要修改所有的工厂接口和工厂实现类。
举例说明
您来到了一个更加高级的奶茶广场,这里不仅有奶茶店,还有卖杯子、吸管等配件的店铺。而且,这些店铺都是连锁经营的,比如“热带风味奶茶店”和“经典风味奶茶店”,它们不仅提供奶茶,还提供与奶茶风格相匹配的杯子和吸管等配件。
您可以选择自己喜欢的奶茶店品牌,然后走进这家店。在这里,您不仅可以喝到他们特色的奶茶,还可以买到与奶茶风格相匹配的杯子和吸管等配件。这些产品都是一系列相互关联的,共同构成了一个产品族。
如果某家店想要推出新口味的奶茶或者新风格的杯子和吸管(比如蓝莓奶茶或陶瓷杯子),他们只需要在自己的品牌内部进行更新和调整。这样,您就可以在不同的品牌之间自由选择,并且每次都能得到一整套风格一致的奶茶及其配件。
这种模式下,新增产品族时只需要在对应的具体工厂类中添加新的产品创建方法,而无需修改抽象工厂接口或现有的其他具体工厂类(除非新增的产品引入了新的产品等级)。这进一步提高了系统的扩展性和灵活性。
Java代码示例
// 奶茶接口
interface MilkTea {
void make();
}
// 杯子接口
interface Cup {
void show();
}
// 珍珠奶茶类
class PearlMilkTeaImpl implements MilkTea {
@Override
public void make() {
System.out.println("制作珍珠奶茶");
}
}
// 抹茶拿铁类
class MatchaLatteImpl implements MilkTea {
@Override
public void make() {
System.out.println("制作抹茶拿铁");
}
}
// 塑料杯类
class PlasticCupImpl implements Cup {
@Override
public void show() {
System.out.println("这是塑料杯");
}
}
// 陶瓷杯类
class CeramicCupImpl implements Cup {
@Override
public void show() {
System.out.println("这是陶瓷杯");
}
}
// 抽象工厂接口
interface AbstractFactory {
MilkTea createMilkTea();
Cup createCup();
}
// 热带风味奶茶店工厂类
class TropicalFlavorFactory implements AbstractFactory {
@Override
public MilkTea createMilkTea() {
//制作珍珠奶茶
return new PearlMilkTeaImpl();
}
@Override
public Cup createCup() {
//塑料杯
return new PlasticCupImpl();
}
}
// 经典风味奶茶店工厂类
class ClassicFlavorFactory implements AbstractFactory {
@Override
public MilkTea createMilkTea() {
//制作抹茶拿铁
return new MatchaLatteImpl();
}
@Override
public Cup createCup() {
//陶瓷杯
return new CeramicCupImpl();
}
}
// 客户端测试类
public class AbstractFactoryClient {
public static void main(String[] args) {
AbstractFactory factory1 = new TropicalFlavorFactory();
MilkTea tea1 = factory1.createMilkTea();
Cup cup1 = factory1.createCup();
tea1.make(); // 输出:制作珍珠奶茶
cup1.show(); // 输出:这是塑料杯
AbstractFactory factory2 = new ClassicFlavorFactory();
MilkTea tea2 = factory2.createMilkTea();
Cup cup2 = factory2.createCup();
tea2.make(); // 输出:制作抹茶拿铁
cup2.show(); // 输出:这是陶瓷杯
// 如果热带风味奶茶店想要推出新口味的奶茶或新风格的杯子,
// 只需要在自己的工厂类中添加新的实现
}
}