简单工厂、工厂方法、抽象工厂模式

1 工厂模式的应用场景

工厂模式主要是为了在不同的条件下创建不同的对象,以植物大战僵尸为例:有三种对象,当创建完对象后,在Function中根据接收到的名字调用不同的对象。

三种对象:

public class Bean {
    public void fight(){
        System.out.println( "绿豆fight");
    }
}
public class Ice {
    public void fight(){
        System.out.println( "冰豆fight");
    }
}
public class Wall {
    public void fight(){
        System.out.println( "果墙fight");
    }
}

Function类:

public class function {
    public void fight(String name){
        if ("Bean".equals(name)){
            Bean bean = new Bean();
            bean.fight();
        }else if ("Ice".equals(name)){
            Ice ice = new Ice();
            ice.fight();
        }else {
            Wall wall = new Wall();
            wall.fight();
        }
    }
}

main类:在main类中输入不同的名字就会调用对象的不同方法。

public class main {
    public static void main(String[] args) {
        new function().fight("Ice");
    }
}

 采用上面的代码完成了基本要求,但对Function类进行分析每次都要有调用一个fight方法,能不能进行简化哪?可以使用接口,构造一个Plant接口

public interface Plant {
    public void fight();
}

这三种对象全部继承该接口,那么就可以利用一个接口可以具有多种具体实现的特性。

Bean类为例:

public class Bean implements  Plant{
    @Override
    public void fight(){
        System.out.println( "绿豆fight");
    }
}

function类:

public class function {
    public void fight(String name){
        Plant plant = null;
        if ("Bean".equals(name)){
            plant = new Bean();
        }else if ("Ice".equals(name)){
            plant = new Ice();
        }else {
            plant = new Wall();
        }
        plant.fight();
    }
}

 以上程序能满足基本要求,在function类中采用了向上转型这样可以提高代码的简洁性但有一点要注意,向上转型后只能使用父类和子类都共有的方法,子类所特有的便不能使用了。除此之外仍有两点要注意:(1)如果function这样的类不止一个,每次增加一个新的场景function这样类就增加一次,那么就意味着我需要在每个这样的类中都要有上面的if else语句这样表明代码的复用性很低,(2)Bean、Ice这样的名字很容易就写错了,如果多个类中都有那么就会增加写错的几率。针对这两个问题解决方法就是简单工厂模式。

2 简单工厂模式

针对(1)创建一个工厂类,通过name创建对象,其实就是把function中的拷贝了一下

public class SimpleFactory {
    public static Plant createPlant(String name){
        Plant plant = null;
        if ("Bean".equals(name)){
            plant = new Bean();
        }else if ("Ice".equals(name)){
            plant = new Ice();
        }else {
            plant = new Wall();
        }
        return plant;
    }
}

function类:这样对该类进行其他操作就方便了很多

public class function {
    public void fight(String name){
        Plant plant = SimpleFactory.createPlant("name");
        plant.fight();
    }
}

针对(2)可以创建一个常量类PlantNameConstant

public class PlantNameConstant {
    /**
     * 绿逗的名字
     */
    public static final String BEAN_NAME = "Bean";
    /**
     * 蓝冰的名字
     */
    public static final String ICE_NAME = "Ice";
    /**
     * 果墙的名字
     */
    public static final String WALL_NAME = "Wall";
}

这样优化后工厂类便可以进一步改进

public class SimpleFactory {
    public static Plant createPlant(String name){
        Plant plant = null;
        if (PlantNameConstant.BEAN_NAME.equals(name)){
            plant = new Bean();
        }else if (PlantNameConstant.ICE_NAME.equals(name)){
            plant = new Ice();
        }else {
            plant = new Wall();
        }
        return plant;
    }
} 

那么在调用时就方便很多了:

public class function {
    public void fight(String name) throws Exception {
        Plant plant = SimpleFactory.createPlant(name);
        plant.fight();
    }
}
public class main {
    public static void main(String[] args) throws Exception {
        new function().fight(PlantNameConstant.BEAN_NAME);
    }
}

目前简单工厂模式已经基本建立好了,但在SimpleFactory类中可以发先if else太多,如果对象多时这样一个个修改也不是一个好办,有没有方法可以使代码变的更好,那就是利用反射机制。

在常量中不再是定义名字,而是直接获取类的全部名字。

public class PlantNameConstant {
    /**
     * 绿逗的名字
     */
    public static final String BEAN_NAME = "cn.uestc.reflect.plant.Bean";
    /**
     * 蓝冰的名字
     */
    public static final String ICE_NAME = "cn.uestc.reflect.plant.Ice";
    /**
     * 果墙的名字
     */
    public static final String WALL_NAME = "cn.uestc.reflect.plant.Wall";
}

这样的话利用反射机制可以通过字节码文件来获取类的对象并实例化

public class SimpleFactory {
    public static Plant createPlant(String name) throws Exception {
        return (Plant)Class.forName(name).newInstance();
    }
}

众多的if else语句现在只需要用一行代码便可以了,目前算是完整实现了一个简单工厂模式。整个思路如下图所示:

 3 抽象工厂

在前面Bean、Ice、Wall对象中都只有一个简单的fight方法,这样在比较复杂的项目中肯定是不太现实的,现在可以考虑更为复杂的情况。考虑对象造型有头发、武器、外形这些对象,之所以不把这些作为属性因为头发有颜色、形状这些,认为其为对象,那么后续的扩展变回更加方便。以Bean为例,那么就需要多个对象的使用。

public class Bean implements Plant{

    private Hair hair;

    private Arms arms;

    public String getName() {
        return "绿逗";
    }

    public void fight() {
        System.out.println("发射一颗豆子");
    }

    public Hair getHair() {
        return hair;
    }

    public void setHair(Hair hair) {
        this.hair = hair;
    }

    public Arms getArms() {
        return arms;
    }

    public void setArms(Arms arms) {
        this.arms = arms;
    }
}

这样如果还采用简单工厂模式由于向上转型只能使用父类和子类共有的对子类特有的无法使用,那么就无法更为精确的设计头发和武器的样式了。这样肯定是不合适的。既然简单工厂无法实现那么考虑一下普通的方式如何实现:

public static Plant createPlant(String name) {
        Plant plant = null;
        if(PlantNameConstant.BEAN_NAME.equals(name)) {
            Bean bean = new Bean();
            Hair hair = new Hair();
            hair.setColor("绿色");
            bean.setHair(hair);
            Arms arms = new Arms();
            arms.setBulletType("普通的豆子");
            bean.setArms(arms);
            plant = bean;
        } else if (PlantNameConstant.ICE_NAME.equals(name)) {
            Ice ice = new Ice();
            Hair hair = new Hair();
            hair.setColor("蓝色");
            ice.setHair(hair);
            Arms arms = new Arms();
            arms.setBulletType("冰冻的豆子");
            ice.setArms(arms);
            plant = ice;
        } else if (PlantNameConstant.WALL_NAME.equals(name)) {
            Wall wall = new Wall();
            Shell shell = new Shell();
            shell.setHardness(5);
            wall.setShell(shell);
            plant = wall;
        } else if ("new_Wall".equals(name)) {
            Wall wall = new Wall();
            Shell shell = new Shell();
            shell.setHardness(10);
            wall.setShell(shell);
            plant = wall;
        }
        return plant;
    }

从上面的代码可以直观的感受到代码的复用性很差,重复代码较多,如果换一个新场景代码要整个复制粘贴修改,这样肯定是不合适。那么此时就应该采用抽象工厂模式。其思路为抽象工厂中生产的不再是对象而是工厂,如BeanFactory,再从生产出来的工厂中创建对象。

抽象工厂:

public interface Factory {
    public Plant createPlant();
}

抽象工厂生产工厂:重点在于下面的一行代码和上面的利用反射机制节省if else一样。

public class  FactoryBuilder {
    public static Factory build(String name) {
        Factory factory = null;
        if(PlantNameConstant.BEAN_NAME.equals(name)) {
            factory = new BeanFactory();
        } else if (PlantNameConstant.ICE_NAME.equals(name)) {
            factory = new IceFactory();
        } else if (PlantNameConstant.WALL_NAME.equals(name)) {
            factory = new WallFactory();
        }
        return factory;
    }

    public static Factory buildByClassName(String name) throws Exception {
        return (Factory)Class.forName(name).newInstance();
    }
}

每一个子工厂各自负责生产对象:

BeanFactory:

public class BeanFactory implements Factory {
    public Plant createPlant() {
        Bean bean = new Bean();
        Hair hair = new Hair();
        hair.setColor("绿色");
        bean.setHair(hair);
        Arms arms = new Arms();
        arms.setBulletType("普通的豆子");
        bean.setArms(arms);
        return bean;
    }
}

IceFactory:

public class IceFactory implements Factory {
    public Plant createPlant() {
        Ice ice = new Ice();
        Hair hair = new Hair();
        hair.setColor("蓝色");
        ice.setHair(hair);
        Arms arms = new Arms();
        arms.setBulletType("冰冻的豆子");
        ice.setArms(arms);
        return ice;
    }
}

那么function类中:

public class Function {
    public void put(String name,int number) throws Exception {
        //创建哪一种工厂
        Factory factory = FactoryBuilder.buildByClassName(name);
        //由相应的工厂创建植物
        Plant plant = factory.createPlant();
        plant.fight();
    }
}
本质:工厂方法是选择单个产品的实现,虽然一个类里面可以有多个工厂方法,但是这些方法之间一般是没有联系的,即使看起来像有联系。但是抽象工厂着重的就是为一个产品簇选择实现,定义在抽象工厂里面的方法通常是有联系的,它们都是产品的某一部分或者是相互依赖的。如果抽象工厂里面只定义个方法,直接创建产品,那么就退化成为工厂方法了。

使用场景:如果希望一个系统独立于它的产品的创建、组合和表示的时候。换句话说,希望个系统只是知道产品的接口,而不关心实现的时候。如果一个系统要由多个产品系列中的一个来配置的时候。换句话说,就是可以动态地切换产品族的时候如果要强调一系列相关产品的接口,以便联合使用它们的时候。

4 工厂方法

其实在抽象工厂代码中已经运用了工厂方法模式,工厂方法:先定义一个接口,然后让实现这个接口的类决定实例化哪一个类。但要注意工厂方法因为创建一个类就需要先创建这个类所对应的工厂因此可能会导致的类的数目过多增加程序的复杂度,因此也要适度运用。一个简单例子为例,如下图所:

在Test类中调用时采用如下代码,由具体的子类PythonVideoFactory来生产具体的Video对象:PythonVideo,然后利用多态,调用其Produce方法。其应用层代码如下:

public class Test {
    public static void main(String[] args) {

        VideoFactory videoFactory = new PythonVideoFactory();
        VideoFactory videoFactory2 = new JavaVideoFactory();
        VideoFactory videoFactory3 = new FEVideoFactory();

        Video video = videoFactory.getVideo();
        Video video2 = videoFactory.getVideo();
        Video video3 = videoFactory.getVideo();

        video.produce();
        video2.produce();
        video3.produce();
    }
}

定义:定义一个用于创建对象的接口,让子类决定实例化哪一个类,工厂方法使一个类的实例化延迟到子类

工厂方法的本质:延迟到子类来选择实现。

优点:工厂方法模式很好地体现了“依赖倒置原则”。依赖倒置原则告诉我们“要依赖抽象,不要依赖于具体类”,简单点说就是:不能让高层组件依赖于低层组件,而且不管高层组件还是低层组件,都应该依赖于抽象。

5 源代码中的使用

(1)在JDK中的使用:

在Calendar类中对象实例化时便使用了简单工厂模式。其类图关系如下所示:

从图中可以看出Calendar为抽象类,实现了三个接口,有两个类直接继承Calendar类一个类间接继承,这三个类均是创建对象的类。

//Calendar的实例化方法
    public static Calendar getInstance()
    {
        return createCalendar(TimeZone.getDefault(), Locale.getDefault(Locale.Category.FORMAT));
    }

    private static Calendar createCalendar(TimeZone zone, Locale aLocale)
    {
        //在if中根据不同的情景选择是使用不同的构造方法创建出一个对象,这里便运用了简单工厂方法
        if (aLocale.hasExtensions()) {
            String caltype = aLocale.getUnicodeLocaleType("ca");
            if (caltype != null) {
                switch (caltype) {
                    case "buddhist":
                        cal = new BuddhistCalendar(zone, aLocale);
                        break;
                    case "japanese":
                        cal = new JapaneseImperialCalendar(zone, aLocale);
                        break;
                    case "gregory":
                        cal = new GregorianCalendar(zone, aLocale);
                        break;
                }
            }
        }
        if (cal == null) {
            if (aLocale.getLanguage() == "th" && aLocale.getCountry() == "TH") {
                cal = new BuddhistCalendar(zone, aLocale);
            } else if (aLocale.getVariant() == "JP" && aLocale.getLanguage() == "ja"
                    && aLocale.getCountry() == "JP") {
                cal = new JapaneseImperialCalendar(zone, aLocale);
            } else {
                cal = new GregorianCalendar(zone, aLocale);
            }
        }
        return cal;
    }

(2)在Loggerback中的使用

 LoggerFactory:

public static Logger getLogger(String name) {
        //工厂模式
        ILoggerFactory iLoggerFactory = getILoggerFactory();
        //iLoggerFactory是一个接口,选择其实现类LoggerContext查看其源码
        return iLoggerFactory.getLogger(name);
    }
    //通过字节码获取名字,调用上面的方法
    public static Logger getLogger(Class clazz) {
        return getLogger(clazz.getName());
    }

 LoggerContext:

//通过名字来生成对象
    public final Logger getLogger(String name) {
        //对名字进行判断,根据不同的名字生成不同的对象
        if (name == null) {
            throw new IllegalArgumentException("name argument cannot be null");
        } else if ("ROOT".equalsIgnoreCase(name)) {
            return this.root;
        } else {
            Logger logger = this.root;
            Logger childLogger = (Logger)this.loggerCache.get(name);
            if (childLogger != null) {
                return childLogger;
            }
            return childLogger;
            }
        }

在上面的代码中使用了两种设计模式,ILoggerFactory iLoggerFactory = getILoggerFactory();这是工厂模式产生了一个工厂接口用于生产对象,简单工厂主要体现在 LoggerContext中根据不同的名字来生产对象。

 5 工厂方法的使用

(1)在JDK中的使用

 在Collection类中的Iterator方法,Collection是一个接口,相当于一个抽象工厂,在此处主要关心Iterator方法,其就相当于工厂方法中的VideoFactory。

Iterator<E> iterator();

选择其中的一个实现类如:ArrayList类,就相当于前面的PythonFactory生产出Itr()对象,然后该对象实现接口,即相当于PythonVideo实现了Video接口。这里也给出了一种代码实现方式不用写新的类,直接在写一个私有方法基础即可,这一减少类。

//生产出Itr对象
public Iterator<E> iterator() {
    return Itr();
}

/**
 * Itr对象又实现了Iterator接口
 */
private class Itr implements Iterator<E> {
    //内部实现代码
}

(2)在LoggerBack中的使用

在简单工厂代码中的,ILoggerFactory就相当于videoFactory这一步就是工厂方法,三个子类继承它,然后生成不同的对象。

 

 6 几种工厂模式的比较

 简单工厂不属于23中设计模式,主要其只是简单的根据名字创建对象;工厂方法是在简单工厂中进行了抽象,利用一个专门的工厂用来生产各种对象;当要生产的对象比价复杂并且与其他对象产生了关联之后,则需要利用抽象工厂如简单生产时绿豆是一个对象,具体考虑时还有头发,武器等等这是关联的其他对象,如下图的抽象工厂类关系图:

每一个课程有视频和笔记两部分组成,视频和笔记是单独的类,课程有java和python两类。利用CourserFactory创建出两个课程的工厂,在javaFactory中会创建javaVideo和javaAritde两个对象,CourserFactory即为抽象工厂专门生产工厂,javaFactory生产对象则使用了工厂方法。

5 相关模式

(1)简单工厂与抽象工厂

简单工厂是用来选择实现的,可以选择任意接口的实现。一个简单工厂可以有多个用于选择并创建对象的方法,多个方法创建的对象可以有关系也可以没有关系。

抽象工厂模式是用来选择产品簇的实现的,也就是说一个抽象工厂里面有多个用于选择并创建对象的方法,但是这些方法所创建的对象之间通常是有关系的,这些被创建的对象通常是构成一个产品簇所需要的部件对象。所以从某种意义上来说,简单工厂和抽象工厂是类似的,如果抽象工厂退化成为只有一个实现,不分层次,那么就相当于简单工厂了。

(2)简单工厂和工厂方法模式

简单工厂和工厂方法模式也是非常类似的。工厂方法的本质也是用来选择实现的,跟简单工厂的区别在于工厂方法是把选择具体实现的功能延迟到子类去实现。如果把工厂方法中选择的实现放到父类直接实现,那就等同于简单工厂。

(3)简单工厂和能创建对象实例的模式

简单工厂的本质是选择实现,所以它可以跟其他任何能够具体的创建对象实例的模式配合使用,比如:单例模式、原型模式、生成器模式等。

(4)工厂方法模式和模板方法模式

这两个模式外观类似,都有一个抽象类,然后由子类来提供一些实现,但是工厂方法模式的子类专注的是创建产品对象,而模板方法模式的子类专注的是为固定的算法骨架提供某些步骤的实现这两个模式可以组合使用,通常在模板方法模式里面,使用工厂方法来创建模板方法需要的对象。

(5)抽象工厂模式和工厂方法模式

这两个模式既有区别,又有联系,可以组合使用。工厂方法模式一般是针对单独的产品对象的创建,而抽象工厂模式注重产品簇对象的创建,这是它们的区别。如果把抽象工厂创建的产品簇简化,这个产品簇就只有一个产品,那么这个时候的抽象工厂跟工厂方法是差不多的,也就是抽象工厂可以退化成工厂方法,而工厂方法又可以退化成简单工厂,这也是它们的联系。在抽象工厂的实现中,还可以使用工厂方法来提供抽象工厂的具体实现,也就是说它们可以组合使用。

(6)抽象工模式和单例模式

这两个模式可以组合使用在抽象工厂模式里面,具体的工厂实现,在整个应用中,通常一个产品系列只需要一个实例就可以了,因此可以把具体的工厂实现成为单例。

 

posted @ 2019-07-20 19:43  windy杨树  阅读(440)  评论(0编辑  收藏  举报