【Java】初探工厂模式

导读:工厂模式(Factory Pattern)是 Java 中最常用的设计模式之一。这种设计模式提供了一种不同于古老的new方式来创建对象,能够增加程序的可维护性、复用性,便于修改。


假设有三个课程类,英语、数学、物理,每个类只有一个属性,即课程名。现在有个“选课”的方法,输出某某同学选择了某门课。

public class Function {
    public static void chooseCourse(String name,String student){
        if(name.equals("English")){
            English english=new English();
            System.out.println(student+"选择了"+english.getName()+"课");
        }else if (name.equals("Math")){
            Math math = new Math();
            System.out.println(student+"选择了"+math.getName()+"课");
        }else if (name.equals("Physics")){
            Physics physics=new Physics();
            System.out.println(student+"选择了"+physics.getName()+"课");
        }
    }
}

再主函数中传入“课程名”和“学生”,“张三选择了数学课”。

public class test {
    public static void main(String[] args) {
        Function.chooseCourse("Math","张三");
    }
}

这种通过new一个对象的方法,有个弊端,就是当类增多时(课程增多时),这个if判断将会十分庞大,维护起来十分麻烦

问题①:不过在这之前,上面的if判断,虽然都是不同类的对象,但都有一个共同的逻辑,即输出某某选择了“某门课”,那能不能把这个逻辑抽取出来呢?


1.问题①的解决:抽取共同逻辑

抽取共同逻辑,不就把这个输出语句拿出来不就行了嘛:

public class Function {
    public static void chooseCourse(String name,String student){
        if(name.equals("English")){
            English english=new English();
        }else if (name.equals("Math")){
            Math math = new Math();
        }else if (name.equals("Physics")){
            Physics physics=new Physics();
        }
        System.out.println(student+"选择了"+physics.getName()++"课");
    }
}

但问题又来了,由于要调用某个对象的getName方法,还是需要知道具体是哪个对象。怎么解决呢?干脆定义一个变量,根据不同的情况,来赋值就行了:

public static void chooseCourse(String name,String student){
    String courseName=null;
    if(name.equals("English")){
        English english=new English();
        courseName=english.getName();
    }else if (name.equals("Math")){
        Math math = new Math();
        courseName=math.getName();
    }else if (name.equals("Physics")){
        Physics physics=new Physics();
        courseName=physics.getName();
    }
    System.out.println(student+"选择了"+courseName);
    }

问题②:上面看似解决了问题,实则不然。目前的课程只有一个属性,自然只需要一个变量来取这个属性,但当课程有N个属性的时候,就需要N个变量来取,整个if判断仍然十分繁琐。比如当增加学分和课时属性的时候,就会变成这样:

public static void chooseCourse(String name,String student){
    String courseName=null;
    Integer courseCredits=null;
    Integer courseHours=null;  
    if(name.equals("English")){
        English english=new English();
        courseName=english.getName();
        courseCredits=english.getCredits();
        courseHours=english.getHours();
    }else if (name.equals("Math")){
        Math math = new Math();
        courseName=math.getName();
        courseCredits=math.getCredits();
        courseHours=math.getHours();
    }else if (name.equals("Physics")){
        Physics physics=new Physics();
        courseName=physics.getName();
        courseCredits=physics.getCredits();
        courseHours=physics.getHours();
    }
    System.out.println(student+"选择了"+courseName+"课,该课有"+courseCredits+"个学分,一共"+courseHours+"个学时");
    }

2.问题②的解决:通过接口或者父类来处理

既然每个对象,都有Name和Credits属性,那么干脆用一个Course接口,提供getName或者getCredits方法,让具体的课程自己去实现就行了。

public interface Course {
    public String getName();
    public Integer getCredits();
}
public class English implements Course {
    private String name;
    private Integer credits;
    public English(){
        this.name="英语";
        this.credits=2;
    }
    //getter and setter
}

这样,上面的if又变简洁了不少。 

public static void chooseCourse(String name,String student){
    Course course=null;
    if(name.equals("English")){
        course=new English();
    }else if (name.equals("Math")){
        course = new Math();
    }else if (name.equals("Physics")){
        course=new Physics();
        }
    System.out.println(student+"选择了"+course.getName()+"课,此课有"+course.getCredits()+"个学分");
    }
}

问题③:但是,上面仅仅是对添加属性有效。如果要添加新的类呢?比如现在有体育、音乐、化学、生物,那这个if又会变得十分繁琐。并且,如果有了新的业务场景,比如还有学生退课、老师授课等新方法。总之,只要有创建课程对象的地方,都需要添加,维护和扩展很难。


3.问题③的解决:简单工厂

对于上面的弊端,我们可以把“创建对象”的过程封装起来,需要创建的时候,直接调用创建工厂的方法就行了

创建一个简单工厂类,将if判断封装到这个类中,用来创建各种课程的对象:

public class SimpleFactory {
    public static Course createCourse(String name){
        Course course=null;
        if(name.equals("English")){
            course=new English();
        }else if (name.equals("Math")){
            course = new Math();
        }else if (name.equals("Physics")){
            course=new Physics();
        }
        return course;
    }
}

这样,chooseCourse方法就会变得十分简洁

改进①:但这样,也有一个细节的问题。目前,所有课程判断的时候,都是手动输入的;主函数调用的时候,也是手动输入的,容易打错,维护性也低

public static void chooseCourse(String name,String student){
        Course course= SimpleFactory.createCourse(name);
        System.out.println(student+"选择了"+course.getName()+"课,此课有"+course.getCredits()+"个学分");
    }
public class test {
    public static void main(String[] args) {
        Function.chooseCourse("Math","张三");
        Function.chooseCourse("English","李四");
        Function.chooseCourse("Physics","王五");
    }
}

 4.改进①:创建常量

创建一个常量类:

public class CourseConstant {
    public static final String ENGLISH_NAME="English";
    public static final String MATH_NAME="Math";
    public static final String PHYSICS_NAME="Physics";
}

然后在上面的“简单工厂”类中修改:

//public class SimpleFunctory
public static Course createCourse(String name){
    Course course=null;
    if(name.equals(CourseConstant.ENGLISH_NAME)){
        course=new English();
    }else if (name.equals(CourseConstant.MATH_NAME)){
        course = new Math();
    }else if (name.equals(CourseConstant.PHYSICS_NAME)){
        course=new Physics();
    }
    return course;
}

//public Class test
public static void main(String[] args) {
    Function.chooseCourse(CourseConstant.ENGLISH_NAME,"张三");
    Function.chooseCourse(CourseConstant.MATH_NAME,"李四");
    Function.chooseCourse(CourseConstant.PHYSICS_NAME,"王五");
}

 改进②:上面的创建过程,其实还可以使用Java的反射机制来进一步进化。

5.改进②:使用Java反射来创建

在SimpleFactory添加一个新方法:

//public class SimpleFactory
public static Course createCourseByName(String name) throws Exception{
    return (Course)Class.forName(name).newInstance();
}

修改常量类

public class CourseConstant {
    public static final String ENGLISH_NAME="course.English";
    public static final String MATH_NAME="course.Math";
    public static final String PHYSICS_NAME="course.Physics";

修改创建的方式:

//public class Function
public static void chooseCourse(String name,String student) throws Exception {
    Course course= SimpleFactory.createCourseByName(name);
    System.out.println(student+"选择了"+course.getName()+"课,此课有"+course.getCredits()+"个学分");
}

这样,就进一步简化了。 但是问题又来了。

问题④:上面每个课程的属性,都比较简单,只是一个单纯的数据结构。如果属性里面还包含其它类,比如有授课老师这个属性,助教这个属性,课代表这个属性,那么情况又会复杂起来。

新建一个Component包,在里面创建三个类,分别是Teacher、TA和ClassRepr,每个类目前只有一个属性,即name。

那么现在English和其它课程类,就要添加这些属性。

public class English implements Course {
    private String name;
    private Integer credits;
    private Teacher teacher;
    private ClassRepr classRepr;
    //getter and setter
}

public class Math implements Course {
    private String name;
    private Integer credits;
    private Teacher teacher;
    private TA ta;
    private ClassRepr classRepr;
    //getter and setter
}

 接下来,在简单工厂类里面:

public static Course createCourse(String name){
    Course course=null;
    if(name.equals(CourseConstant.ENGLISH_NAME)){
        English english=new English();
            
        Teacher teacher=new Teacher();
        teacher.setName("刘老师");
        english.setTeacher(teacher);
            
        ClassRepr classRepr=new ClassRepr();
        classRepr.setName("李同学");
        english.setClassRepr(classRepr);
            
        course=english;
    }else if (name.equals(CourseConstant.MATH_NAME)){
        Math math = new Math();
        Teacher teacher=new Teacher();
        teacher.setName("何老师");
        math.setTeacher(teacher);
        TA ta=new TA();
        ta.setName("郭同学");
        math.setTa(ta);
        ClassRepr classRepr=new ClassRepr();
        classRepr.setName("贺同学");
        math.setClassRepr(classRepr);
        course=math;
    }else if (name.equals(CourseConstant.PHYSICS_NAME)){
        Physics physics=new Physics();
        Teacher teacher=new Teacher();
        teacher.setName("何老师");
        physics.setTeacher(teacher);
        TA ta=new TA();
        ta.setName("郭同学");
        physics.setTa(ta);
        ClassRepr classRepr=new ClassRepr();
        classRepr.setName("贺同学");
        physics.setClassRepr(classRepr);
        course=physics;
    }
    return course;
}

代码量陡然增加! 而且之间的反射方法,也不能用了。除此之外,代码复用性和扩展性也变差了很多,不符合开闭原则(修改跟扩展的分离),无论是修改还是扩展还是复用,都需要在上面的代码做修改。


6.问题④的解决:工厂方法

解决的思路就是把每一个类的创建都交给一个工厂类,而不是用一个工厂类来一揽子创建所有不同的类

创建三个相应的工厂类:

public class EnglishFactory {
    public Course createCourse(){
        English english=new English();
        Teacher teacher=new Teacher();
        teacher.setName("刘老师");
        english.setTeacher(teacher);
        ClassRepr classRepr=new ClassRepr();
        classRepr.setName("李同学");
        english.setClassRepr(classRepr);
        return english;
    }
}

public class MathFactory {
    public Course createCourse(){
        Math math = new Math();
        Teacher teacher=new Teacher();
        teacher.setName("何老师");
        math.setTeacher(teacher);
        TA ta=new TA();
        ta.setName("郭同学");
        math.setTa(ta);
        ClassRepr classRepr=new ClassRepr();
        classRepr.setName("贺同学");
        math.setClassRepr(classRepr);
        return math;
    }
}

public class PhysicsFactory {
    public Course createCourse(){
        Physics physics=new Physics();
        Teacher teacher=new Teacher();
        teacher.setName("何老师");
        physics.setTeacher(teacher);
        TA ta=new TA();
        ta.setName("郭同学");
        physics.setTa(ta);
        ClassRepr classRepr=new ClassRepr();
        classRepr.setName("贺同学");
        physics.setClassRepr(classRepr);
        return physics;
    }
}

 由于每个工厂都有createCourse方法,还需要封装到一个接口里面:

public interface Factory {
    public Course createCourse();
}

再让每个工厂实现这个接口。

同时还需要编写一个工厂创建的类,用来判断到底用哪个工厂进行创建:

public class FactoryBuilder {
    public static Factory build(String name){
        Factory factory=null;
        if(name.equals(CourseConstant.ENGLISH_NAME)){
            factory=new EnglishFactory();
        }else if (name.equals(CourseConstant.MATH_NAME)){
            factory = new MathFactory();
        }else if (name.equals(CourseConstant.PHYSICS_NAME)){
            factory=new PhysicsFactory();
        }
        return factory;
    }
}

 然后进行调用:

public class Function {  
    public static void chooseCourse(String name,String student) throws Exception {
        Factory factory= FactoryBuilder.build(name);
        Course course=factory.createCourse();
        System.out.println(student+"选择了"+course.getName()+"课,此课有"+course.getCredits()+"个学分");
    }
}

这样,如果有新的课程,直接创建新的课程工厂实现工厂接口,添加一条创建判断就行了。 

同时,上面的FactoryBuilder又可以用反射来处理了:

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

只不过是反射工厂对象罢了。 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章