• 带你认识工厂类设计模式——简单工厂&工厂方法&抽象工厂&简单抽象工厂&反射简单抽象工厂


    开始之前,我们需要了解三个知识点:
    1.开闭原则:是说软件实体(类,模块,函数等)应该可以扩展,但是不能修改。对应扩展是开放的,对于更改是封闭的。开闭原则是面向对象设计的核心所在,遵循这个原则可以带来面向对象技术所声称的巨大好处,也就是可维护,可扩展,可复用,灵活性好。然而,对于应用程序中每个部分都刻意进行抽象同样不是一个好主意,拒接不成熟的抽象和抽象本身一样重要。
    2.代码灵活性:需要可维护,可扩展,可复用三个特性,然后考虑实现跨平台,可移植性等。灵活性和可扩展性通常成对出现。
    3.单一职责:就一个类而言,应该仅有一个引起它变化的原因。如果一个类承担的职责越多,就等于把这些职责耦合在一起,一个职责的变化可能会削弱或抑制这个类完成其他职责的能力,这种耦合会导致脆弱的设计,当变化发生时,设计会遭受到意想不到的破坏。

    简单工厂模式

    简单工厂模式类图

    在这里插入图片描述
    抽象算法类:用于被具体的算法类继承。
    具体的算法类:继承了抽象算法类,根据具体的算法去重写抽象算法类中的方法。
    算法工厂类:返回一个具体的算法对象。

    简单工厂实现代码实现

    为了后序方便理解,我们就一直使用这个例子,只是不断添加需求而已。
    例子:买车 or 造车?现在我们需要一辆汽车代步。
    在未学习简单工厂之前,面对一个这个需求,我们应该怎么去做?
    直接写一个Car类,去实例化对象,或者写一个接口或抽象类,去实例它的实例化对象
    我们这使用第二种方式:

    //编写一个Car的接口
    public interface Car {
        //制造车
        public void carName();
    }
    //编写一个BYD去实现Car接口,重写它的方法
    public class BYD implements Car {
        @Override
        public void carName() {
            System.out.println("比亚迪汽车");
        }
    }
    
    //客户端代码
    public class test {
        public static void main(String[] args) {
            BYD car = new BYD();
            car.carName();
        }
    }
    输出结果:比亚迪汽车
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    简单快捷,那为啥还要去写什么简单工厂?因为使创建对象和使用对象相分离,就好像现在你是客户,你需要一辆汽车代步,你是自己做还是去4s店中购买一辆?(现实要是可以这么简单自己就能做出的话,我愿意自己做!)自己new 一个对象就好像制作一样,直接使用,不用去了解具体构造才符合我们嘛。那我们去使用简单工厂试试怎么写。

    //还是一个Car接口
    public interface Car {
        //制造车
        public void carName();
    }
    //编写一个BYD去实现Car接口,重写它的方法
    public class BYD implements Car {
        @Override
        public void carName() {
            System.out.println("比亚迪汽车");
        }
    }
    //编写一个车厂,从车厂中购买车
    public class CarFactory {
        public Car buy(String type) {
            Car car = new BYD();
            return car;
        }
    }
    //客户端代码
    public class test {
        public static void main(String[] args) {
            CarFactory carFactory = new CarFactory();
            Car car = carFactory.buy("BYD");
            car.carName();
        }
    }
    输出结果:比亚迪汽车
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    哎?确实不用去直接new 一个车对象了,但是我们不是还去new 了一个车厂类嘛?这和之前的有啥不同?别急,谁规定车厂只卖一种车的。

    //还是一个Car接口
    public interface Car {
        //制造车
        public void carName();
    }
    //编写一个BYD去实现Car接口,重写它的方法
    public class BYD implements Car {
        @Override
        public void carName() {
            System.out.println("比亚迪汽车");
        }
    }
    //再编写一个BENZ去实现Car接口,重写它的方法
    public class BENZ implements Car {
        @Override
        public void carName() {
            System.out.println("奔驰汽车");
        }
    }
    //编写一个车厂,从车厂中购买车
    public class CarFactory {
    
        public Car choice(String type) {
            Car car = null;
            switch (type) {
                case "BYD":
                    car = new BYD();
                    break;
                case "BENZ":
                    car = new BENZ();
                    break;
            }
            return car;
        }
    }
    //客户端代码
    public class test {
        public static void main(String[] args) {
            CarFactory carFactory = new CarFactory();
            Car car = carFactory.choice("BYD");
            car.carName();
            Car car1 = carFactory.choice("BENZ");
            car1.carName();
        }
    }
    输出结果:比亚迪汽车
    		 奔驰汽车
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47

    小结

    现在,是不是看出什么区别了,没错,当我们有多个对象被同一个父类修饰时,我们就可以去使用简单工厂模式去灵活的创建对象。但是它也不是没有弊端,我们可以灵活的去创建对象是因为我们使用了分支语句switch去做了逻辑判断。现在我们要再添加一个品牌的汽车,是不是再创建一个品牌类就行。但是我们要灵活的去创建对象的话就需要去修改switch语句了。这样我们就违背了一个设计模式的重要的原则——开闭原则。


    工厂方法模式

    简单工厂违背了开闭原则,所以工厂方法模式就诞生了。

    工厂方法模式类图

    在这里插入图片描述
    抽象父类:具体实现类所继承或实现的公共父类
    具体实现类:继承或实现公共父类
    工厂抽象类:为了符合开闭原则,定义的一个工厂抽象类
    子工厂类,每个指定的子工厂创建对应的对象

    工厂方法模式代码实现

    接着上面的例子:
    上面我们使用一个工厂创建所有对象,使用switch语句来使代码更加灵活。但是,如果新增一个产品势必会去修改switch语句,这就不符合开闭原则了。
    为此,我们需要学习工厂方法模式:
    多话不说,上代码!

    //一个公共接口
    public interface Car {
        public void carName();
    }
    //编写一个BYD去实现Car接口,重写它的方法
    public class BYD implements Car {
        @Override
        public void carName() {
            System.out.println("比亚迪汽车");
        }
    }
    //再编写一个BENZ去实现Car接口,重写它的方法
    public class BENZ implements Car {
        @Override
        public void carName() {
            System.out.println("奔驰汽车");
        }
    }
    //为了不去修改switch语句,我们直接不使用它了,改为提取抽象类,调用它的子类
    //所以我们抽象一个工厂类
    public interface Factory {
        public Car makeCar();
    }
    //再写一个具体的工厂类,这个类去生成BYD汽车
    public class BYDFactory implements Factory {
        @Override
        public Car makeCar() {
            return new BYD();
        }
    }
    //再再写一个具体的工厂类,这个类去生成BENZ汽车
    public class BYDFactory implements Factory {
        @Override
        public Car makeCar() {
            return new BYD();
        }
    }
    //客户端代码
    public class Customer {
        public static void main(String[] args) {
            Factory bydFactory = new BYDFactory();
            Car car1 = bydFactory.makeCar();
            car1.carName();
    
            Factory benzFactory = new BENZFactory();
            Car car2 = benzFactory.makeCar();
            car2.carName();
        }
    }
    输出结果:比亚迪汽车
    		 奔驰汽车
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51

    小结

    现在,我们如果需要新增加一个产品,那么我们只需要去新增加一个具体子类,新增一个对应的工厂类就行。现在的工厂方法类就符合开闭原则了。等等,好像它又不怎么灵活了,没错,当我们去买车时,确实不用去创建新车了,但是我们需要去创建工厂了。
    那么,都是去创建对象,他们之间的差距是什么?
    举个例子来说明一下:现在100个人,某公司提供每人一辆BYD汽车。
    如果不使用工厂,我们需要创建100个BYD对象。
    使用工厂模式,我们只需要创建一个工厂,让这些人去提车就行。虽然是通过方法去获取Car对象,好似没什么区别,但是,现在我们更改需求,BYD公司推出,现在有了BYD2.0,可以用BYD直接换BYD2.0。
    如果我们没有使用工厂,那么我们需要去修改100处的代码进行换车。
    但是如果我们使用了工厂方法,那么我们只需要去修改工厂中创建的对象就行了。这就是两者的差距。


    抽象工厂模式

    在只有一个产品类时,我们可以使用简单工厂或工厂方法去实现,但是如果产品类为两个或多个,使用简单工厂就无法满足单一职责。这时候就需要去抽象其工厂类,满足单一职责。为此,抽象工厂模式诞生。
    下面就让我们去了解了解抽象工厂。

    抽象工厂模式类图

    在这里插入图片描述
    抽象工厂类:抽象类或者接口,定义具体工厂类相同的方法
    具体的工厂类:实现或继承抽象工厂类,重写其方法。使用方法实例化对应的对象。
    抽象产品A/B:抽象类或者接口,定义该产品的属性和方法
    具体产品A/B:根据品牌创建对应的具体产品类

    抽象工厂模式代码实现

    例子:我们现在去买车,车的产品从原来的汽车变为现在可以选择购买电车或者油车。

    //抽象工厂类:
    public interface Factory {
        //生产电车
        Tram tram();
        //生产油车
        OilTank OIL_TANK();
    }
    //抽象产品类A
    public interface Tram {
        void manufacture();
    }
    //抽象产品B
    public interface OilTank {
        void manufacture();
    }
    //具体工厂
    //BENZ车厂
    public class BENZFactory implements Factory {
        @Override
        public Tram tram() {
            return new BENZTram();
        }
    
        @Override
        public OilTank OIL_TANK() {
            return new BENZOilTank();
        }
    }
    //BYD车厂
    public class BYDFactory implements Factory {
        @Override
        public Tram tram() {
            return new BYDTram();
        }
    
        @Override
        public OilTank OIL_TANK() {
            return new BYDOilTank();
        }
    }
    具体产品类:
    //BENZ电车
    public class BENZTram implements Tram {
        @Override
        public void manufacture() {
            System.out.println("奔驰电车");
        }
    }
    //BYD电车
    public class BYDTram implements Tram {
        @Override
        public void manufacture() {
            System.out.println("比亚迪电车");
        }
    }
    //BENZ油车
    public class BENZOilTank implements OilTank {
        @Override
        public void manufacture() {
            System.out.println("奔驰油车");
        }
    }
    //BYD油车
    public class BYDOilTank implements OilTank {
        @Override
        public void manufacture() {
            System.out.println("比亚迪油车");
        }
    }
    //客户端
    public class test {
        public static void main(String[] args) {
            FactoryBYDFactory = new BYDFactory();
            BYDFactory.OIL_TANK().manufacture();
            BYDFactory.tram().manufacture();
    
            FactoryBENZFactory = new BENZFactory();
            BENZFactory.OIL_TANK().manufacture();
            BENZFactory.tram().manufacture();
        }
    }
    输出结果:比亚迪油车
    	   	 比亚迪电车
    	 	 奔驰油车
    		 奔驰电车
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85

    小结:

    现在,我们可以去总结一下抽象工厂是否满足开头的三个原则:单一职责:不用说,我们就是因为这个才写的抽象工厂,那肯定是满足的。开闭原则:现在无论我们去添加一个新的品牌或者产品都是直接创建,去继承或实现对应抽象类或接口即可。所以开闭原则我们也是满足的。灵活性:现在我们客户想要去购买一辆车,好像确实不用去创建车了,想要什么品牌只要创建一个对应车厂就行。但是我们却要去了解车产具体的方法才行,不像简单工厂那样,我们只需要输入想要卖什么车就行。再说,我们无论是新增加一个品牌还是产品也好。这个要写的代码量好像有点多啊。最重要的一点,比如:某公司给每人配了一辆BYD,过了几年发现不是很好用,现在要换成BENZ,那么我们当初写的FactoryBYDFactory = new BYDFactory();就需要换成FactoryBENZFactory = new BENZFactory();之前写了100次就要改100遍。所以灵活性它不满足我们的需求。


    用简单工厂改进抽象工厂模式

    为了满足,多个产品下,还要足够灵活,我们可以去使用简单工厂去修改一下抽象工厂的方法。

    简单抽象工厂模式类图

    在这里插入图片描述
    具体的工厂:去根据方法创建不同的品牌的对象。根据参数的不同创建不同产品的对象。
    抽象品牌类A/B:品牌的商品的相同的方法。推荐使用接口。
    具体品牌A/B下的具体商品类:实现或继承抽象品牌类。

    简单抽象工厂模式代码实现

    看着这个类图,和上面简单工厂对比,不得了,简直一模一样。
    那就看看代码怎么样:

    //车厂
    public class CarFactory {
    
        private static final String car = "oilTank";
    //    private static final String car = "tram";
    
        public BENZ buyBENZ() {
            BENZ benz = null;
            switch (car) {
                case "oilTank":
                    benz = new BENZOilTank();
                    break;
                case "tram":
                    benz = new BENZTram();
                    break;
            }
            return benz;
        }
        public BYD buyBYD(){
            BYD byd = null;
            switch (car) {
                case "oilTank":
                    byd = new BYDOilTank();
                    break;
                case "tram":
                    byd = new BYDTram();
                    break;
            }
            return byd;
        }
    }
    //品牌接口:
    public interface BENZ {
        void manufacture();
    }
    public interface BYD {
        void manufacture();
    }
    //具体产品类:
    public class BENZOilTank implements BENZ {
        @Override
        public void manufacture() {
            System.out.println("奔驰油车");
        }
    }
    public class BENZTram implements BENZ {
        @Override
        public void manufacture() {
            System.out.println("奔驰电车");
        }
    }
    public class BYDOilTank implements BYD {
        @Override
        public void manufacture() {
            System.out.println("比亚迪油车");
        }
    }
    public class BYDTram implements BYD {
        @Override
        public void manufacture() {
            System.out.println("比亚迪电车");
        }
    }
    //客户端:
    public class test {
        public static void main(String[] args) {
            CarFactory car = new CarFactory();
            car.buyBENZ().manufacture();
            car.buyBYD().manufacture();
        }
    }
    输出结果:奔驰油车
    		 比亚迪油车
    当我们参数变为private static final String car = "tram";时
    输出结果:奔驰电车
    		 比亚迪电车
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76

    小结:

    和抽象工厂一比,这代码量就少了好多。而且我要什么车就修改参数就行,或者不设参数,设立get/set方法直接让客户端输入。再在车厂设一个判断语句去判断调用那个方法创建对象。但是看到这有些帅哥美女就发现了问题了,这TM不是有学回去了吗?没错,它由简单工厂改进,也就继承了简单工厂的好处,但是也继承了坏处,唯一剩下属于抽象工厂的也就是可以处理多产品这个优势了
    在这里插入图片描述
    咋们看下来,发现是不是和图上的一模一样,这就是个轮回嘛,到最终都没有解决这个开闭原则和灵活性的问题(请先忽略反射+配置文件+简单抽象工厂,给二哥个面子)。问题又回到最初的起点:怎么解决switch语句,不使用分支语句去判断创建对象呢?
    那就是使用反射+配置文件去替代分支语句。


    反射+配置文件版简单抽象工厂模式

    使用反射和配置文件不熟悉的帅哥美女可以去了解一下,这我就不画类图了,就是上面的类图加一个品牌抽象类的父类车类和一个读取和写入配置文件类。

    反射+配置文件版简单抽象工厂模式代码

    具体代码实现如下:
    为满足灵活性,根据客户端用户输入的数据去创建对应的对象,所以在车厂添加了车的品牌和类型属性,为了读取配置文件所以添加了config属性。

    //车厂
    public class CarFactory {
    
        private Config config = new Config();//写入和读取配置文件对象
        private String brand;//车的品牌
        private String type;//车的类型
        public CarFactory(String brand, String type) {
            this.brand = brand;
            this.type = type;
            config.write(brand,type);
            config.read();
        }
        public Car car(){
            String s = config.getProp().getProperty(brand+type);
            Car car = null;
            try {
                car = (Car) Class.forName(s).newInstance();
            }catch (Exception e){
                e.printStackTrace();
            }
            return car;
        }
    }
    //写入和读取配置文件类
    public class Config {
    
        private Properties prop = new Properties();
        //写入配置文件方法
        public void write(String brand, String type){
            //因为我们通过反射去创建对象,需要的是被创建对象的全路径,所以命名很重要
            prop.setProperty(brand+type,"com.factory.reflexAbstracFactory."+brand+type);
            try {
                FileWriter fw = new FileWriter("src/car.properties");
                prop.store(fw,null);
            }catch (Exception e){
                e.printStackTrace();
            }
        }
        //读取配置文件
        public void read(){
            try {
                InputStream is = test.class.getClassLoader().getResourceAsStream("car.properties");
                prop.load(is);
            }catch (Exception e){
                e.printStackTrace();
            }
        }
        public Properties getProp() {
            return prop;
        }
        public void setProp(Properties prop) {
            this.prop = prop;
        }
    }
    //接口:
    //品牌接口的父接口:为了方便在车厂获取对象
    public interface Car {
        public void manufacture();
    }
    //品牌接口:
    public interface BYD extends Car {
        public void manufacture();
    }
    public interface BENZ extends Car  {
        public void manufacture();
    }
    //具体产品类:
    public class BENZOilTank implements BENZ {
        @Override
        public void manufacture() {
            System.out.println("奔驰油车");
        }
    }
    public class BENZTram implements BENZ {
        @Override
        public void manufacture() {
            System.out.println("奔驰电车");
        }
    }
    public class BYDOilTank implements BYD {
        @Override
        public void manufacture() {
            System.out.println("比亚迪油车");
        }
    }
    public class BYDTram implements BYD {
        @Override
        public void manufacture() {
            System.out.println("比亚迪电车");
        }
    }
    //客户端
    public class test {
        public static void main(String[] args) {
    
            System.out.println("请输入你要购买的汽车品牌");
            Scanner sc = new Scanner(System.in);
            String brand = sc.nextLine();
            System.out.println("请输入你要购买的汽车类型");
            String type = sc.nextLine();
            CarFactory carFactory = new CarFactory(brand,type);
    
            Car car = carFactory.car();
            car.manufacture();
        }
    }
    输出结果:
    请输入你要购买的汽车品牌
    BYD
    请输入你要购买的汽车类型
    Tram
    比亚迪电车
    //配置文件
    BYDTram=com.factory.reflexAbstracFactory.BYDTram
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114

    总结

    现在,反射+配置文件+简单抽象工厂,灵活性不用说:可以随客户的需求更改随时切换,满足单一职责:为了满足单一职责,我们都把读取和写入配置文件都独立出来了,所以也满足开闭原则:我们做这个就是为了解决开闭原则的,现在没有switch语句了,所以当然也是满足的。
    好了,兄弟们,工厂模式就学完了。但是有一点我们要注意:学习设计模式是为了我们代码具备高维护、高复用等等特性,而不是一昧的滥用。从上面代码也可以看出有些地方不是很合理,我只是为了让大家更好了解,所以一直只使用买车这个例子。在我们实际开发中应该去深度了解代码逻辑需要使用些什么模式,而不是乱用。好了,工厂模式学完了,祝大家度过美好快乐的一天!


  • 相关阅读:
    python笔记--列表、字典、元组和集合
    2022 年杭电多校第七场补题记录
    【车载开发系列】CAPL语言事件类型概述
    Springboot2.0踩得坑(embeddedservletcontainercustomizer)
    linux新建账号并配置权限
    数字时钟制作
    【MySQL系列】 MySQL表的增删改查(进阶)
    HTML 与 CSS 有什么区别?
    Windows窗体程序开启console(cmd命令行)
    测试 SAP 电商云 Spartacus UI 3.4.x 和 4.3.x 的 guest checkout 功能
  • 原文地址:https://blog.csdn.net/m0_63217468/article/details/127909818