讲个故事,看看能不能理解工厂方法模式
工厂方法模式
前言
今天想聊一下老生常谈的一种模式,工厂方法模式。另外本文中会出现大量的代码,不过都很简单容易理解。
下面两张图,是本文中可能会用到的。感兴趣的可以看一下。希望大家可以看完本文,喜欢的话可以点个赞支持下。
常见的设计原则
工厂模式的种类
讲个故事
这几年新能源车比较流行。特斯拉,比亚迪,五菱等车企都在发展新能源车。汽车嘛,品牌不同,配置不同,价格也不太一样。毕业生小白就职于一家汽车媒体公司,一天组长把他叫来,让他收集一下这些车企新能源车的相关信息。今天的故事就从这里开始了。
正常的写法
收到任务后的小白心想,这个任务太简单了,没有任何难度。于是他立刻开始动手,因此有了下面这份代码。
产品类
// 比亚迪类
class BYD{
public void name() {
System.out.println("比亚迪");
}
public void price() {
System.out.println("22万");
}
public void kind() {
System.out.println("新能源");
}
}
// 特斯拉类
class TSLA{
public void name() {
System.out.println("特斯拉");
}
public void price() {
System.out.println("32万");
}
public void kind() {
System.out.println("新能源汽车");
}
}
客户端类
// 客户端调用代码
class Client {
public static void main(String[] args) {
BYD byd = new BYD();
TSLA tsla = new TSLA();
}
}
目前看着还算不错,两个品牌都有了自己的对象。于是他立刻把代码交给组长看。
普通写法遇到的问题
组长看过之后,对他说: 现在来说,只有两个品牌还好,但是后面的业务肯定还要发展,品牌肯定不止一个。如果Client中有10个汽车品牌,100个汽车品牌该怎么办?难道一个Client类要和100个汽车品牌的类都有关联吗?迪米特法则——最少知道原则,难道你没听过吗?一旦100个类中的一个类进行修改,就可能导致Client类出现问题! 你去对比下设计原则,看下问题出在哪儿!!!
简单工厂模式的写法
小白,仔细看了一遍代码,发现现在这种写法的确是耦合过于严重。Client类会和每个用到的汽车产品类产生关联。不过好在可以用依赖反转 + 封装的方式修改原有代码,解决这个问题。
- 将汽车都有的属性或者方法方法抽离到汽车接口中
- 所有的汽车产品都实现汽车接口。这样可以通过多态的特性,将Client类与汽车产品之间进行解耦。
- 将所有对象的创建过程封装到一个管理类中,这个类可以叫做Manager或者Factory。
简单工厂模式的代码
1.将公共的方法抽离到接口中
/**
* @author jtl
* @date 2021/7/20 16:18
*/
interface Car {
void name();
void price();
void kind();
}
2.汽车品牌实现该接口
class BYD implements Car {
@Override
public void name() {
System.out.println("比亚迪");
}
@Override
public void price() {
System.out.println("22万");
}
@Override
public void kind() {
System.out.println("新能源");
}
}
class BMW implements Car{
@Override
public void name() {
System.out.println("宝马");
}
@Override
public void price() {
System.out.println("40万");
}
@Override
public void kind() {
System.out.println("燃油车");
}
}
class Tesla implements Car {
@Override
public void name() {
System.out.println("特斯拉");
}
@Override
public void price() {
System.out.println("32万");
}
@Override
public void kind() {
System.out.println("新能源汽车");
}
}
3.汽车的管理工厂类
class CarFactory {
public static Car getCar(String name) {
Car car =null ;
switch (name){
case "宝马":
car = new BMW();
break;
case "特斯拉":
car = new TSLA();
break;
case "比亚迪":
car = new BYD();
break;
default:
break;
}
return car;
}
}
4.客户端调用
class Client {
public static void main(String[] args) {
Car car = CarFactory.getCar("比亚迪");
Car car = CarFactory.getCar("特斯拉");
Car car = CarFactory.getCar("宝马");
}
}
简单工厂遇到的问题
通过简单工厂模式,让Client类只和Car还有CarFactory打交道,降低了耦合度,哪怕出现1000个汽车品牌,也都和Client类没有关系了。
但是随之而来的是一个新的问题。每次要新增汽车品牌,都要修改Factory类中的方法。这可是违反了设计原则中的最关键的一条原则之一,开闭原则,即对扩展开放对修改关闭。
而且虽然Client类耦合度下来了,但是所有的汽车类都和Factory类有了关联。这依旧违反迪米特法则——最少知道原则。
工厂方法模式
正在小白一筹莫展的时候,组长走了过来对他说。你能想到这一步已经很出色了,我这里有一份代码已经上传到GitLab上,里面用到了工厂方法模式。你拉取一下看看写法。随后不等小白说谢就转身离开了。小白把代码拉下来之后就看到了下面的代码。
正常方式实现的工厂方法
抽象产品代码
/**
* Author(作者):jtl
* Date(日期):2023/2/8 14:17
* Detail(详情):抽象产品接口
*/
public interface ICar {
void name();
void price();
}
实际产品代码
/**
* Author(作者):jtl
* Date(日期):2023/2/8 14:20
* Detail(详情):具体产品类----比亚迪电动车
*/
public class BYD implements ICar{
@Override
public void name() {
System.out.println("比亚迪新能源电动车");
}
@Override
public void price() {
System.out.println("20W");
}
}
/**
* Author(作者):jtl
* Date(日期):2023/2/8 14:21
* Detail(详情):具体产品类---特斯拉电动车
*/
public class TSLA implements ICar{
@Override
public void name() {
System.out.println("特斯拉新能源电动车");
}
@Override
public void price() {
System.out.println("28W");
}
}
抽象工厂代码
/**
* Author(作者):jtl
* Date(日期):2023/2/8 14:18
* Detail(详情):抽象工厂接口
*/
public interface IFactory {
ICar buildCar();
}
实际工厂代码
/**
* Author(作者):jtl
* Date(日期):2023/2/8 14:23
* Detail(详情):具体工厂类---比亚迪新能源工厂
*/
public class BYDFactory implements IFactory{
@Override
public ICar buildCar() {
return new BYD();
}
}
/**
* Author(作者):jtl
* Date(日期):2023/2/8 14:24
* Detail(详情):具体工厂类---特斯拉上海工厂
*/
public class TSLAFactory implements IFactory{
@Override
public ICar buildCar() {
return new TSLA();
}
}
客户端调用代码
/**
* Author(作者):jtl
* Date(日期):2023/2/8 14:24
* Detail(详情):工厂方法模式客户端
*/
public class Client {
public static void main(String[] args) {
IFactory bydFactory = new BYDFactory();
bydFactory.buildCar().name();
IFactory tslaFactory = new TSLAFactory();
tslaFactory.buildCar().name();
}
}
为了解决,简单工厂模式违反开闭原则和迪米特法则——最少知道原则。组长对简单工厂进行了修改。
- 将汽车共有的属性或者方法抽离到**汽车接口(ICar)**中
- 所有的汽车产品(TSLA,BYD等) 都实现汽车接口(ICar)。这样可以通过多态的特性,进行Client类与汽车产品之间的解耦。
- 将创建汽车产品TSLA,BYD等) 的功能进行抽离封装到IFactory工厂接口中
- 每一个汽车产品(TSLA,BYD等) 都有自己的实现了IFactory接口的汽车工厂(TSLAFactory,BYDFactory等)
新的问题
虽然说,工厂方法模式解决了简单工厂中Factory类和各种汽车产品类耦合度过高的问题。而且再也不用,每次新增一个汽车产品就去简单工厂中修改代码了。
但是工厂方法模式有一个致命的问题就是,每新增一个汽车品牌就要增加一个汽车产品和汽车工厂。今天新增个五菱的汽车和五菱工厂,明天再新增个理想的汽车和理想的工厂。新增1000个汽车品牌就要新增1000个汽车和工厂。这样下去class的数量就会过多。这是一个不可忽视的问题。
通过反射实现的工厂方法
小白把他想到的问题,一五一十的告诉了组长。组长十分安慰的看着小白说,你能想到这个问题很好,说明你进步了。工厂方法模式的确会出现这样的问题。因此我用反射写了一份工厂方法模式的代码,虽然说和汽车没关系,但是你可以类比一下。毕竟不能什么都和汽车有关系啊!
抽象工厂代码
对应IFactory
public abstract class Factory {
public abstract <T extends Product> T createProduct(Class<T> clazz);
}
抽象产品代码
对应ICar
public abstract class Product {
abstract void method();
}
具体工厂代码
对应BYDFactory等汽车工厂
public class ConcreteFactory extends Factory{
// 通过反射的方式创建具体工厂,解决了,一个具体工厂对应一个具体产品的问题
@Override
public <T extends Product> T createProduct(Class<T> clazz) {
Product product = null;
try {
product = (Product) Class.forName(clazz.getName()).newInstance();
}catch (Exception e){
e.printStackTrace();
}
return (T) product;
}
}
具体产品代码
对应BYD,TSLA等汽车产品
public class ConcreteProductA extends Product{
@Override
void method() {
System.out.println("具体产品A");
}
}
public class ConcreteProductB extends Product{
@Override
void method() {
System.out.println("具体产品B");
}
}
客户端代码实现
public class Client {
public static void main(String[] args) {
Factory factory = new ConcreteFactory();
Product productA = factory.createProduct(ConcreteProductA.class);
Product productB = factory.createProduct(ConcreteProductB.class);
productA.method();//具体产品A
productB.method();//具体产品B
}
}
反射工厂方法中的妥协
通过反射的方式,解决了工厂方法模式中,过多的产品导致的产品工厂过多的问题。但是,Client类又和产品类有了一些耦合。只能说,没有十全十美的设计模式,只有合不合适。
枯燥无味的定义
听完了故事,就来看一下,官方说法的定义和使用场景吧,
相关定义
工厂方法模式分为四个组成部分,抽象工厂,抽象产品,具体工厂,具体产品。
工厂方法模式属于创建型设计模式。是为了将对象的创建与Client类解耦。
工厂会返回对象给Client,Client不知道创建的具体细节。
使用场景
1. 有大量相同属性的同类型对象。
2. 需要将这些对象的创建对客户端进行解耦。
使用步骤
1. 将具有相同特征的对象的共同特征进行提取。生成抽象产品类。
2. 生成继承抽象产品的具体产品。
3. 创建抽象工厂,生成返回抽象产品的抽象方法。
4. 生成创建具体产品的具体工厂,该工厂继承抽象工厂。重写抽象方法,返回具体产品的对象。
注意事项
- 优点,将对象的创建与客户端代码分离实现解耦。
- 缺点,每新增一个种类就要新增一个抽象产品和抽象工厂。
- 提升,可以用反射的方式实现具体工厂,而不是每有一个具体产品就创建一个与之相对应的具体工厂。可以减少代码量。
总结
至此,工厂方法模式就讲完了。简单工厂可以看作是工厂方法模式的一种。如果是简单的创建同类型对象的话,简单工厂模式就可以胜任了。但是如果是大的项目,还是推荐使用工厂方法模式进行解耦。至于是使用正常的工厂方法,还是反射的工厂方法,那就完全看个人的选择了。毕竟,只有合适才是最好的!
另外,工厂方法模式的使用前提是,大量的同类型的对象的创建,这点可千万别记错了。
转载自:https://juejin.cn/post/7197813134991491132